Cleaning up the configuration settings

This commit is contained in:
Zoe Roux 2021-04-23 00:46:38 +02:00
parent 8db47d62fd
commit 540a3c27de
14 changed files with 304 additions and 83 deletions

View File

@ -109,18 +109,18 @@ namespace Kyoo.Models
if (!ep.Show.IsMovie) if (!ep.Show.IsMovie)
{ {
if (ep.EpisodeNumber > 1) if (ep.EpisodeNumber > 1)
previous = await library.Get(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1); previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber - 1);
else if (ep.SeasonNumber > 1) else if (ep.SeasonNumber > 1)
{ {
int count = await library.GetCount<Episode>(x => x.ShowID == ep.ShowID int count = await library.GetCount<Episode>(x => x.ShowID == ep.ShowID
&& x.SeasonNumber == ep.SeasonNumber - 1); && x.SeasonNumber == ep.SeasonNumber - 1);
previous = await library.Get(ep.ShowID, ep.SeasonNumber - 1, count); previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber - 1, count);
} }
if (ep.EpisodeNumber >= await library.GetCount<Episode>(x => x.SeasonID == ep.SeasonID)) if (ep.EpisodeNumber >= await library.GetCount<Episode>(x => x.SeasonID == ep.SeasonID))
next = await library.Get(ep.ShowID, ep.SeasonNumber + 1, 1); next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber + 1, 1);
else else
next = await library.Get(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1);
} }
return new WatchItem(ep.ID, return new WatchItem(ep.ID,

View File

@ -86,7 +86,7 @@ namespace Kyoo.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<Episode> GetOrDefault(int id) public override async Task<Episode> GetOrDefault(int id)
{ {
Episode ret = await base.Get(id); Episode ret = await base.GetOrDefault(id);
if (ret != null) if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID); ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret; return ret;
@ -111,9 +111,9 @@ namespace Kyoo.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
public override async Task<Episode> GetOrDefault(Expression<Func<Episode, bool>> predicate) public override async Task<Episode> GetOrDefault(Expression<Func<Episode, bool>> where)
{ {
Episode ret = await base.Get(predicate); Episode ret = await base.GetOrDefault(where);
if (ret != null) if (ret != null)
ret.ShowSlug = await _shows.GetSlug(ret.ShowID); ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret; return ret;

View File

@ -92,15 +92,15 @@ namespace Kyoo.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
public override async Task<LibraryItem> Get(int id) public override async Task<LibraryItem> GetOrDefault(int id)
{ {
return id > 0 return id > 0
? new LibraryItem(await _shows.Value.Get(id)) ? new LibraryItem(await _shows.Value.GetOrDefault(id))
: new LibraryItem(await _collections.Value.Get(-id)); : new LibraryItem(await _collections.Value.GetOrDefault(-id));
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<LibraryItem> Get(string slug) public override Task<LibraryItem> GetOrDefault(string slug)
{ {
throw new InvalidOperationException("You can't get a library item by a slug."); throw new InvalidOperationException("You can't get a library item by a slug.");
} }
@ -189,7 +189,7 @@ namespace Kyoo.Controllers
where, where,
sort, sort,
limit); limit);
if (!items.Any() && await _libraries.Value.Get(id) == null) if (!items.Any() && await _libraries.Value.GetOrDefault(id) == null)
throw new ItemNotFound(); throw new ItemNotFound();
return items; return items;
} }
@ -204,7 +204,7 @@ namespace Kyoo.Controllers
where, where,
sort, sort,
limit); limit);
if (!items.Any() && await _libraries.Value.Get(slug) == null) if (!items.Any() && await _libraries.Value.GetOrDefault(slug) == null)
throw new ItemNotFound(); throw new ItemNotFound();
return items; return items;
} }

View File

@ -98,9 +98,9 @@ namespace Kyoo.Controllers
} }
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<Season> Get(Expression<Func<Season, bool>> predicate) public override async Task<Season> Get(Expression<Func<Season, bool>> where)
{ {
Season ret = await base.Get(predicate); Season ret = await base.Get(where);
ret.ShowSlug = await _shows.GetSlug(ret.ShowID); ret.ShowSlug = await _shows.GetSlug(ret.ShowID);
return ret; return ret;
} }

View File

@ -36,13 +36,13 @@ namespace Kyoo.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override Task<Track> Get(string slug) Task<Track> IRepository<Track>.Get(string slug)
{ {
return Get(slug, StreamType.Unknown); return Get(slug);
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<Track> Get(string slug, StreamType type) public async Task<Track> Get(string slug, StreamType type = StreamType.Unknown)
{ {
Track ret = await GetOrDefault(slug, type); Track ret = await GetOrDefault(slug, type);
if (ret == null) if (ret == null)
@ -51,7 +51,7 @@ namespace Kyoo.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
public Task<Track> GetOrDefault(string slug, StreamType type) public Task<Track> GetOrDefault(string slug, StreamType type = StreamType.Unknown)
{ {
Match match = Regex.Match(slug, Match match = Regex.Match(slug,
@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)(\.(?<type>\w*))?\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?"); @"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)(\.(?<type>\w*))?\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");

View File

@ -113,10 +113,8 @@
</Target> </Target>
<Target Name="BuildTranscoder" BeforeTargets="BeforeBuild" Condition="'$(SkipTranscoder)' != 'true'"> <Target Name="BuildTranscoder" BeforeTargets="BeforeBuild" Condition="'$(SkipTranscoder)' != 'true'">
<Exec WorkingDirectory="$(TranscoderRoot)" Condition="'$(IsWindows)' != 'true'" <Exec WorkingDirectory="$(TranscoderRoot)" Condition="'$(IsWindows)' != 'true'" Command="mkdir -p build %26%26 cd build %26%26 cmake .. %26%26 make -j" />
Command="mkdir -p build %26%26 cd build %26%26 cmake .. %26%26 make -j" /> <Exec WorkingDirectory="$(TranscoderRoot)" Condition="'$(IsWindows)' == 'true'" Command="(if not exist build mkdir build) %26%26 cd build %26%26 cmake .. -G &quot;NMake Makefiles&quot; %26%26 nmake" />
<Exec WorkingDirectory="$(TranscoderRoot)" Condition="'$(IsWindows)' == 'true'"
Command='(if not exist build mkdir build) %26%26 cd build %26%26 cmake .. -G "NMake Makefiles" %26%26 nmake' />
<Copy SourceFiles="$(TranscoderRoot)/build/$(TranscoderBinary)" DestinationFolder="." /> <Copy SourceFiles="$(TranscoderRoot)/build/$(TranscoderBinary)" DestinationFolder="." />
</Target> </Target>

View File

@ -2,6 +2,7 @@ using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -10,28 +11,71 @@ using Npgsql;
namespace Kyoo namespace Kyoo
{ {
/// <summary>
/// The database handle used for all local repositories.
/// </summary>
/// <remarks>
/// It should not be used directly, to access the database use a <see cref="ILibraryManager"/> or repositories.
/// </remarks>
public class DatabaseContext : DbContext public class DatabaseContext : DbContext
{ {
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) /// <summary>
{ /// All libraries of Kyoo. See <see cref="Library"/>.
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; /// </summary>
ChangeTracker.LazyLoadingEnabled = false;
}
public DbSet<Library> Libraries { get; set; } public DbSet<Library> Libraries { get; set; }
/// <summary>
/// All collections of Kyoo. See <see cref="Collection"/>.
/// </summary>
public DbSet<Collection> Collections { get; set; } public DbSet<Collection> Collections { get; set; }
/// <summary>
/// All shows of Kyoo. See <see cref="Show"/>.
/// </summary>
public DbSet<Show> Shows { get; set; } public DbSet<Show> Shows { get; set; }
/// <summary>
/// All seasons of Kyoo. See <see cref="Season"/>.
/// </summary>
public DbSet<Season> Seasons { get; set; } public DbSet<Season> Seasons { get; set; }
/// <summary>
/// All episodes of Kyoo. See <see cref="Episode"/>.
/// </summary>
public DbSet<Episode> Episodes { get; set; } public DbSet<Episode> Episodes { get; set; }
/// <summary>
/// All tracks of Kyoo. See <see cref="Track"/>.
/// </summary>
public DbSet<Track> Tracks { get; set; } public DbSet<Track> Tracks { get; set; }
/// <summary>
/// All genres of Kyoo. See <see cref="Genres"/>.
/// </summary>
public DbSet<Genre> Genres { get; set; } public DbSet<Genre> Genres { get; set; }
/// <summary>
/// All people of Kyoo. See <see cref="People"/>.
/// </summary>
public DbSet<People> People { get; set; } public DbSet<People> People { get; set; }
/// <summary>
/// All studios of Kyoo. See <see cref="Studio"/>.
/// </summary>
public DbSet<Studio> Studios { get; set; } public DbSet<Studio> Studios { get; set; }
/// <summary>
/// All providers of Kyoo. See <see cref="Provider"/>.
/// </summary>
public DbSet<Provider> Providers { get; set; } public DbSet<Provider> Providers { get; set; }
/// <summary>
/// All metadataIDs (ExternalIDs) of Kyoo. See <see cref="MetadataID"/>.
/// </summary>
public DbSet<MetadataID> MetadataIds { get; set; } public DbSet<MetadataID> MetadataIds { get; set; }
/// <summary>
/// All people's role. See <see cref="PeopleRole"/>.
/// </summary>
public DbSet<PeopleRole> PeopleRoles { get; set; } public DbSet<PeopleRole> PeopleRoles { get; set; }
/// <summary>
/// Get a generic link between two resource types.
/// </summary>
/// <remarks>Types are order dependant. You can't inverse the order. Please always put the owner first.</remarks>
/// <typeparam name="T1">The first resource type of the relation. It is the owner of the second</typeparam>
/// <typeparam name="T2">The second resource type of the relation. It is the contained resource.</typeparam>
/// <returns>All links between the two types.</returns>
public DbSet<Link<T1, T2>> Links<T1, T2>() public DbSet<Link<T1, T2>> Links<T1, T2>()
where T1 : class, IResource where T1 : class, IResource
where T2 : class, IResource where T2 : class, IResource
@ -39,7 +83,10 @@ namespace Kyoo
return Set<Link<T1, T2>>(); return Set<Link<T1, T2>>();
} }
/// <summary>
/// A basic constructor that set default values (query tracker behaviors, mapping enums...)
/// </summary>
public DatabaseContext() public DatabaseContext()
{ {
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>(); NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
@ -50,6 +97,21 @@ namespace Kyoo
ChangeTracker.LazyLoadingEnabled = false; ChangeTracker.LazyLoadingEnabled = false;
} }
/// <summary>
/// Create a new <see cref="DatabaseContext"/>.
/// </summary>
/// <param name="options">Connection options to use (witch databse provider to use, connection strings...)</param>
public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
ChangeTracker.LazyLoadingEnabled = false;
}
/// <summary>
/// Set database parameters to support every types of Kyoo.
/// </summary>
/// <param name="modelBuilder">The database's model builder.</param>
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@ -58,14 +120,6 @@ namespace Kyoo
modelBuilder.HasPostgresEnum<ItemType>(); modelBuilder.HasPostgresEnum<ItemType>();
modelBuilder.HasPostgresEnum<StreamType>(); modelBuilder.HasPostgresEnum<StreamType>();
// modelBuilder.Entity<Library>()
// .Property(x => x.Paths)
// .HasColumnType("text[]");
//
// modelBuilder.Entity<Show>()
// .Property(x => x.Aliases)
// .HasColumnType("text[]");
modelBuilder.Entity<Track>() modelBuilder.Entity<Track>()
.Property(t => t.IsDefault) .Property(t => t.IsDefault)
.ValueGeneratedNever(); .ValueGeneratedNever();
@ -196,6 +250,13 @@ namespace Kyoo
.IsUnique(); .IsUnique();
} }
/// <summary>
/// Return a new or an in cache temporary object wih the same ID as the one given
/// </summary>
/// <param name="model">If a resource with the same ID is found in the database, it will be used.
/// <see cref="model"/> will be used overwise</param>
/// <typeparam name="T">The type of the resource</typeparam>
/// <returns>A resource that is now tracked by this context.</returns>
public T GetTemporaryObject<T>(T model) public T GetTemporaryObject<T>(T model)
where T : class, IResource where T : class, IResource
{ {
@ -206,6 +267,11 @@ namespace Kyoo
return model; return model;
} }
/// <summary>
/// Save changes that are applied to this context.
/// </summary>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public override int SaveChanges() public override int SaveChanges()
{ {
try try
@ -221,6 +287,13 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save changes that are applied to this context.
/// </summary>
/// <param name="acceptAllChangesOnSuccess">Indicates whether AcceptAllChanges() is called after the changes
/// have been sent successfully to the database.</param>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public override int SaveChanges(bool acceptAllChangesOnSuccess) public override int SaveChanges(bool acceptAllChangesOnSuccess)
{ {
try try
@ -236,6 +309,13 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save changes that are applied to this context.
/// </summary>
/// <param name="duplicateMessage">The message that will have the <see cref="DuplicatedItemException"/>
/// (if a duplicate is found).</param>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public int SaveChanges(string duplicateMessage) public int SaveChanges(string duplicateMessage)
{ {
try try
@ -251,6 +331,14 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save changes that are applied to this context.
/// </summary>
/// <param name="acceptAllChangesOnSuccess">Indicates whether AcceptAllChanges() is called after the changes
/// have been sent successfully to the database.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
CancellationToken cancellationToken = new()) CancellationToken cancellationToken = new())
{ {
@ -267,6 +355,12 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save changes that are applied to this context.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new()) public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
{ {
try try
@ -282,6 +376,14 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save changes that are applied to this context.
/// </summary>
/// <param name="duplicateMessage">The message that will have the <see cref="DuplicatedItemException"/>
/// (if a duplicate is found).</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <exception cref="DuplicatedItemException">A duplicated item has been found.</exception>
/// <returns>The number of state entries written to the database.</returns>
public async Task<int> SaveChangesAsync(string duplicateMessage, public async Task<int> SaveChangesAsync(string duplicateMessage,
CancellationToken cancellationToken = new()) CancellationToken cancellationToken = new())
{ {
@ -298,6 +400,12 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save changes if no duplicates are found. If one is found, no change are saved but the current changes are no discarded.
/// The current context will still hold those invalid changes.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <returns>The number of state entries written to the database or -1 if a duplicate exist.</returns>
public async Task<int> SaveIfNoDuplicates(CancellationToken cancellationToken = new()) public async Task<int> SaveIfNoDuplicates(CancellationToken cancellationToken = new())
{ {
try try
@ -310,12 +418,31 @@ namespace Kyoo
} }
} }
/// <summary>
/// Save items or retry with a custom method if a duplicate is found.
/// </summary>
/// <param name="obj">The item to save (other changes of this context will also be saved)</param>
/// <param name="onFail">A function to run on fail, the <see cref="obj"/> param wil be mapped.
/// The second parameter is the current retry number.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <typeparam name="T">The type of the item to save</typeparam>
/// <returns>The number of state entries written to the database.</returns>
public Task<T> SaveOrRetry<T>(T obj, Func<T, int, T> onFail, CancellationToken cancellationToken = new()) public Task<T> SaveOrRetry<T>(T obj, Func<T, int, T> onFail, CancellationToken cancellationToken = new())
{ {
return SaveOrRetry(obj, onFail, 0, cancellationToken); return SaveOrRetry(obj, onFail, 0, cancellationToken);
} }
public async Task<T> SaveOrRetry<T>(T obj, /// <summary>
/// Save items or retry with a custom method if a duplicate is found.
/// </summary>
/// <param name="obj">The item to save (other changes of this context will also be saved)</param>
/// <param name="onFail">A function to run on fail, the <see cref="obj"/> param wil be mapped.
/// The second parameter is the current retry number.</param>
/// <param name="recurse">The current retry number.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete</param>
/// <typeparam name="T">The type of the item to save</typeparam>
/// <returns>The number of state entries written to the database.</returns>
private async Task<T> SaveOrRetry<T>(T obj,
Func<T, int, T> onFail, Func<T, int, T> onFail,
int recurse, int recurse,
CancellationToken cancellationToken = new()) CancellationToken cancellationToken = new())
@ -337,11 +464,20 @@ namespace Kyoo
} }
} }
/// <summary>
/// Check if the exception is a duplicated exception.
/// </summary>
/// <remarks>WARNING: this only works for PostgreSQL</remarks>
/// <param name="ex">The exception to check</param>
/// <returns>True if the exception is a duplicate exception. False otherwise</returns>
private static bool IsDuplicateException(Exception ex) private static bool IsDuplicateException(Exception ex)
{ {
return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation}; return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation};
} }
/// <summary>
/// Delete every changes that are on this context.
/// </summary>
private void DiscardChanges() private void DiscardChanges()
{ {
foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged

View File

@ -3,7 +3,11 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.VisualBasic.FileIO; using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Kyoo namespace Kyoo
{ {
@ -18,12 +22,9 @@ namespace Kyoo
/// <param name="args">Command line arguments</param> /// <param name="args">Command line arguments</param>
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
if (args.Length > 0) if (!File.Exists("./settings.json"))
FileSystem.CurrentDirectory = args[0]; File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "settings.json"), "settings.json");
if (!File.Exists("./appsettings.json"))
File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json"), "appsettings.json");
bool? debug = Environment.GetEnvironmentVariable("ENVIRONMENT")?.ToLowerInvariant() switch bool? debug = Environment.GetEnvironmentVariable("ENVIRONMENT")?.ToLowerInvariant() switch
{ {
"d" => true, "d" => true,
@ -49,15 +50,50 @@ namespace Kyoo
await host.Build().RunAsync(); await host.Build().RunAsync();
} }
/// <summary>
/// Register settings.json, environment variables and command lines arguments as configuration.
/// </summary>
/// <param name="builder">The configuration builder to use</param>
/// <param name="args">The command line arguments</param>
/// <returns>The modified configuration builder</returns>
private static IConfigurationBuilder SetupConfig(IConfigurationBuilder builder, string[] args)
{
return builder.AddJsonFile("./settings.json", false, true)
.AddEnvironmentVariables()
.AddCommandLine(args);
}
/// <summary> /// <summary>
/// Createa a web host /// Createa a web host
/// </summary> /// </summary>
/// <param name="args">Command line parameters that can be handled by kestrel</param> /// <param name="args">Command line parameters that can be handled by kestrel</param>
/// <returns>A new web host instance</returns> /// <returns>A new web host instance</returns>
private static IWebHostBuilder CreateWebHostBuilder(string[] args) => private static IWebHostBuilder CreateWebHostBuilder(string[] args)
WebHost.CreateDefaultBuilder(args) {
.UseKestrel(config => { config.AddServerHeader = false; }) WebHost.CreateDefaultBuilder(args);
.UseUrls("http://*:5000")
return new WebHostBuilder()
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
.UseConfiguration(SetupConfig(new ConfigurationBuilder(), args).Build())
.ConfigureAppConfiguration(x => SetupConfig(x, args))
.ConfigureLogging((context, builder) =>
{
builder.AddConfiguration(context.Configuration.GetSection("logging"))
.AddConsole()
.AddDebug()
.AddEventSourceLogger();
})
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
if (context.HostingEnvironment.IsDevelopment())
StaticWebAssetsLoader.UseStaticWebAssets(context.HostingEnvironment, context.Configuration);
})
.ConfigureServices(x => x.AddRouting())
.UseKestrel(options => { options.AddServerHeader = false; })
.UseIIS()
.UseIISIntegration()
.UseStartup<Startup>(); .UseStartup<Startup>();
}
} }
} }

View File

@ -64,14 +64,28 @@ namespace Kyoo.Api
[Authorize(Policy = "Read")] [Authorize(Policy = "Read")]
public async Task<ActionResult<Season>> GetSeason(string showSlug, int seasonNumber, int episodeNumber) public async Task<ActionResult<Season>> GetSeason(string showSlug, int seasonNumber, int episodeNumber)
{ {
return await _libraryManager.Get(showSlug, seasonNumber); try
{
return await _libraryManager.Get(showSlug, seasonNumber);
}
catch (ItemNotFound)
{
return NotFound();
}
} }
[HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")] [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")]
[Authorize(Policy = "Read")] [Authorize(Policy = "Read")]
public async Task<ActionResult<Season>> GetSeason(int showID, int seasonNumber, int episodeNumber) public async Task<ActionResult<Season>> GetSeason(int showID, int seasonNumber, int episodeNumber)
{ {
return await _libraryManager.Get(showID, seasonNumber); try
{
return await _libraryManager.Get(showID, seasonNumber);
}
catch (ItemNotFound)
{
return NotFound();
}
} }
[HttpGet("{episodeID:int}/track")] [HttpGet("{episodeID:int}/track")]
@ -120,7 +134,7 @@ namespace Kyoo.Api
new Sort<Track>(sortBy), new Sort<Track>(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get(showID, seasonNumber, episodeNumber) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber, episodeNumber) == null)
return NotFound(); return NotFound();
return Page(resources, limit); return Page(resources, limit);
} }
@ -130,10 +144,10 @@ namespace Kyoo.Api
} }
} }
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/track")] [HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/track")]
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] [HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")]
[Authorize(Policy = "Read")] [Authorize(Policy = "Read")]
public async Task<ActionResult<Page<Track>>> GetEpisode(string showSlug, public async Task<ActionResult<Page<Track>>> GetEpisode(string slug,
int seasonNumber, int seasonNumber,
int episodeNumber, int episodeNumber,
[FromQuery] string sortBy, [FromQuery] string sortBy,
@ -144,13 +158,13 @@ namespace Kyoo.Api
try try
{ {
ICollection<Track> resources = await _libraryManager.GetAll( ICollection<Track> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere<Track>(where, x => x.Episode.Show.Slug == showSlug ApiHelper.ParseWhere<Track>(where, x => x.Episode.Show.Slug == slug
&& x.Episode.SeasonNumber == seasonNumber && x.Episode.SeasonNumber == seasonNumber
&& x.Episode.EpisodeNumber == episodeNumber), && x.Episode.EpisodeNumber == episodeNumber),
new Sort<Track>(sortBy), new Sort<Track>(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get(showSlug, seasonNumber, episodeNumber) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(slug, seasonNumber, episodeNumber) == null)
return NotFound(); return NotFound();
return Page(resources, limit); return Page(resources, limit);
} }

View File

@ -75,7 +75,7 @@ namespace Kyoo.Api
new Sort<Episode>(sortBy), new Sort<Episode>(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get(showSlug, seasonNumber) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(showSlug, seasonNumber) == null)
return NotFound(); return NotFound();
return Page(resources, limit); return Page(resources, limit);
} }
@ -102,7 +102,7 @@ namespace Kyoo.Api
new Sort<Episode>(sortBy), new Sort<Episode>(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get(showID, seasonNumber) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber) == null)
return NotFound(); return NotFound();
return Page(resources, limit); return Page(resources, limit);
} }

View File

@ -30,14 +30,14 @@ namespace Kyoo.Api
Track subtitle; Track subtitle;
try try
{ {
subtitle = await _libraryManager.Get(slug, StreamType.Subtitle); subtitle = await _libraryManager.GetOrDefault(slug, StreamType.Subtitle);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
return BadRequest(new {error = ex.Message}); return BadRequest(new {error = ex.Message});
} }
if (subtitle == null || subtitle.Type != StreamType.Subtitle) if (subtitle is not {Type: StreamType.Subtitle})
return NotFound(); return NotFound();
if (subtitle.Codec == "subrip" && extension == "vtt") if (subtitle.Codec == "subrip" && extension == "vtt")

View File

@ -1,31 +1,25 @@
{ {
"server.urls": "http://0.0.0.0:5000", "server.urls": "http://*:5000",
"public_url": "http://localhost:5000/", "public_url": "http://localhost:5000/",
"http_port": 5000,
"https_port": 44300,
"Database": { "database": {
"Server": "127.0.0.1", "server": "127.0.0.1",
"Port": "5432", "port": "5432",
"Database": "kyooDB", "database": "kyooDB",
"User Id": "kyoo", "user ID": "kyoo",
"Password": "kyooPassword", "password": "kyooPassword",
"Pooling": "true", "pooling": "true",
"MaxPoolSize": "95", "maxPoolSize": "95",
"Timeout": "30" "timeout": "30"
}, },
"Logging": { "logging": {
"LogLevel": { "logLevel": {
"Default": "Warning", "default": "Warning",
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information", "Microsoft.Hosting.Lifetime": "Information"
"Microsoft.EntityFrameworkCore.DbUpdateException": "None",
"Microsoft.EntityFrameworkCore.Update": "None",
"Microsoft.EntityFrameworkCore.Database.Command": "None"
} }
}, },
"AllowedHosts": "*",
"parallelTasks": "1", "parallelTasks": "1",

View File

@ -5,7 +5,8 @@ After=network.target
[Service] [Service]
User=kyoo User=kyoo
ExecStart=/usr/lib/kyoo/Kyoo /var/lib/kyoo WorkingDirectory=/var/lib/kyoo
ExecStart=/usr/lib/kyoo/Kyoo
Restart=on-abort Restart=on-abort
TimeoutSec=20 TimeoutSec=20

42
settings.json Normal file
View File

@ -0,0 +1,42 @@
{
"server.urls": "http://*:5000",
"public_url": "http://localhost:5000/",
"database": {
"server": "127.0.0.1",
"port": "5432",
"database": "kyooDB",
"user ID": "kyoo",
"password": "kyooPassword",
"pooling": "true",
"maxPoolSize": "95",
"timeout": "30"
},
"logging": {
"logLevel": {
"default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"parallelTasks": "1",
"scheduledTasks": {
"scan": "24:00:00"
},
"certificatePassword": "passphrase",
"transmuxTempPath": "cached/kyoo/transmux",
"transcodeTempPath": "cached/kyoo/transcode",
"peoplePath": "people",
"providerPath": "providers",
"profilePicturePath": "users/",
"plugins": "plugins/",
"defaultPermissions": "read,play,write,admin",
"newUserPermissions": "read,play,write,admin",
"regex": "(?:\\/(?<Collection>.*?))?\\/(?<Show>.*?)(?: \\(\\d+\\))?\\/\\k<Show>(?: \\(\\d+\\))?(?:(?: S(?<Season>\\d+)E(?<Episode>\\d+))| (?<Absolute>\\d+))?.*$",
"subtitleRegex": "^(?<Episode>.*)\\.(?<Language>\\w{1,3})\\.(?<Default>default\\.)?(?<Forced>forced\\.)?.*$"
}