diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-11-21 15:48:26 -0500 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-11-21 15:48:26 -0500 |
| commit | 17bacee0890cb03a579f9469e435d922bbdfdd50 (patch) | |
| tree | 7445da7bb2087c35075a37b6a1d8a71db480381a | |
| parent | ee1a746031f15bdfc5c5e38fab704f5fcb9b67ee (diff) | |
consolidate Artist & MusicArtist
35 files changed, 246 insertions, 500 deletions
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index d01e96a5a..ee0721d5e 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Api private readonly char[] _dashReplaceChars = new[] { '?', '/' }; private const char SlugChar = '-'; - protected Artist GetArtist(string name, ILibraryManager libraryManager) + protected MusicArtist GetArtist(string name, ILibraryManager libraryManager) { return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager)); } @@ -147,21 +147,7 @@ namespace MediaBrowser.Api return name; } - return libraryManager.RootFolder.GetRecursiveChildren() - .OfType<Audio>() - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + return libraryManager.GetAllArtists() .FirstOrDefault(i => { i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index ee313ea3b..4e8864644 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -194,20 +194,7 @@ namespace MediaBrowser.Api.DefaultTheme .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) .ToList(); - var artists = allItems.OfType<Audio>() - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + var artists = _libraryManager.GetAllArtists(allItems) .Randomize() .Select(i => { diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 260d93860..8fca3f338 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -264,16 +264,14 @@ namespace MediaBrowser.Api { var item = _dtoService.GetItemByDtoId(request.Id); - var folder = item as Folder; - try { await item.RefreshMetadata(CancellationToken.None, forceRefresh: request.Forced).ConfigureAwait(false); - if (folder != null) + if (item.IsFolder) { // Collection folders don't validate their children so we'll have to simulate that here - var collectionFolder = folder as CollectionFolder; + var collectionFolder = item as CollectionFolder; if (collectionFolder != null) { @@ -281,6 +279,8 @@ namespace MediaBrowser.Api } else { + var folder = (Folder)item; + await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced).ConfigureAwait(false); } } @@ -303,10 +303,10 @@ namespace MediaBrowser.Api { await child.RefreshMetadata(CancellationToken.None, forceRefresh: request.Forced).ConfigureAwait(false); - var folder = child as Folder; - - if (folder != null) + if (child.IsFolder) { + var folder = (Folder)child; + await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index cfbaf70c9..10149906c 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -126,15 +126,6 @@ namespace MediaBrowser.Api UpdateItem(request, item); await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - var musicArtist = Artist.FindMusicArtist(item, _libraryManager); - - if (musicArtist != null) - { - UpdateItem(request, musicArtist); - - await _libraryManager.UpdateItem(musicArtist, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } } public void Post(UpdateStudio request) diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index 06b90b32c..3863ef209 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -367,7 +367,7 @@ namespace MediaBrowser.Api BoxSetCount = boxsets.Count, BookCount = books.Count, - UniqueTypes = items.Select(i => i.GetType().Name).Distinct().ToList() + UniqueTypes = items.Select(i => i.GetClientTypeName()).Distinct().ToList() }; var people = items.SelectMany(i => i.People) @@ -390,19 +390,7 @@ namespace MediaBrowser.Api people = request.UserId.HasValue ? FilterItems(people, request, request.UserId.Value).ToList() : people; counts.PersonCount = people.Count; - var artists = items.OfType<Audio>().SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + var artists = _libraryManager.GetAllArtists(items) .Select(i => { try diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index f87434e97..113df4e38 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -144,7 +144,7 @@ namespace MediaBrowser.Api IndexNumber = item.IndexNumber, ParentIndexNumber = item.ParentIndexNumber, ItemId = _dtoService.GetDtoId(item), - Type = item.GetType().Name, + Type = item.GetClientTypeName(), MediaType = item.MediaType, MatchedTerm = hintInfo.MatchedTerm, DisplayMediaType = item.DisplayMediaType, @@ -187,9 +187,7 @@ namespace MediaBrowser.Api result.SongCount = songs.Count; - result.Artists = songs - .SelectMany(i => i.Artists) - .Distinct(StringComparer.OrdinalIgnoreCase) + result.Artists = _libraryManager.GetAllArtists(songs) .ToArray(); result.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i)); diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index ca626116a..4dd3d744f 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class ArtistsService /// </summary> - public class ArtistsService : BaseItemsByNameService<Artist> + public class ArtistsService : BaseItemsByNameService<MusicArtist> { /// <summary> /// Initializes a new instance of the <see cref="ArtistsService" /> class. @@ -109,24 +109,9 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> /// <param name="items">The items.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<Artist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) + protected override IEnumerable<MusicArtist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { - var itemsList = items.OfType<Audio>().ToList(); - - return itemsList - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + return LibraryManager.GetAllArtists(items) .Select(name => LibraryManager.GetArtist(name)); } } diff --git a/MediaBrowser.Controller/Entities/Audio/Artist.cs b/MediaBrowser.Controller/Entities/Audio/Artist.cs deleted file mode 100644 index 947ee1122..000000000 --- a/MediaBrowser.Controller/Entities/Audio/Artist.cs +++ /dev/null @@ -1,86 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Runtime.Serialization; - -namespace MediaBrowser.Controller.Entities.Audio -{ - /// <summary> - /// Class Artist - /// </summary> - public class Artist : BaseItem, IItemByName, IHasMusicGenres - { - public Artist() - { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); - } - - public string LastFmImageUrl { get; set; } - public string LastFmImageSize { get; set; } - - /// <summary> - /// Gets the user data key. - /// </summary> - /// <returns>System.String.</returns> - public override string GetUserDataKey() - { - return GetUserDataKey(this); - } - - [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } - - /// <summary> - /// Finds the music artist. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="libraryManager">The library manager.</param> - /// <returns>MusicArtist.</returns> - public static MusicArtist FindMusicArtist(Artist artist, ILibraryManager libraryManager) - { - return FindMusicArtist(artist, libraryManager.RootFolder.RecursiveChildren.OfType<MusicArtist>()); - } - - /// <summary> - /// Finds the music artist. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="allMusicArtists">All music artists.</param> - /// <returns>MusicArtist.</returns> - public static MusicArtist FindMusicArtist(Artist artist, IEnumerable<MusicArtist> allMusicArtists) - { - var musicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz); - - return allMusicArtists.FirstOrDefault(i => - { - if (!string.IsNullOrWhiteSpace(musicBrainzId) && string.Equals(musicBrainzId, i.GetProviderId(MetadataProviders.Musicbrainz), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return string.Compare(i.Name, artist.Name, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0; - }); - } - - /// <summary> - /// Gets the user data key. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>System.String.</returns> - public static string GetUserDataKey(BaseItem item) - { - var id = item.GetProviderId(MetadataProviders.Musicbrainz); - - if (!string.IsNullOrEmpty(id)) - { - return "Artist-Musicbrainz-" + id; - } - - return "Artist-" + item.Name; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index d791c92ae..a234844ff 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,11 +1,52 @@ - +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + namespace MediaBrowser.Controller.Entities.Audio { /// <summary> /// Class MusicArtist /// </summary> - public class MusicArtist : Folder + public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess { + [IgnoreDataMember] + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + + public bool IsAccessedByName { get; set; } + + public override bool IsFolder + { + get + { + return !IsAccessedByName; + } + } + + protected override IEnumerable<BaseItem> ActualChildren + { + get + { + if (IsAccessedByName) + { + throw new InvalidOperationException("Artists accessed by name do not have children."); + } + + return base.ActualChildren; + } + } + + public override string GetClientTypeName() + { + if (IsAccessedByName) + { + //return "Artist"; + } + + return base.GetClientTypeName(); + } + /// <summary> /// Gets or sets the last fm image URL. /// </summary> @@ -13,13 +54,35 @@ namespace MediaBrowser.Controller.Entities.Audio public string LastFmImageUrl { get; set; } public string LastFmImageSize { get; set; } + public MusicArtist() + { + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + /// <summary> /// Gets the user data key. /// </summary> /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Artist.GetUserDataKey(this); + return GetUserDataKey(this); + } + + /// <summary> + /// Gets the user data key. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public static string GetUserDataKey(BaseItem item) + { + var id = item.GetProviderId(MetadataProviders.Musicbrainz); + + if (!string.IsNullOrEmpty(id)) + { + return "Artist-Musicbrainz-" + id; + } + + return "Artist-" + item.Name; } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 29765fe12..a10a2c263 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1145,6 +1145,11 @@ namespace MediaBrowser.Controller.Entities return changed; } + public virtual string GetClientTypeName() + { + return GetType().Name; + } + /// <summary> /// Determines if the item is considered new based on user settings /// </summary> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8dbc98193..5c561dc0f 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1038,16 +1038,13 @@ namespace MediaBrowser.Controller.Entities } } - if (recursive) + if (recursive && child.IsFolder) { - var folder = child as Folder; + var folder = (Folder)child; - if (folder != null) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) { - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) - { - hasLinkedChildren = true; - } + hasLinkedChildren = true; } } } @@ -1150,14 +1147,11 @@ namespace MediaBrowser.Controller.Entities list.Add(child); } - if (recursive) + if (recursive && child.IsFolder) { - var folder = child as Folder; + var folder = (Folder)child; - if (folder != null) - { - folder.AddChildrenToList(list, true, filter); - } + folder.AddChildrenToList(list, true, filter); } } } diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index 7284bf101..1cb375374 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -12,6 +12,11 @@ namespace MediaBrowser.Controller.Entities Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } + public interface IHasDualAccess : IItemByName + { + bool IsAccessedByName { get; } + } + public static class IItemByNameExtensions { public static ItemByNameCounts GetItemByNameCounts(this IItemByName item, User user) diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 0a89c0df8..338edd568 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="name">The name.</param> /// <returns>Task{Artist}.</returns> - Artist GetArtist(string name); + MusicArtist GetArtist(string name); /// <summary> /// Gets a Studio @@ -302,5 +302,18 @@ namespace MediaBrowser.Controller.Library /// <param name="updateType">Type of the update.</param> /// <returns>Task.</returns> Task SaveMetadata(BaseItem item, ItemUpdateType updateType); + + /// <summary> + /// Gets all artists. + /// </summary> + /// <returns>IEnumerable{System.String}.</returns> + IEnumerable<string> GetAllArtists(); + + /// <summary> + /// Gets all artists. + /// </summary> + /// <param name="items">The items.</param> + /// <returns>IEnumerable{System.String}.</returns> + IEnumerable<string> GetAllArtists(IEnumerable<BaseItem> items); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8837d04f5..393107f1e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -115,7 +115,6 @@ <Compile Include="Session\ISessionManager.cs" /> <Compile Include="Drawing\ImageExtensions.cs" /> <Compile Include="Entities\AggregateFolder.cs" /> - <Compile Include="Entities\Audio\Artist.cs" /> <Compile Include="Entities\Audio\Audio.cs" /> <Compile Include="Entities\Audio\MusicAlbum.cs" /> <Compile Include="Entities\Audio\MusicArtist.cs" /> diff --git a/MediaBrowser.Providers/FolderProviderFromXml.cs b/MediaBrowser.Providers/FolderProviderFromXml.cs index 449de7450..8459fceef 100644 --- a/MediaBrowser.Providers/FolderProviderFromXml.cs +++ b/MediaBrowser.Providers/FolderProviderFromXml.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Providers /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return item is Folder && item.LocationType == LocationType.FileSystem; + return item.IsFolder && item.LocationType == LocationType.FileSystem; } /// <summary> diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 00924675a..46bce4909 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -81,11 +81,9 @@ <Compile Include="Music\ArtistInfoFromSongProvider.cs" /> <Compile Include="Music\ArtistProviderFromXml.cs" /> <Compile Include="Music\FanArtAlbumProvider.cs" /> - <Compile Include="Music\FanArtArtistByNameProvider.cs" /> <Compile Include="Music\FanArtArtistProvider.cs" /> <Compile Include="Music\FanArtUpdatesPrescanTask.cs" /> <Compile Include="Music\LastfmAlbumProvider.cs" /> - <Compile Include="Music\LastfmArtistByNameProvider.cs" /> <Compile Include="Music\LastFmImageProvider.cs" /> <Compile Include="Music\LastfmArtistProvider.cs" /> <Compile Include="Music\LastfmBaseProvider.cs" /> diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index 8ad2761cd..5f285e6d8 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -1,5 +1,4 @@ -using System.Linq; -using MediaBrowser.Common.MediaInfo; +using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaInfo; @@ -11,6 +10,7 @@ using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs b/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs index b5a3b92f9..7069e65ef 100644 --- a/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs +++ b/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs @@ -27,10 +27,15 @@ namespace MediaBrowser.Providers.Music protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { - // If song metadata has changed - if (GetComparisonData((MusicArtist)item) != providerInfo.FileStamp) + var artist = (MusicArtist)item; + + if (!artist.IsAccessedByName) { - return true; + // If song metadata has changed + if (GetComparisonData(artist) != providerInfo.FileStamp) + { + return true; + } } return base.NeedsRefreshInternal(item, providerInfo); @@ -47,7 +52,7 @@ namespace MediaBrowser.Providers.Music return GetComparisonData(songs); } - private Guid GetComparisonData(List<Audio> songs) + private Guid GetComparisonData(IEnumerable<Audio> songs) { var genres = songs.SelectMany(i => i.Genres) .Distinct(StringComparer.OrdinalIgnoreCase) @@ -60,23 +65,26 @@ namespace MediaBrowser.Providers.Music { var artist = (MusicArtist)item; - BaseProviderInfo data; - if (!item.ProviderData.TryGetValue(Id, out data)) + if (!artist.IsAccessedByName) { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } + BaseProviderInfo data; + if (!item.ProviderData.TryGetValue(Id, out data)) + { + data = new BaseProviderInfo(); + item.ProviderData[Id] = data; + } - var songs = artist.RecursiveChildren.OfType<Audio>().ToList(); + var songs = artist.RecursiveChildren.OfType<Audio>().ToList(); - if (!item.LockedFields.Contains(MetadataFields.Genres)) - { - artist.Genres = songs.SelectMany(i => i.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } + if (!item.LockedFields.Contains(MetadataFields.Genres)) + { + artist.Genres = songs.SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } - data.FileStamp = GetComparisonData(songs); + data.FileStamp = GetComparisonData(songs); + } SetLastRefreshed(item, DateTime.UtcNow); return TrueTaskResult; diff --git a/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs b/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs index 8cc49ac39..b7e846353 100644 --- a/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs +++ b/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Music /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return (item is Artist || item is MusicArtist) && item.LocationType == LocationType.FileSystem; + return (item is MusicArtist) && item.LocationType == LocationType.FileSystem; } /// <summary> @@ -88,16 +88,7 @@ namespace MediaBrowser.Providers.Music try { - var artist = item as Artist; - - if (artist != null) - { - new BaseItemXmlParser<Artist>(Logger).Fetch(artist, path, cancellationToken); - } - else - { - new BaseItemXmlParser<MusicArtist>(Logger).Fetch((MusicArtist)item, path, cancellationToken); - } + new BaseItemXmlParser<MusicArtist>(Logger).Fetch((MusicArtist)item, path, cancellationToken); } finally { diff --git a/MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs deleted file mode 100644 index 5d18f16ff..000000000 --- a/MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.Providers.Music -{ - /// <summary> - /// Class FanArtArtistByNameProvider - /// </summary> - public class FanArtArtistByNameProvider : FanArtArtistProvider - { - /// <summary> - /// Initializes a new instance of the <see cref="FanArtArtistByNameProvider" /> class. - /// </summary> - public FanArtArtistByNameProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem) - : base(httpClient, logManager, configurationManager, providerManager, fileSystem) - { - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item is Artist; - } - - /// <summary> - /// Gets a value indicating whether [save local meta]. - /// </summary> - /// <value><c>true</c> if [save local meta]; otherwise, <c>false</c>.</value> - protected override bool SaveLocalMeta - { - get - { - return true; - } - } - } -} diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs index cd4005223..a2ae597d7 100644 --- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Music /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return item is Artist || item is MusicArtist || item is MusicAlbum; + return item is MusicArtist || item is MusicAlbum; } /// <summary> diff --git a/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs deleted file mode 100644 index 26ac68c3d..000000000 --- a/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Music -{ - /// <summary> - /// Class LastfmArtistByNameProvider - /// </summary> - public class LastfmArtistByNameProvider : LastfmArtistProvider - { - /// <summary> - /// Initializes a new instance of the <see cref="LastfmArtistByNameProvider" /> class. - /// </summary> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="libraryManager">The library manager.</param> - public LastfmArtistByNameProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, ILibraryManager libraryManager) - : base(jsonSerializer, httpClient, logManager, configurationManager, libraryManager) - { - } - - /// <summary> - /// Gets a value indicating whether [save local meta]. - /// </summary> - /// <value><c>true</c> if [save local meta]; otherwise, <c>false</c>.</value> - protected override bool SaveLocalMeta - { - get - { - return true; - } - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item is Artist; - } - - /// <summary> - /// Gets the provider version. - /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion - { - get - { - return "7"; - } - } - - /// <summary> - /// Fetches the lastfm data. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="musicBrainzId">The music brainz id.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - protected override async Task FetchLastfmData(BaseItem item, string musicBrainzId, bool force, CancellationToken cancellationToken) - { - var artist = (Artist)item; - - // See if we can avoid an http request by finding the matching MusicArtist entity - var musicArtist = Artist.FindMusicArtist(artist, LibraryManager); - - if (musicArtist != null && !force) - { - LastfmHelper.ProcessArtistData(musicArtist, artist); - } - else - { - await base.FetchLastfmData(item, musicBrainzId, force, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs index 3113d2541..2a7977fa6 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs @@ -84,17 +84,6 @@ namespace MediaBrowser.Providers.Music /// <returns>Task{System.String}.</returns> private async Task<string> FindId(BaseItem item, CancellationToken cancellationToken) { - if (item is Artist) - { - // Since MusicArtists are refreshed first, try to find it from one of them - var id = FindIdFromMusicArtistEntity(item); - - if (!string.IsNullOrEmpty(id)) - { - return id; - } - } - try { // If we don't get anything, go directly to music brainz diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs index 800fdc438..8e2aed50e 100644 --- a/MediaBrowser.Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Providers/Music/LastfmHelper.cs @@ -39,21 +39,12 @@ namespace MediaBrowser.Providers.Music var musicArtist = artist as MusicArtist; - string imageSize; - if (musicArtist != null) { + string imageSize; musicArtist.LastFmImageUrl = GetImageUrl(data, out imageSize); musicArtist.LastFmImageSize = imageSize; } - - var artistByName = artist as Artist; - - if (artistByName != null) - { - artistByName.LastFmImageUrl = GetImageUrl(data, out imageSize); - artistByName.LastFmImageSize = imageSize; - } } private static string GetImageUrl(IHasLastFmImages data, out string size) @@ -85,15 +76,6 @@ namespace MediaBrowser.Providers.Music return null; } - public static void ProcessArtistData(MusicArtist source, Artist target) - { - target.PremiereDate = source.PremiereDate; - target.ProductionYear = source.ProductionYear; - target.Tags = source.Tags.ToList(); - target.Overview = source.Overview; - target.ProductionLocations = source.ProductionLocations.ToList(); - } - public static void ProcessAlbumData(BaseItem item, LastfmAlbum data) { var overview = data.wiki != null ? data.wiki.content : null; diff --git a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs index 6ff891f60..cdb07d3d7 100644 --- a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs +++ b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Music public bool Supports(BaseItem item) { - return item is MusicArtist || item is Artist; + return item is MusicArtist; } public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs index 0b1b95b1e..72e8c6f6b 100644 --- a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Music public bool Supports(BaseItem item) { - return item is MusicAlbum || item is MusicArtist || item is Artist; + return item is MusicAlbum || item is MusicArtist; } public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) @@ -41,13 +41,6 @@ namespace MediaBrowser.Providers.Music RemoteImageInfo info = null; - var artist = item as Artist; - - if (artist != null) - { - info = GetInfo(artist.LastFmImageUrl, artist.LastFmImageSize); - } - var album = item as MusicAlbum; if (album != null) { diff --git a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs index 55331db93..81f092a46 100644 --- a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs @@ -43,7 +43,8 @@ namespace MediaBrowser.Providers.Savers // If new metadata has been downloaded or metadata was manually edited, proceed if (wasMetadataDownloaded || wasMetadataEdited) { - if (item is Artist) + var artist = item as MusicArtist; + if (artist != null && artist.IsAccessedByName) { return true; } diff --git a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs index 8c19e2c28..319f6c7d0 100644 --- a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType) { - if (!(item is Folder)) + if (!item.IsFolder) { return false; } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 4efafcd77..dd1e2b9fa 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -277,7 +277,7 @@ namespace MediaBrowser.Server.Implementations.Dto Id = GetDtoId(item), Name = item.Name, MediaType = item.MediaType, - Type = item.GetType().Name, + Type = item.GetClientTypeName(), RunTimeTicks = item.RunTimeTicks }; @@ -932,7 +932,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.RemoteTrailers = item.RemoteTrailers; } - dto.Type = item.GetType().Name; + dto.Type = item.GetClientTypeName(); dto.CommunityRating = item.CommunityRating; dto.VoteCount = item.VoteCount; diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 6a2df70b1..58a2fcd7e 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -391,10 +391,23 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="item">The item.</param> private void UpdateItemInLibraryCache(BaseItem item) { - if (!(item is IItemByName)) + if (item is IItemByName) { - LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); + var hasDualAccess = item as IHasDualAccess; + if (hasDualAccess != null) + { + if (hasDualAccess.IsAccessedByName) + { + return; + } + } + else + { + return; + } } + + LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); } /// <summary> @@ -657,16 +670,6 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> - /// Gets a Genre - /// </summary> - /// <param name="name">The name.</param> - /// <returns>Task{Genre}.</returns> - public Artist GetArtist(string name) - { - return GetItemByName<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name); - } - - /// <summary> /// The us culture /// </summary> private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -688,6 +691,16 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> + /// Gets a Genre + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{Genre}.</returns> + public MusicArtist GetArtist(string name) + { + return GetItemByName<MusicArtist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name); + } + + /// <summary> /// The images by name item cache /// </summary> private readonly ConcurrentDictionary<string, BaseItem> _itemsByName = new ConcurrentDictionary<string, BaseItem>(StringComparer.OrdinalIgnoreCase); @@ -697,12 +710,12 @@ namespace MediaBrowser.Server.Implementations.Library { if (string.IsNullOrEmpty(path)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("path"); } if (string.IsNullOrEmpty(name)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("name"); } var validFilename = _fileSystem.GetValidFilename(name).Trim(); @@ -743,6 +756,20 @@ namespace MediaBrowser.Server.Implementations.Library private Tuple<bool, T> CreateItemByName<T>(string path, string name) where T : BaseItem, new() { + var isArtist = typeof(T) == typeof(MusicArtist); + + if (isArtist) + { + var existing = RootFolder.RecursiveChildren + .OfType<T>() + .FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (existing != null) + { + return new Tuple<bool, T>(false, existing); + } + } + var fileInfo = new DirectoryInfo(path); var isNew = false; @@ -779,6 +806,11 @@ namespace MediaBrowser.Server.Implementations.Library isNew = true; } + if (isArtist) + { + (item as MusicArtist).IsAccessedByName = true; + } + // Set this now so we don't cause additional file system access during provider executions item.ResetResolveArgs(fileInfo); @@ -1363,16 +1395,19 @@ namespace MediaBrowser.Server.Implementations.Library { var item = ItemRepository.RetrieveItem(id); - var folder = item as Folder; - - if (folder != null) + if (item != null && item.IsFolder) { - folder.LoadSavedChildren(); + LoadSavedChildren(item as Folder); } return item; } + private void LoadSavedChildren(Folder item) + { + item.LoadSavedChildren(); + } + private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); /// <summary> @@ -1470,5 +1505,30 @@ namespace MediaBrowser.Server.Implementations.Library return collectionTypes.Count == 1 ? collectionTypes[0] : null; } + + + public IEnumerable<string> GetAllArtists() + { + return GetAllArtists(RootFolder.RecursiveChildren); + } + + public IEnumerable<string> GetAllArtists(IEnumerable<BaseItem> items) + { + return items + .OfType<Audio>() + .SelectMany(i => + { + var list = new List<string>(); + + if (!string.IsNullOrEmpty(i.AlbumArtist)) + { + list.Add(i.AlbumArtist); + } + list.AddRange(i.Artists); + + return list; + }) + .Distinct(StringComparer.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs index d5fccc6fd..b194b2e94 100644 --- a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs @@ -74,21 +74,7 @@ namespace MediaBrowser.Server.Implementations.Library })); // Find artists - var artists = items.OfType<Audio>() - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Where(i => !string.IsNullOrEmpty(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) + var artists = _libraryManager.GetAllArtists(items) .ToList(); foreach (var item in artists) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 7d46d7060..1984a2420 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -57,7 +57,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var allItems = _libraryManager.RootFolder.GetRecursiveChildren(); - var allMusicArtists = allItems.OfType<MusicArtist>().ToList(); var allSongs = allItems.OfType<Audio>().ToList(); var innerProgress = new ActionableProgress<double>(); @@ -80,36 +79,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { cancellationToken.ThrowIfCancellationRequested(); - artist.ValidateImages(); - artist.ValidateBackdrops(); - - var musicArtist = Artist.FindMusicArtist(artist, allMusicArtists); - - if (musicArtist != null) - { - MergeImages(musicArtist.Images, artist.Images); - - // Merge backdrops - var additionalBackdrops = musicArtist - .BackdropImagePaths - .Except(artist.BackdropImagePaths) - .ToList(); - - var sources = additionalBackdrops - .Select(musicArtist.GetImageSourceInfo) - .Where(i => i != null) - .ToList(); - - foreach (var path in additionalBackdrops) - { - artist.RemoveImageSourceForPath(path); - } - - artist.BackdropImagePaths.AddRange(additionalBackdrops); - artist.ImageSources.AddRange(sources); - } - - if (!artist.LockedFields.Contains(MetadataFields.Genres)) + // Only do this for artists accessed by name. Folder-based artists use ArtistInfoFromSongsProvider + if (artist.IsAccessedByName && !artist.LockedFields.Contains(MetadataFields.Genres)) { // Avoid implicitly captured closure var artist1 = artist; @@ -145,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <param name="artist">The artist.</param> /// <param name="userId">The user id.</param> /// <param name="allItems">All items.</param> - private void SetItemCounts(Artist artist, Guid? userId, IEnumerable<IHasArtist> allItems) + private void SetItemCounts(MusicArtist artist, Guid? userId, IEnumerable<IHasArtist> allItems) { var name = artist.Name; @@ -171,50 +142,18 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } /// <summary> - /// Merges the images. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="target">The target.</param> - private void MergeImages(Dictionary<ImageType, string> source, Dictionary<ImageType, string> target) - { - foreach (var key in source.Keys - .Where(k => !target.ContainsKey(k))) - { - string path; - - if (source.TryGetValue(key, out path)) - { - target[key] = path; - } - } - } - - /// <summary> /// Gets all artists. /// </summary> /// <param name="allSongs">All songs.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> /// <returns>Task{Artist[]}.</returns> - private async Task<List<Artist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) + private async Task<List<MusicArtist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) { - var allArtists = allSongs - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + var allArtists = _libraryManager.GetAllArtists(allSongs) .ToList(); - var returnArtists = new List<Artist>(allArtists.Count); + var returnArtists = new List<MusicArtist>(allArtists.Count); var numComplete = 0; var numArtists = allArtists.Count; diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index d9ee9e219..85b17888e 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -508,7 +508,7 @@ namespace MediaBrowser.Server.Implementations.Providers return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) }; } - if (item is MusicAlbum || item is Artist || item is MusicArtist) + if (item is MusicAlbum || item is MusicArtist) { return new[] { Path.Combine(item.MetaLocation, "folder" + extension) }; } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 1e867ecef..681479eb7 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -495,6 +495,7 @@ namespace MediaBrowser.WebDashboard.Api "moviestudios.js", "movietrailers.js", "musicalbums.js", + "musicalbumartists.js", "musicartists.js", "musicgenres.js", "musicrecommended.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 071120ee0..135c15072 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -80,6 +80,9 @@ </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ApiClient.js" />
+ <Content Include="dashboard-ui\musicalbumartists.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\allusersettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -341,6 +344,9 @@ <Content Include="dashboard-ui\livetvrecordings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\musicalbumartists.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvchannels.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
|
