Updating tvdb providers to use item identities

This commit is contained in:
Thomas Gillen 2014-05-06 03:00:16 +01:00
parent a14e919a85
commit 0a3381471d
13 changed files with 231 additions and 55 deletions

View File

@ -32,6 +32,7 @@ namespace MediaBrowser.Controller.Entities
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
LockedFields = new List<MetadataFields>();
ImageInfos = new List<ItemImageInfo>();
Identities = new List<IItemIdentity>();
}
/// <summary>
@ -244,6 +245,9 @@ namespace MediaBrowser.Controller.Entities
public bool IsUnidentified { get; set; }
[IgnoreDataMember]
public List<IItemIdentity> Identities { get; set; }
/// <summary>
/// Gets or sets the locked fields.
/// </summary>

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
@ -55,5 +56,10 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value><c>true</c> if this instance is unidentified; otherwise, <c>false</c>.</value>
bool IsUnidentified { get; set; }
/// <summary>
/// Gets the item identities.
/// </summary>
List<IItemIdentity> Identities { get; set; }
}
}

View File

@ -194,6 +194,7 @@
<Compile Include="Security\IEncryptionManager.cs" />
<Compile Include="Subtitles\ISubtitleManager.cs" />
<Compile Include="Subtitles\ISubtitleProvider.cs" />
<Compile Include="Providers\ItemIdentifier.cs" />
<Compile Include="Providers\ItemIdentities.cs" />
<Compile Include="Providers\ItemLookupInfo.cs" />
<Compile Include="Providers\MetadataRefreshOptions.cs" />

View File

@ -138,5 +138,12 @@ namespace MediaBrowser.Controller.Providers
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
IEnumerable<IItemIdentityProvider<TLookupInfo, TIdentity>> GetItemIdentityProviders<TLookupInfo, TIdentity>()
where TLookupInfo : ItemLookupInfo
where TIdentity : IItemIdentity;
IEnumerable<IItemIdentityConverter<TIdentity>> GetItemIdentityConverters<TIdentity>()
where TIdentity : IItemIdentity;
}
}

View File

@ -1,18 +1,15 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Providers.Manager
namespace MediaBrowser.Controller.Providers
{
public class ItemIdentifier<TLookupInfo, TIdentity>
where TLookupInfo : ItemLookupInfo
where TIdentity : IItemIdentity
{
public async Task<IEnumerable<TIdentity>> FindIdentities(TLookupInfo item, ProviderManager providerManager, CancellationToken cancellationToken)
public async Task<IEnumerable<TIdentity>> FindIdentities(TLookupInfo item, IProviderManager providerManager, CancellationToken cancellationToken)
{
var providers = providerManager.GetItemIdentityProviders<TLookupInfo, TIdentity>();
var converters = providerManager.GetItemIdentityConverters<TIdentity>();
@ -27,8 +24,11 @@ namespace MediaBrowser.Providers.Manager
Order = provider.Order
};
if (!Equals(result.Identity, default(TIdentity)))
{
identities.Add(result);
}
}
var convertersAvailable = new List<IItemIdentityConverter<TIdentity>>(converters);
bool changesMade;
@ -43,7 +43,7 @@ namespace MediaBrowser.Providers.Manager
var input = identities.FirstOrDefault(id => id.Identity.Type == converter.SourceType);
var existing = identities.Where(id => id.Identity.Type == converter.ResultType);
if (input != null && !existing.Any(id => id.Order >= converter.Order))
if (input != null && !existing.Any(id => id.Order <= converter.Order))
{
var result = new IdentityPair
{
@ -51,11 +51,14 @@ namespace MediaBrowser.Providers.Manager
Order = converter.Order
};
if (!Equals(result.Identity, default(TIdentity)))
{
identities.Add(result);
convertersAvailable.RemoveAt(i);
changesMade = true;
}
}
}
} while (changesMade);
return identities.Select(id => id.Identity);

View File

@ -1,4 +1,7 @@
using MediaBrowser.Model.Entities;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
@ -96,8 +99,10 @@ namespace MediaBrowser.Controller.Providers
public string Path { get; set; }
}
public class EpisodeInfo : ItemLookupInfo
public class EpisodeInfo : ItemLookupInfo, IHasIdentities<EpisodeIdentity>
{
private List<EpisodeIdentity> _identities = new List<EpisodeIdentity>();
public Dictionary<string, string> SeriesProviderIds { get; set; }
public int? IndexNumberEnd { get; set; }
@ -107,6 +112,27 @@ namespace MediaBrowser.Controller.Providers
{
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public IEnumerable<EpisodeIdentity> Identities
{
get { return _identities; }
}
public async Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken)
{
var identifier = new ItemIdentifier<EpisodeInfo, EpisodeIdentity>();
_identities = (await identifier.FindIdentities(this, providerManager, cancellationToken)).ToList();
}
}
public class EpisodeIdentity : IItemIdentity
{
public string Type { get; set; }
public string SeriesId { get; set; }
public int? SeasonIndex { get; set; }
public int IndexNumber { get; set; }
public int? IndexNumberEnd { get; set; }
}
public class SongInfo : ItemLookupInfo
@ -116,9 +142,29 @@ namespace MediaBrowser.Controller.Providers
public List<string> Artists { get; set; }
}
public class SeriesInfo : ItemLookupInfo
public class SeriesInfo : ItemLookupInfo, IHasIdentities<SeriesIdentity>
{
private List<SeriesIdentity> _identities = new List<SeriesIdentity>();
public int? AnimeSeriesIndex { get; set; }
public IEnumerable<SeriesIdentity> Identities
{
get { return _identities; }
}
public async Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken)
{
var identifier = new ItemIdentifier<SeriesInfo, SeriesIdentity>();
_identities = (await identifier.FindIdentities(this, providerManager, cancellationToken)).ToList();
}
}
public class SeriesIdentity : IItemIdentity
{
public string Type { get; set; }
public string Id { get; set; }
}
public class PersonLookupInfo : ItemLookupInfo
@ -151,8 +197,10 @@ namespace MediaBrowser.Controller.Providers
public string SeriesName { get; set; }
}
public class SeasonInfo : ItemLookupInfo
public class SeasonInfo : ItemLookupInfo, IHasIdentities<SeasonIdentity>
{
private List<SeasonIdentity> _identities = new List<SeasonIdentity>();
public Dictionary<string, string> SeriesProviderIds { get; set; }
public int? AnimeSeriesIndex { get; set; }
@ -160,5 +208,25 @@ namespace MediaBrowser.Controller.Providers
{
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public IEnumerable<SeasonIdentity> Identities
{
get { return _identities; }
}
public async Task FindIdentities(IProviderManager providerManager, CancellationToken cancellationToken)
{
var identifier = new ItemIdentifier<SeasonInfo, SeasonIdentity>();
_identities = (await identifier.FindIdentities(this, providerManager, cancellationToken)).ToList();
}
}
public class SeasonIdentity : IItemIdentity
{
public string Type { get; set; }
public string SeriesId { get; set; }
public int SeasonIndex { get; set; }
}
}

View File

@ -104,6 +104,9 @@ namespace MediaBrowser.Providers.Manager
refreshResult.AddStatus(ProviderRefreshStatus.Failure, ex.Message);
}
// Identify item
TIdType id = null;
// Next run metadata providers
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
{
@ -120,7 +123,8 @@ namespace MediaBrowser.Providers.Manager
if (providers.Count > 0)
{
var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
id = await CreateInitialLookupInfo(itemOfType, cancellationToken).ConfigureAwait(false);
var result = await RefreshWithProviders(itemOfType, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
updateType = updateType | result.UpdateType;
refreshResult.AddStatus(result.Status, result.ErrorMessage);
@ -129,6 +133,13 @@ namespace MediaBrowser.Providers.Manager
}
}
if (id == null)
{
id = await CreateInitialLookupInfo(itemOfType, cancellationToken).ConfigureAwait(false);
}
MergeIdentities(itemOfType, id);
// Next run remote image providers, but only if local image providers didn't throw an exception
if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
{
@ -162,6 +173,15 @@ namespace MediaBrowser.Providers.Manager
}
}
private void MergeIdentities(TItemType item, TIdType id)
{
var hasIdentity = id as IHasIdentities<IItemIdentity>;
if (hasIdentity != null)
{
item.Identities = hasIdentity.Identities.ToList();
}
}
/// <summary>
/// Befores the save.
/// </summary>
@ -267,7 +287,7 @@ namespace MediaBrowser.Providers.Manager
return item is TItemType;
}
protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, MetadataRefreshOptions options, List<IMetadataProvider> providers, ItemImageProvider imageService, CancellationToken cancellationToken)
protected virtual async Task<RefreshResult> RefreshWithProviders(TItemType item, TIdType id, MetadataRefreshOptions options, List<IMetadataProvider> providers, ItemImageProvider imageService, CancellationToken cancellationToken)
{
var refreshResult = new RefreshResult
{
@ -288,7 +308,7 @@ namespace MediaBrowser.Providers.Manager
// If replacing all metadata, run internet providers first
if (options.ReplaceAllMetadata)
{
await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
await ExecuteRemoteProviders(item, temp, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
}
var hasLocalMetadata = false;
@ -346,7 +366,7 @@ namespace MediaBrowser.Providers.Manager
// Local metadata is king - if any is found don't run remote providers
if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh))
{
await ExecuteRemoteProviders(item, temp, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
await ExecuteRemoteProviders(item, temp, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), refreshResult, cancellationToken).ConfigureAwait(false);
}
if (refreshResult.UpdateType > ItemUpdateType.None)
@ -387,10 +407,8 @@ namespace MediaBrowser.Providers.Manager
return new TItemType();
}
private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken)
private async Task ExecuteRemoteProviders(TItemType item, TItemType temp, TIdType id, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, RefreshResult refreshResult, CancellationToken cancellationToken)
{
TIdType id = await CreateInitialLookupInfo(item, cancellationToken).ConfigureAwait(false);
var unidentifiedCount = 0;
var identifiedCount = 0;

View File

@ -95,7 +95,6 @@
<Compile Include="LiveTv\ProgramMetadataService.cs" />
<Compile Include="LiveTv\VideoRecordingService.cs" />
<Compile Include="Manager\ImageSaver.cs" />
<Compile Include="Manager\ItemIdentifier.cs" />
<Compile Include="Manager\ItemImageProvider.cs" />
<Compile Include="Manager\ProviderManager.cs" />
<Compile Include="Manager\MetadataService.cs" />
@ -204,6 +203,7 @@
<Compile Include="TV\SeriesMetadataService.cs" />
<Compile Include="TV\TvdbEpisodeImageProvider.cs" />
<Compile Include="People\TvdbPersonImageProvider.cs" />
<Compile Include="TV\TvdbSeasonIdentityProvider.cs" />
<Compile Include="TV\TvdbSeasonImageProvider.cs" />
<Compile Include="TV\TvdbSeriesImageProvider.cs" />
<Compile Include="TV\SeasonMetadataService.cs" />

View File

@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Class RemoteEpisodeProvider
/// </summary>
class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasChangeMonitor
class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IItemIdentityProvider<EpisodeInfo, EpisodeIdentity>, IHasChangeMonitor
{
internal static TvdbEpisodeProvider Current;
private readonly IFileSystem _fileSystem;
@ -40,19 +40,18 @@ namespace MediaBrowser.Providers.TV
{
var list = new List<RemoteSearchResult>();
string seriesTvdbId;
searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
var identity = searchInfo.Identities.FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString()) ?? await FindIdentity(searchInfo).ConfigureAwait(false);
if (!string.IsNullOrEmpty(seriesTvdbId))
if (identity != null)
{
await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesTvdbId, searchInfo.MetadataLanguage,
await TvdbSeriesProvider.Current.EnsureSeriesInfo(identity.SeriesId, searchInfo.MetadataLanguage,
cancellationToken).ConfigureAwait(false);
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesTvdbId);
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.SeriesId);
try
{
var item = FetchEpisodeData(searchInfo, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
var item = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
if (item != null)
{
@ -83,20 +82,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
public Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
string seriesTvdbId;
searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
var identity = searchInfo.Identities.FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString()) ?? await FindIdentity(searchInfo).ConfigureAwait(false);
var result = new MetadataResult<Episode>();
if (!string.IsNullOrEmpty(seriesTvdbId))
if (identity != null)
{
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesTvdbId);
var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, identity.SeriesId);
try
{
result.Item = FetchEpisodeData(searchInfo, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
result.Item = FetchEpisodeData(searchInfo, identity, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken);
result.HasMetadata = result.Item != null;
}
catch (FileNotFoundException)
@ -105,7 +103,7 @@ namespace MediaBrowser.Providers.TV
}
}
return Task.FromResult(result);
return result;
}
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
@ -211,18 +209,14 @@ namespace MediaBrowser.Providers.TV
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="seriesDataPath">The series data path.</param>
/// <param name="seriesProviderIds"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
private Episode FetchEpisodeData(EpisodeInfo id, string seriesDataPath, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
private Episode FetchEpisodeData(EpisodeInfo id, EpisodeIdentity identity, string seriesDataPath, Dictionary<string, string> seriesProviderIds, CancellationToken cancellationToken)
{
if (id.IndexNumber == null)
{
return null;
}
var episodeNumber = id.IndexNumber.Value;
var episodeNumber = identity.IndexNumber;
var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0;
var seasonNumber = id.ParentIndexNumber + seasonOffset;
var seasonNumber = identity.SeasonIndex + seasonOffset;
if (seasonNumber == null)
{
@ -263,7 +257,7 @@ namespace MediaBrowser.Providers.TV
usingAbsoluteData = true;
}
var end = id.IndexNumberEnd ?? episodeNumber;
var end = identity.IndexNumberEnd ?? episodeNumber;
episodeNumber++;
while (episodeNumber <= end)
@ -722,5 +716,29 @@ namespace MediaBrowser.Providers.TV
{
throw new NotImplementedException();
}
public Task<EpisodeIdentity> FindIdentity(EpisodeInfo info)
{
string seriesTvdbId;
info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
if (string.IsNullOrEmpty(seriesTvdbId) || info.IndexNumber == null)
{
return Task.FromResult<EpisodeIdentity>(null);
}
var id = new EpisodeIdentity
{
Type = MetadataProviders.Tvdb.ToString(),
SeriesId = seriesTvdbId,
SeasonIndex = info.ParentIndexNumber,
IndexNumber = info.IndexNumber.Value,
IndexNumberEnd = info.IndexNumberEnd
};
return Task.FromResult(id);
}
public int Order { get { return 0; } }
}
}

View File

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Providers.TV
{
public class TvdbSeasonIdentityProvider : IItemIdentityProvider<SeasonInfo, SeasonIdentity>
{
public Task<SeasonIdentity> FindIdentity(SeasonInfo info)
{
string tvdbSeriesId;
if (!info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out tvdbSeriesId) || string.IsNullOrEmpty(tvdbSeriesId) || info.IndexNumber == null)
{
return Task.FromResult<SeasonIdentity>(null);
}
var result = new SeasonIdentity
{
Type = MetadataProviders.Tvdb.ToString(),
SeriesId = tvdbSeriesId,
SeasonIndex = info.IndexNumber.Value
};
return Task.FromResult(result);
}
public int Order { get { return 0; } }
}
}

View File

@ -65,7 +65,8 @@ namespace MediaBrowser.Providers.TV
var season = (Season)item;
var series = season.Series;
var seriesId = series != null ? series.GetProviderId(MetadataProviders.Tvdb) : null;
var identity = season.Identities.OfType<SeasonIdentity>().FirstOrDefault(id => id.Type == MetadataProviders.Tvdb.ToString());
var seriesId = identity != null ? identity.SeriesId : null;
if (!string.IsNullOrEmpty(seriesId) && season.IndexNumber.HasValue)
{
@ -78,7 +79,7 @@ namespace MediaBrowser.Providers.TV
try
{
int seasonNumber = AdjustForSeriesOffset(series, season.IndexNumber.Value);
int seasonNumber = AdjustForSeriesOffset(series, identity.SeasonIndex);
return GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, cancellationToken);
}
catch (FileNotFoundException)

View File

@ -23,7 +23,7 @@ using System.Xml;
namespace MediaBrowser.Providers.TV
{
public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IItemIdentityProvider<SeriesInfo, SeriesIdentity>, IHasOrder
{
private const string TvdbSeriesOffset = "TvdbSeriesOffset";
private const string TvdbSeriesOffsetFormat = "{0}-{1}";
@ -70,9 +70,17 @@ namespace MediaBrowser.Providers.TV
var seriesId = itemId.GetProviderId(MetadataProviders.Tvdb);
if (string.IsNullOrEmpty(seriesId))
{
seriesId = itemId.Identities
.Where(id => id.Type == MetadataProviders.Tvdb.ToString())
.Select(id => id.Id)
.FirstOrDefault();
if (string.IsNullOrEmpty(seriesId))
{
seriesId = await FindSeries(itemId.Name, cancellationToken).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(seriesId))
{
@ -1121,6 +1129,20 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
public async Task<SeriesIdentity> FindIdentity(SeriesInfo info)
{
string tvdbId;
if (!info.ProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out tvdbId))
tvdbId = await FindSeries(info.Name, CancellationToken.None);
if (!string.IsNullOrEmpty(tvdbId))
{
return new SeriesIdentity {Type = MetadataProviders.Tvdb.ToString(), Id = tvdbId};
}
return null;
}
public int Order
{
get

View File

@ -1,6 +1,8 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
@ -283,7 +285,4 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal