Add XMLTV guide content ETags
This commit is contained in:
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using AutoFixture.AutoMoq;
|
||||
using Jellyfin.LiveTv.Listings;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
@@ -66,6 +67,7 @@ public class XmlTvListingsProviderTests
|
||||
Assert.True(program.HasImage);
|
||||
Assert.Equal("https://domain.tld/image.png", program.ImageUrl);
|
||||
Assert.Equal("3297", program.ChannelId);
|
||||
AssertXmlTvEtag(program.Etag);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -85,5 +87,60 @@ public class XmlTvListingsProviderTests
|
||||
var program = programsList[0];
|
||||
Assert.DoesNotContain(program.Genres, g => string.IsNullOrEmpty(g));
|
||||
Assert.Equal("3297", program.ChannelId);
|
||||
AssertXmlTvEtag(program.Etag);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProgramsAsync_Etag_SameContentIsStable()
|
||||
{
|
||||
var first = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml");
|
||||
var second = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml");
|
||||
|
||||
Assert.Equal(first.Etag, second.Etag);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-title-change.xml")]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-description-change.xml")]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-icon-change.xml")]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-category-change.xml")]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-progid-change.xml")]
|
||||
public async Task GetProgramsAsync_Etag_ChangesWhenMappedContentChanges(string changedPath)
|
||||
{
|
||||
var original = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml");
|
||||
var changed = await GetSingleProgramAsync(changedPath);
|
||||
|
||||
Assert.NotEqual(original.Etag, changed.Etag);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-reordered.xml")]
|
||||
[InlineData("Test Data/LiveTv/Listings/XmlTv/etag-unknown-field.xml")]
|
||||
public async Task GetProgramsAsync_Etag_DoesNotChangeWhenMappedContentIsEquivalent(string equivalentPath)
|
||||
{
|
||||
var original = await GetSingleProgramAsync("Test Data/LiveTv/Listings/XmlTv/etag-base.xml");
|
||||
var equivalent = await GetSingleProgramAsync(equivalentPath);
|
||||
|
||||
Assert.Equal(original.Etag, equivalent.Etag);
|
||||
}
|
||||
|
||||
private async Task<ProgramInfo> GetSingleProgramAsync(string path)
|
||||
{
|
||||
var info = new ListingsProviderInfo()
|
||||
{
|
||||
Id = Path.GetFileNameWithoutExtension(path),
|
||||
Path = path
|
||||
};
|
||||
|
||||
var startDate = new DateTime(2022, 11, 4, 0, 0, 0, DateTimeKind.Utc);
|
||||
var programs = await _xmlTvListingsProvider.GetProgramsAsync(info, "3297", startDate, startDate.AddDays(1), CancellationToken.None);
|
||||
|
||||
return Assert.Single(programs.ToList());
|
||||
}
|
||||
|
||||
private static void AssertXmlTvEtag(string? etag)
|
||||
{
|
||||
Assert.NotNull(etag);
|
||||
Assert.StartsWith("xmltv-sha256-v1:", etag!, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Jellyfin.LiveTv.Listings;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.LiveTv.Tests.Listings;
|
||||
|
||||
public class XmlTvProgramEtagTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryCreate_GenreOrderIsSignificant()
|
||||
{
|
||||
// GuideManager assigns item.Genres = info.Genres.ToArray() preserving order,
|
||||
// so the same genres in a different order is a real mapped-content change.
|
||||
var first = NewProgram();
|
||||
first.Genres = new() { "Drama", "Action" };
|
||||
|
||||
var second = NewProgram();
|
||||
second.Genres = new() { "Action", "Drama" };
|
||||
|
||||
Assert.True(XmlTvProgramEtag.TryCreate(first, out var firstEtag, out _));
|
||||
Assert.True(XmlTvProgramEtag.TryCreate(second, out var secondEtag, out _));
|
||||
Assert.NotEqual(firstEtag, secondEtag);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesStored_EqualXmlTvEtags_ReturnsTrue()
|
||||
{
|
||||
const string Etag = XmlTvProgramEtag.Prefix + "ABCDEF0123456789";
|
||||
Assert.True(XmlTvProgramEtag.MatchesStored(Etag, Etag));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesStored_DifferentXmlTvEtags_ReturnsFalse()
|
||||
{
|
||||
Assert.False(XmlTvProgramEtag.MatchesStored(
|
||||
XmlTvProgramEtag.Prefix + "AAAA",
|
||||
XmlTvProgramEtag.Prefix + "BBBB"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchesStored_EqualNonXmlTvEtags_ReturnsFalse()
|
||||
{
|
||||
// Other providers (e.g. Schedules Direct) use their own etag schemes.
|
||||
// The IsXmlTvEtag gate must keep them on the field-by-field update path
|
||||
// even when their incoming and stored values happen to match exactly.
|
||||
const string Etag = "sd-abc123";
|
||||
Assert.False(XmlTvProgramEtag.MatchesStored(Etag, Etag));
|
||||
}
|
||||
|
||||
private static ProgramInfo NewProgram() => new()
|
||||
{
|
||||
Id = "program-id",
|
||||
ChannelId = "channel-id",
|
||||
Name = "Program Name",
|
||||
StartDate = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc),
|
||||
EndDate = new DateTime(2026, 1, 1, 13, 0, 0, DateTimeKind.Utc),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Base Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<category lang="en">series</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Base Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<category lang="en">sports</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Base Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Changed description.</desc>
|
||||
<category lang="en">series</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Base Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<category lang="en">series</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<icon src="https://domain.tld/changed.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Base Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<category lang="en">series</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789013</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" stop="20221104140000 +0000" start="20221104130000 +0000">
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<category lang="en">series</category>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<title lang="en">Base Program</title>
|
||||
</programme>
|
||||
</tv>
|
||||
@@ -0,0 +1,17 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Changed Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<category lang="en">series</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
@@ -0,0 +1,18 @@
|
||||
<tv date="20221104">
|
||||
<programme channel="3297" start="20221104130000 +0000" stop="20221104140000 +0000">
|
||||
<title lang="en">Base Program</title>
|
||||
<sub-title lang="en">Base Episode</sub-title>
|
||||
<desc lang="en">Base description.</desc>
|
||||
<category lang="en">series</category>
|
||||
<episode-num system="xmltv_ns">0 . 1 . </episode-num>
|
||||
<episode-num system="dd_progid">EP123456789012</episode-num>
|
||||
<rating system="VCHIP">
|
||||
<value>TV-G</value>
|
||||
</rating>
|
||||
<star-rating>
|
||||
<value>3/5</value>
|
||||
</star-rating>
|
||||
<previously-unknown-field>Ignored by Jellyfin XMLTV mapping.</previously-unknown-field>
|
||||
<icon src="https://domain.tld/base.png"/>
|
||||
</programme>
|
||||
</tv>
|
||||
Reference in New Issue
Block a user