diff --git a/Kyoo.Common/Models/Attributes/InjectedAttribute.cs b/Kyoo.Common/Models/Attributes/InjectedAttribute.cs
index 1e9a8ece..b036acdf 100644
--- a/Kyoo.Common/Models/Attributes/InjectedAttribute.cs
+++ b/Kyoo.Common/Models/Attributes/InjectedAttribute.cs
@@ -8,7 +8,8 @@ namespace Kyoo.Models.Attributes
/// An attribute to inform that the service will be injected automatically by a service provider.
///
///
- /// It should only be used on and will be injected before calling
+ /// It should only be used on and will be injected before calling .
+ /// It can also be used on and it will be injected before calling .
///
[AttributeUsage(AttributeTargets.Property)]
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
diff --git a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs
index 3eafb90c..a7958c91 100644
--- a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs
+++ b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs
@@ -2,17 +2,41 @@ using System;
namespace Kyoo.Models.Attributes
{
+ ///
+ /// Remove an property from the serialization pipeline. It will simply be skipped.
+ ///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SerializeIgnoreAttribute : Attribute {}
+ ///
+ /// Remove a property from the deserialization pipeline. The user can't input value for this property.
+ ///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DeserializeIgnoreAttribute : Attribute {}
+ ///
+ /// Change the way the field is serialized. It allow one to use a string format like formatting instead of the default value.
+ /// This can be disabled for a request by setting the "internal" query string parameter to true.
+ ///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SerializeAsAttribute : Attribute
{
+ ///
+ /// The format string to use.
+ ///
public string Format { get; }
+ ///
+ /// Create a new with the selected format.
+ ///
+ ///
+ /// The format string can contains any property within {}. It will be replaced by the actual value of the property.
+ /// You can also use the special value {HOST} that will put the webhost address.
+ ///
+ ///
+ /// The show's poster serialized uses this format string: {HOST}/api/shows/{Slug}/poster
+ ///
+ /// The format to use
public SerializeAsAttribute(string format)
{
Format = format;
diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs
index 78f604f2..7d48dd75 100644
--- a/Kyoo.Common/Models/LibraryItem.cs
+++ b/Kyoo.Common/Models/LibraryItem.cs
@@ -5,6 +5,9 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
+ ///
+ /// The type of item, ether a show, a movie or a collection.
+ ///
public enum ItemType
{
Show,
@@ -12,16 +15,50 @@ namespace Kyoo.Models
Collection
}
+ ///
+ /// A type union between and .
+ /// This is used to list content put inside a library.
+ ///
public class LibraryItem : IResource
{
+ ///
public int ID { get; set; }
+
+ ///
public string Slug { get; set; }
+
+ ///
+ /// The title of the show or collection.
+ ///
public string Title { get; set; }
+
+ ///
+ /// The summary of the show or collection.
+ ///
public string Overview { get; set; }
+
+ ///
+ /// Is this show airing, not aired yet or finished? This is only applicable for shows.
+ ///
public Status? Status { get; set; }
- public string TrailerUrl { get; set; }
- public int? StartYear { get; set; }
- public int? EndYear { get; set; }
+
+ ///
+ /// The date this show or collection started airing. It can be null if this is unknown.
+ ///
+ public DateTime? StartAir { get; set; }
+
+ ///
+ /// The date this show or collection finished airing.
+ /// It must be after the but can be the same (example: for movies).
+ /// It can also be null if this is unknown.
+ ///
+ public DateTime? EndAir { get; set; }
+
+ ///
+ /// The path of this item's poster.
+ /// By default, the http path for this poster is returned from the public API.
+ /// This can be disabled using the internal query flag.
+ ///
[SerializeAs("{HOST}/api/{_type}/{Slug}/poster")] public string Poster { get; set; }
[UsedImplicitly] private string _type => Type == ItemType.Collection ? "collection" : "show";
public ItemType Type { get; set; }
@@ -35,9 +72,8 @@ namespace Kyoo.Models
Title = show.Title;
Overview = show.Overview;
Status = show.Status;
- TrailerUrl = show.TrailerUrl;
- StartYear = show.StartYear;
- EndYear = show.EndYear;
+ StartAir = show.StartAir;
+ EndAir = show.EndAir;
Poster = show.Poster;
Type = show.IsMovie ? ItemType.Movie : ItemType.Show;
}
@@ -49,9 +85,8 @@ namespace Kyoo.Models
Title = collection.Name;
Overview = collection.Overview;
Status = Models.Status.Unknown;
- TrailerUrl = null;
- StartYear = null;
- EndYear = null;
+ StartAir = null;
+ EndAir = null;
Poster = collection.Poster;
Type = ItemType.Collection;
}
@@ -63,9 +98,8 @@ namespace Kyoo.Models
Title = x.Title,
Overview = x.Overview,
Status = x.Status,
- TrailerUrl = x.TrailerUrl,
- StartYear = x.StartYear,
- EndYear = x.EndYear,
+ StartAir = x.StartAir,
+ EndAir = x.EndAir,
Poster= x.Poster,
Type = x.IsMovie ? ItemType.Movie : ItemType.Show
};
@@ -77,10 +111,9 @@ namespace Kyoo.Models
Title = x.Name,
Overview = x.Overview,
Status = Models.Status.Unknown,
- TrailerUrl = null,
- StartYear = null,
- EndYear = null,
- Poster= x.Poster,
+ StartAir = null,
+ EndAir = null,
+ Poster = x.Poster,
Type = ItemType.Collection
};
}
diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs
index 2df85f1f..41758b52 100644
--- a/Kyoo.Common/Models/Link.cs
+++ b/Kyoo.Common/Models/Link.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
namespace Kyoo.Models
@@ -55,13 +54,12 @@ namespace Kyoo.Models
where T1 : class, IResource
where T2 : class, IResource
{
- public virtual T1 First { get; set; }
- public virtual T2 Second { get; set; }
+ public T1 First { get; set; }
+ public T2 Second { get; set; }
public Link() {}
- [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
public Link(T1 first, T2 second, bool privateItems = false)
: base(first, second)
{
diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs
index d1752d50..400d65da 100644
--- a/Kyoo.Common/Models/MetadataID.cs
+++ b/Kyoo.Common/Models/MetadataID.cs
@@ -6,19 +6,19 @@ namespace Kyoo.Models
{
[SerializeIgnore] public int ID { get; set; }
[SerializeIgnore] public int ProviderID { get; set; }
- public virtual Provider Provider {get; set; }
+ public Provider Provider {get; set; }
[SerializeIgnore] public int? ShowID { get; set; }
- [SerializeIgnore] public virtual Show Show { get; set; }
+ [SerializeIgnore] public Show Show { get; set; }
[SerializeIgnore] public int? EpisodeID { get; set; }
- [SerializeIgnore] public virtual Episode Episode { get; set; }
+ [SerializeIgnore] public Episode Episode { get; set; }
[SerializeIgnore] public int? SeasonID { get; set; }
- [SerializeIgnore] public virtual Season Season { get; set; }
+ [SerializeIgnore] public Season Season { get; set; }
[SerializeIgnore] public int? PeopleID { get; set; }
- [SerializeIgnore] public virtual People People { get; set; }
+ [SerializeIgnore] public People People { get; set; }
public string DataID { get; set; }
public string Link { get; set; }
diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs
index e7d14d79..7656401f 100644
--- a/Kyoo.Common/Models/Resources/Show.cs
+++ b/Kyoo.Common/Models/Resources/Show.cs
@@ -1,53 +1,168 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
+using Kyoo.Common.Models.Attributes;
+using Kyoo.Controllers;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
+ ///
+ /// A series or a movie.
+ ///
public class Show : IResource, IOnMerge
{
+ ///
public int ID { get; set; }
+
+ ///
public string Slug { get; set; }
+
+ ///
+ /// The title of this show.
+ ///
public string Title { get; set; }
+
+ ///
+ /// The list of alternative titles of this show.
+ ///
[EditableRelation] public string[] Aliases { get; set; }
+
+ ///
+ /// The path of the root directory of this show.
+ /// This can be any kind of path supported by
+ ///
[SerializeIgnore] public string Path { get; set; }
+
+ ///
+ /// The summary of this show.
+ ///
public string Overview { get; set; }
+
+ ///
+ /// Is this show airing, not aired yet or finished?
+ ///
public Status? Status { get; set; }
+
+ ///
+ /// An URL to a trailer. This could be any path supported by the .
+ ///
+ /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file.
public string TrailerUrl { get; set; }
+
+ ///
+ /// The date this show started airing. It can be null if this is unknown.
+ ///
+ public DateTime? StartAir { get; set; }
+
+ ///
+ /// The date this show finished airing.
+ /// It must be after the but can be the same (example: for movies).
+ /// It can also be null if this is unknown.
+ ///
+ public DateTime? EndAir { get; set; }
- public int? StartYear { get; set; }
- public int? EndYear { get; set; }
-
+ ///
+ /// The path of this show's poster.
+ /// By default, the http path for this poster is returned from the public API.
+ /// This can be disabled using the internal query flag.
+ ///
[SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; }
+
+ ///
+ /// The path of this show's logo.
+ /// By default, the http path for this logo is returned from the public API.
+ /// This can be disabled using the internal query flag.
+ ///
[SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; }
+
+ ///
+ /// The path of this show's backdrop.
+ /// By default, the http path for this backdrop is returned from the public API.
+ /// This can be disabled using the internal query flag.
+ ///
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; }
+ ///
+ /// True if this show represent a movie, false otherwise.
+ ///
public bool IsMovie { get; set; }
- [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; }
-
+ ///
+ /// The link to metadata providers that this show has. See for more information.
+ ///
+ [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; }
+ ///
+ /// The ID of the Studio that made this show. This value is only set when the has been loaded.
+ ///
[SerializeIgnore] public int? StudioID { get; set; }
- [LoadableRelation(nameof(StudioID))] [EditableRelation] public virtual Studio Studio { get; set; }
- [LoadableRelation] [EditableRelation] public virtual ICollection Genres { get; set; }
- [LoadableRelation] [EditableRelation] public virtual ICollection People { get; set; }
- [LoadableRelation] public virtual ICollection Seasons { get; set; }
- [LoadableRelation] public virtual ICollection Episodes { get; set; }
- [LoadableRelation] public virtual ICollection Libraries { get; set; }
- [LoadableRelation] public virtual ICollection Collections { get; set; }
+ ///
+ /// The Studio that made this show. This must be explicitly loaded via a call to .
+ ///
+ [LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; }
+
+ ///
+ /// The list of genres (themes) this show has.
+ ///
+ [LoadableRelation] [EditableRelation] public ICollection Genres { get; set; }
+
+ ///
+ /// The list of people that made this show.
+ ///
+ [LoadableRelation] [EditableRelation] public ICollection People { get; set; }
+
+ ///
+ /// The different seasons in this show. If this is a movie, this list is always null or empty.
+ ///
+ [LoadableRelation] public ICollection Seasons { get; set; }
+
+ ///
+ /// The list of episodes in this show. If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to -1).
+ /// Having an episode is necessary to store metadata and tracks.
+ ///
+ [LoadableRelation] public ICollection Episodes { get; set; }
+
+ ///
+ /// The list of libraries that contains this show.
+ ///
+ [LoadableRelation] public ICollection Libraries { get; set; }
+
+ ///
+ /// The list of collections that contains this show.
+ ///
+ [LoadableRelation] public ICollection Collections { get; set; }
#if ENABLE_INTERNAL_LINKS
- [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; }
- [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; }
- [SerializeIgnore] public virtual ICollection> GenreLinks { get; set; }
+ ///
+ /// The internal link between this show and libraries in the list.
+ ///
+ [Link] public ICollection> LibraryLinks { get; set; }
+
+ ///
+ /// The internal link between this show and collections in the list.
+ ///
+ [Link] public ICollection> CollectionLinks { get; set; }
+
+ ///
+ /// The internal link between this show and genres in the list.
+ ///
+ [Link] public ICollection> GenreLinks { get; set; }
#endif
+ ///
+ /// Retrieve the internal provider's ID of a show using it's provider slug.
+ ///
+ /// This method will never return anything if the are not loaded.
+ /// The slug of the provider
+ /// The field of the asked provider.
public string GetID(string provider)
{
- return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID;
+ return ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
}
- public virtual void OnMerge(object merged)
+ ///
+ public void OnMerge(object merged)
{
if (ExternalIDs != null)
foreach (MetadataID id in ExternalIDs)
@@ -64,5 +179,8 @@ namespace Kyoo.Models
}
}
+ ///
+ /// The enum containing show's status.
+ ///
public enum Status { Finished, Airing, Planned, Unknown }
}
diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs
index 9eea3a7b..ebc3c4c1 100644
--- a/Kyoo.Common/Models/Resources/Studio.cs
+++ b/Kyoo.Common/Models/Resources/Studio.cs
@@ -3,31 +3,40 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
+ ///
+ /// A studio that make shows.
+ ///
public class Studio : IResource
{
+ ///
public int ID { get; set; }
+
+ ///
public string Slug { get; set; }
+
+ ///
+ /// The name of this studio.
+ ///
public string Name { get; set; }
- [LoadableRelation] public virtual ICollection Shows { get; set; }
+ ///
+ /// The list of shows that are made by this studio.
+ ///
+ [LoadableRelation] public ICollection Shows { get; set; }
+ ///
+ /// Create a new, empty, .
+ ///
public Studio() { }
+ ///
+ /// Create a new with a specific name, the slug is calculated automatically.
+ ///
+ /// The name of the studio.
public Studio(string name)
{
Slug = Utility.ToSlug(name);
Name = name;
}
-
- public Studio(string slug, string name)
- {
- Slug = slug;
- Name = name;
- }
-
- public static Studio Default()
- {
- return new Studio("unknown", "Unknown Studio");
- }
}
}
diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs
index 92438e0a..290ecd3f 100644
--- a/Kyoo.Common/Models/Resources/Track.cs
+++ b/Kyoo.Common/Models/Resources/Track.cs
@@ -1,11 +1,13 @@
-using Kyoo.Models.Watch;
-using System.Globalization;
+using System.Globalization;
using System.Linq;
-using System.Runtime.InteropServices;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
+ ///
+ /// The list of available stream types.
+ /// Attachments are only used temporarily by the transcoder but are not stored in a database.
+ ///
public enum StreamType
{
Unknown = 0,
@@ -15,82 +17,15 @@ namespace Kyoo.Models
Attachment = 4
}
- namespace Watch
- {
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
- public class Stream
- {
- public string Title { get; set; }
- public string Language { get; set; }
- public string Codec { get; set; }
- [MarshalAs(UnmanagedType.I1)] public bool isDefault;
- [MarshalAs(UnmanagedType.I1)] public bool isForced;
- [SerializeIgnore] public string Path { get; set; }
- [SerializeIgnore] public StreamType Type { get; set; }
-
- public Stream() {}
-
- public Stream(string title, string language, string codec, bool isDefault, bool isForced, string path, StreamType type)
- {
- Title = title;
- Language = language;
- Codec = codec;
- this.isDefault = isDefault;
- this.isForced = isForced;
- Path = path;
- Type = type;
- }
-
- public Stream(Stream stream)
- {
- Title = stream.Title;
- Language = stream.Language;
- isDefault = stream.isDefault;
- isForced = stream.isForced;
- Codec = stream.Codec;
- Path = stream.Path;
- Type = stream.Type;
- }
- }
- }
-
- public class Track : Stream, IResource
+ ///
+ /// A video, audio or subtitle track for an episode.
+ ///
+ public class Track : IResource
{
+ ///
public int ID { get; set; }
- [SerializeIgnore] public int EpisodeID { get; set; }
- public int TrackIndex { get; set; }
- public bool IsDefault
- {
- get => isDefault;
- set => isDefault = value;
- }
- public bool IsForced
- {
- get => isForced;
- set => isForced = value;
- }
-
- public string DisplayName
- {
- get
- {
- string language = GetLanguage(Language);
-
- if (language == null)
- return $"Unknown (index: {TrackIndex})";
- CultureInfo info = CultureInfo.GetCultures(CultureTypes.NeutralCultures)
- .FirstOrDefault(x => x.ThreeLetterISOLanguageName == language);
- string name = info?.EnglishName ?? language;
- if (IsForced)
- name += " Forced";
- if (IsExternal)
- name += " (External)";
- if (Title != null && Title.Length > 1)
- name += " - " + Title;
- return name;
- }
- }
-
+
+ ///
public string Slug
{
get
@@ -112,34 +47,90 @@ namespace Kyoo.Models
return $"{Episode.Slug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}";
}
}
-
- public bool IsExternal { get; set; }
- [LoadableRelation(nameof(EpisodeID))] public virtual Episode Episode { get; set; }
- public Track() { }
+ ///
+ /// The title of the stream.
+ ///
+ public string Title { get; set; }
+
+ ///
+ /// The language of this stream (as a ISO-639-2 language code)
+ ///
+ public string Language { get; set; }
+
+ ///
+ /// The codec of this stream.
+ ///
+ public string Codec { get; set; }
+
+
+ ///
+ /// Is this stream the default one of it's type?
+ ///
+ public bool IsDefault { get; set; }
+
+ ///
+ /// Is this stream tagged as forced?
+ ///
+ public bool IsForced { get; set; }
+
+ ///
+ /// Is this track extern to the episode's file?
+ ///
+ public bool IsExternal { get; set; }
+
+ ///
+ /// The path of this track.
+ ///
+ [SerializeIgnore] public string Path { get; set; }
+
+ ///
+ /// The type of this stream.
+ ///
+ [SerializeIgnore] public StreamType Type { get; set; }
+
+ ///
+ /// The ID of the episode that uses this track. This value is only set when the has been loaded.
+ ///
+ [SerializeIgnore] public int EpisodeID { get; set; }
+ ///
+ /// The episode that uses this track.
+ ///
+ [LoadableRelation(nameof(EpisodeID))] public Episode Episode { get; set; }
- public Track(StreamType type,
- string title,
- string language,
- bool isDefault,
- bool isForced,
- string codec,
- bool isExternal,
- string path)
- : base(title, language, codec, isDefault, isForced, path, type)
- {
- IsExternal = isExternal;
- }
+ ///
+ /// The index of this track on the episode.
+ ///
+ public int TrackIndex { get; set; }
- public Track(Stream stream)
- : base(stream)
+ ///
+ /// A user-friendly name for this track. It does not include the track type.
+ ///
+ public string DisplayName
{
- IsExternal = false;
+ get
+ {
+ string language = GetLanguage(Language);
+
+ if (language == null)
+ return $"Unknown (index: {TrackIndex})";
+ CultureInfo info = CultureInfo.GetCultures(CultureTypes.NeutralCultures)
+ .FirstOrDefault(x => x.ThreeLetterISOLanguageName == language);
+ string name = info?.EnglishName ?? language;
+ if (IsForced)
+ name += " Forced";
+ if (IsExternal)
+ name += " (External)";
+ if (Title is {Length: > 1})
+ name += " - " + Title;
+ return name;
+ }
}
//Converting mkv track language to c# system language tag.
private static string GetLanguage(string mkvLanguage)
{
+ // TODO delete this and have a real way to get the language string from the ISO-639-2.
return mkvLanguage switch
{
"fre" => "fra",
diff --git a/Kyoo.Common/Utility/EnumerableExtensions.cs b/Kyoo.Common/Utility/EnumerableExtensions.cs
index 459d5f1c..93f1b52e 100644
--- a/Kyoo.Common/Utility/EnumerableExtensions.cs
+++ b/Kyoo.Common/Utility/EnumerableExtensions.cs
@@ -20,7 +20,8 @@ namespace Kyoo
/// The type of items in the returned list
/// The list mapped.
/// The list or the mapper can't be null
- public static IEnumerable Map([NotNull] this IEnumerable self,
+ [LinqTunnel]
+ public static IEnumerable Map([NotNull] this IEnumerable self,
[NotNull] Func mapper)
{
if (self == null)
@@ -52,6 +53,7 @@ namespace Kyoo
/// The type of items in the returned list
/// The list mapped as an AsyncEnumerable
/// The list or the mapper can't be null
+ [LinqTunnel]
public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self,
[NotNull] Func> mapper)
{
@@ -84,6 +86,7 @@ namespace Kyoo
/// The type of items in the returned list
/// The list mapped as an AsyncEnumerable
/// The list or the mapper can't be null
+ [LinqTunnel]
public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self,
[NotNull] Func> mapper)
{
@@ -110,6 +113,7 @@ namespace Kyoo
/// The type of items in the async list and in the returned list.
/// A task that will return a simple list
/// The list can't be null
+ [LinqTunnel]
public static Task> ToListAsync([NotNull] this IAsyncEnumerable self)
{
if (self == null)
@@ -134,6 +138,7 @@ namespace Kyoo
/// The type of items inside the list
/// The iterable and the action can't be null.
/// The iterator proxied, there is no dual iterations.
+ [LinqTunnel]
public static IEnumerable IfEmpty([NotNull] this IEnumerable self, [NotNull] Action action)
{
if (self == null)
@@ -236,6 +241,7 @@ namespace Kyoo
/// The number of items in each chunk
/// The type of data in the initial list.
/// A list of chunks
+ [LinqTunnel]
public static IEnumerable> BatchBy(this List list, int countPerList)
{
for (int i = 0; i < list.Count; i += countPerList)
@@ -249,6 +255,7 @@ namespace Kyoo
/// The number of items in each chunk
/// The type of data in the initial list.
/// A list of chunks
+ [LinqTunnel]
public static IEnumerable BatchBy(this IEnumerable list, int countPerList)
{
T[] ret = new T[countPerList];
diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs
index 2c97ddce..7ac753dd 100644
--- a/Kyoo.Tests/KAssert.cs
+++ b/Kyoo.Tests/KAssert.cs
@@ -1,4 +1,5 @@
using System.Reflection;
+using JetBrains.Annotations;
using Xunit;
using Xunit.Sdk;
@@ -15,6 +16,7 @@ namespace Kyoo.Tests
/// The value to check against
/// The value to check
/// The type to check
+ [AssertionMethod]
public static void DeepEqual(T expected, T value)
{
foreach (PropertyInfo property in typeof(T).GetProperties())
@@ -24,6 +26,7 @@ namespace Kyoo.Tests
///
/// Explicitly fail a test.
///
+ [AssertionMethod]
public static void Fail()
{
throw new XunitException();
@@ -33,6 +36,7 @@ namespace Kyoo.Tests
/// Explicitly fail a test.
///
/// The message that will be seen in the test report
+ [AssertionMethod]
public static void Fail(string message)
{
throw new XunitException(message);
diff --git a/Kyoo.Tests/Library/TestSample.cs b/Kyoo.Tests/Library/TestSample.cs
index 5642054b..16eed5d8 100644
--- a/Kyoo.Tests/Library/TestSample.cs
+++ b/Kyoo.Tests/Library/TestSample.cs
@@ -26,8 +26,8 @@ namespace Kyoo.Tests
"school students, they had long ceased to think of each other as friends.",
Status = Status.Finished,
TrailerUrl = null,
- StartYear = 2011,
- EndYear = 2011,
+ StartAir = new DateTime(2011),
+ EndAir = new DateTime(2011),
Poster = "poster",
Logo = "logo",
Backdrop = "backdrop",
diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs
index 823aa9d2..8f6e006e 100644
--- a/Kyoo/Controllers/Transcoder.cs
+++ b/Kyoo/Controllers/Transcoder.cs
@@ -57,7 +57,7 @@ namespace Kyoo.Controllers
Stream stream = Marshal.PtrToStructure(streamsPtr);
if (stream!.Type != StreamType.Unknown)
{
- tracks[j] = new Track(stream);
+ tracks[j] = stream.ToTrack();
j++;
}
streamsPtr += size;
diff --git a/Kyoo/Models/Stream.cs b/Kyoo/Models/Stream.cs
new file mode 100644
index 00000000..3639a4a6
--- /dev/null
+++ b/Kyoo/Models/Stream.cs
@@ -0,0 +1,67 @@
+using System.Runtime.InteropServices;
+using Kyoo.Models.Attributes;
+
+namespace Kyoo.Models.Watch
+{
+ ///
+ /// The unmanaged stream that the transcoder will return.
+ ///
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
+ public class Stream
+ {
+ ///
+ /// The title of the stream.
+ ///
+ public string Title { get; set; }
+
+ ///
+ /// The language of this stream (as a ISO-639-2 language code)
+ ///
+ public string Language { get; set; }
+
+ ///
+ /// The codec of this stream.
+ ///
+ public string Codec { get; set; }
+
+ ///
+ /// Is this stream the default one of it's type?
+ ///
+ [MarshalAs(UnmanagedType.I1)] public bool IsDefault;
+
+ ///
+ /// Is this stream tagged as forced?
+ ///
+ [MarshalAs(UnmanagedType.I1)] public bool IsForced;
+
+ ///
+ /// The path of this track.
+ ///
+ [SerializeIgnore] public string Path { get; set; }
+
+ ///
+ /// The type of this stream.
+ ///
+ [SerializeIgnore] public StreamType Type { get; set; }
+
+
+ ///
+ /// Create a track from this stream.
+ ///
+ /// A new track that represent this stream.
+ public Track ToTrack()
+ {
+ return new()
+ {
+ Title = Title,
+ Language = Language,
+ Codec = Codec,
+ IsDefault = IsDefault,
+ IsForced = IsForced,
+ Path = Path,
+ Type = Type,
+ IsExternal = false
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs
index e6a6cebf..ebe3eaed 100644
--- a/Kyoo/Tasks/Crawler.cs
+++ b/Kyoo/Tasks/Crawler.cs
@@ -292,13 +292,19 @@ namespace Kyoo.Tasks
catch (DuplicatedItemException)
{
old = await libraryManager.GetOrDefault(show.Slug);
- if (old.Path == showPath)
+ if (old != null && old.Path == showPath)
{
await libraryManager.Load(old, x => x.ExternalIDs);
return old;
}
- show.Slug += $"-{show.StartYear}";
- await libraryManager.Create(show);
+
+ if (show.StartAir != null)
+ {
+ show.Slug += $"-{show.StartAir.Value.Year}";
+ await libraryManager.Create(show);
+ }
+ else
+ throw;
}
await ThumbnailsManager.Validate(show);
return show;