Adding a TrackIndex for tracks, support duplicated values & slugs for tracks that are not subtitles

This commit is contained in:
Zoe Roux 2021-03-18 00:16:14 +01:00
parent aac1975a75
commit 2138f0909b
9 changed files with 59 additions and 23 deletions

View File

@ -103,19 +103,13 @@ namespace Kyoo.Models
StreamType.Font => "font.",
_ => ""
};
string slug = $"{Episode.Slug}.{type}{Language}{(TrackIndex != 0 ? TrackIndex : "")}";
if (IsForced)
slug += "-forced";
switch (Codec)
string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty;
string codec = Codec switch
{
case "ass":
slug += ".ass";
break;
case "subrip":
slug += ".srt";
break;
}
return slug;
"subrip" => ".srt",
{} x => $".{x}"
};
return $"{Episode.Slug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}";
}
}

View File

@ -188,7 +188,7 @@ namespace Kyoo.Controllers
&& x.IsForced == y.IsForced
&& x.Codec == y.Codec
&& x.Type == y.Type);
return _tracks.CreateIfNotExists(x, true);
return _tracks.Create(x);
}).ToListAsync();
return resource;
}

View File

@ -4,6 +4,7 @@ using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers
@ -12,7 +13,7 @@ namespace Kyoo.Controllers
{
private bool _disposed;
private readonly DatabaseContext _database;
protected override Expression<Func<Track, object>> DefaultSort => x => x.ID;
protected override Expression<Func<Track, object>> DefaultSort => x => x.TrackIndex;
public TrackRepository(DatabaseContext database) : base(database)
@ -44,7 +45,7 @@ namespace Kyoo.Controllers
public Task<Track> Get(string slug, StreamType type)
{
Match match = Regex.Match(slug,
@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)(\.(?<type>\w*))?\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
if (!match.Success)
{
@ -61,6 +62,8 @@ namespace Kyoo.Controllers
int episodeNumber = match.Groups["episode"].Success ? int.Parse(match.Groups["episode"].Value) : -1;
string language = match.Groups["language"].Value;
bool forced = match.Groups["forced"].Success;
if (match.Groups["type"].Success)
type = Enum.Parse<StreamType>(match.Groups["type"].Value, true);
if (type == StreamType.Unknown)
{
@ -94,7 +97,13 @@ namespace Kyoo.Controllers
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync($"Trying to insert a duplicated track (slug {obj.Slug} already exists).");
await _database.SaveOrRetry(obj, (x, i) =>
{
if (i > 10)
throw new DuplicatedItemException($"More than 10 same tracks exists {x.Slug}. Aborting...");
x.TrackIndex++;
return x;
});
return obj;
}

View File

@ -185,6 +185,9 @@ namespace Kyoo
modelBuilder.Entity<Episode>()
.HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber})
.IsUnique();
modelBuilder.Entity<Track>()
.HasIndex(x => new {x.EpisodeID, x.Type, x.Language, x.TrackIndex, x.IsForced})
.IsUnique();
}
public T GetTemporaryObject<T>(T model)
@ -301,6 +304,33 @@ namespace Kyoo
}
}
public Task<T> SaveOrRetry<T>(T obj, Func<T, int, T> onFail, CancellationToken cancellationToken = new())
{
return SaveOrRetry(obj, onFail, 0, cancellationToken);
}
public async Task<T> SaveOrRetry<T>(T obj,
Func<T, int, T> onFail,
int recurse,
CancellationToken cancellationToken = new())
{
try
{
await base.SaveChangesAsync(true, cancellationToken);
return obj;
}
catch (DbUpdateException ex) when (IsDuplicateException(ex))
{
recurse++;
return await SaveOrRetry(onFail(obj, recurse), onFail, recurse, cancellationToken);
}
catch (DbUpdateException)
{
DiscardChanges();
throw;
}
}
private static bool IsDuplicateException(Exception ex)
{
return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation};

View File

@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Kyoo.Models.DatabaseMigrations.Internal
{
[DbContext(typeof(DatabaseContext))]
[Migration("20210317180448_Initial")]
[Migration("20210317220956_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -499,7 +499,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
b.HasKey("ID");
b.HasIndex("EpisodeID");
b.HasIndex("EpisodeID", "Type", "Language", "TrackIndex", "IsForced")
.IsUnique();
b.ToTable("Tracks");
});

View File

@ -543,9 +543,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Tracks_EpisodeID",
name: "IX_Tracks_EpisodeID_Type_Language_TrackIndex_IsForced",
table: "Tracks",
column: "EpisodeID");
columns: new[] { "EpisodeID", "Type", "Language", "TrackIndex", "IsForced" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)

View File

@ -497,7 +497,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
b.HasKey("ID");
b.HasIndex("EpisodeID");
b.HasIndex("EpisodeID", "Type", "Language", "TrackIndex", "IsForced")
.IsUnique();
b.ToTable("Tracks");
});

View File

@ -196,7 +196,7 @@ namespace Kyoo
ctx.Response.Headers.Remove("X-Powered-By");
ctx.Response.Headers.Remove("Server");
ctx.Response.Headers.Add("Feature-Policy", "autoplay 'self'; fullscreen");
ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'");
ctx.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self' blob: 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'");
ctx.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
ctx.Response.Headers.Add("Referrer-Policy", "no-referrer");
ctx.Response.Headers.Add("Access-Control-Allow-Origin", "null");

View File

@ -35,7 +35,7 @@ namespace Kyoo.Api
return BadRequest(new {error = ex.Message});
}
if (subtitle == null)
if (subtitle == null || subtitle.Type != StreamType.Subtitle)
return NotFound();
if (subtitle.Codec == "subrip" && extension == "vtt")