Windows: Fixing windows setup
@ -89,3 +89,5 @@ resharper_xmldoc_attribute_indent = align_by_first_attribute
|
|||||||
resharper_xmldoc_indent_child_elements = RemoveIndent
|
resharper_xmldoc_indent_child_elements = RemoveIndent
|
||||||
resharper_xmldoc_indent_text = RemoveIndent
|
resharper_xmldoc_indent_text = RemoveIndent
|
||||||
|
|
||||||
|
# Waiting for https://github.com/dotnet/roslyn/issues/44596 to get fixed.
|
||||||
|
# file_header_template = Kyoo - A portable and vast media library solution.\nCopyright (c) Kyoo.\n\nSee AUTHORS.md and LICENSE file in the project root for full license information.\n\nKyoo is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\nany later version.\n\nKyoo is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Authors
|
# Authors
|
||||||
Alphabetical order by first name.
|
Ordered by the date of the first commit.
|
||||||
|
|
||||||
* Zoe Roux ([@AnonymusRaccoon](http://github.com/AnonymusRaccoon))
|
* Zoe Roux ([@AnonymusRaccoon](http://github.com/AnonymusRaccoon))
|
||||||
|
@ -25,6 +25,7 @@ Here are a few things you can do that will increase the likelihood of your pull
|
|||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
- [Why should you indent with tabs](https://www.reddit.com/r/javascript/comments/c8drjo/nobody_talks_about_the_real_reason_to_use_tabs/)
|
||||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||||
- [Using Pull Requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
|
- [Using Pull Requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
|
||||||
- [GitHub Help](https://docs.github.com/en)
|
- [GitHub Help](https://docs.github.com/en)
|
||||||
|
24
Kyoo.sln
@ -23,6 +23,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Host.WindowsTrait", "s
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Host.Console", "src\Kyoo.Host.Console\Kyoo.Host.Console.csproj", "{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Host.Console", "src\Kyoo.Host.Console\Kyoo.Host.Console.csproj", "{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Swagger", "src\Kyoo.Swagger\Kyoo.Swagger.csproj", "{7D1A7596-73F6-4D35-842E-A5AD9C620596}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{FEAE1B0E-D797-470F-9030-0EF743575ECC}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{8D28F5EF-0CD7-4697-A2A7-24EC31A48F21}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Databases", "Databases", "{865461CA-EC06-4B42-91CF-8723B0A9BB67}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{C569FF25-7E01-484C-9F72-5B99845AD94B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -81,5 +91,19 @@ Global
|
|||||||
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7D1A7596-73F6-4D35-842E-A5AD9C620596}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{0C8AA7EA-E723-4532-852F-35AA4E8AFED5} = {FEAE1B0E-D797-470F-9030-0EF743575ECC}
|
||||||
|
{BAB270D4-E0EA-4329-BA65-512FDAB01001} = {8D28F5EF-0CD7-4697-A2A7-24EC31A48F21}
|
||||||
|
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C} = {8D28F5EF-0CD7-4697-A2A7-24EC31A48F21}
|
||||||
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6} = {865461CA-EC06-4B42-91CF-8723B0A9BB67}
|
||||||
|
{3213C96D-0BF3-460B-A8B5-B9977229408A} = {865461CA-EC06-4B42-91CF-8723B0A9BB67}
|
||||||
|
{6515380E-1E57-42DA-B6E3-E1C8A848818A} = {865461CA-EC06-4B42-91CF-8723B0A9BB67}
|
||||||
|
{D8658BEA-8949-45AC-BEBB-A4FFC4F800F5} = {C569FF25-7E01-484C-9F72-5B99845AD94B}
|
||||||
|
{98851001-40DD-46A6-94B3-2F8D90722076} = {C569FF25-7E01-484C-9F72-5B99845AD94B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
BIN
icons/banner.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
icons/icon-128x128.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
icons/icon-16x16.png
Normal file
After Width: | Height: | Size: 597 B |
BIN
icons/icon-256x256.ico
Normal file
After Width: | Height: | Size: 105 KiB |
BIN
icons/icon-256x256.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
icons/icon-32x32.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/icon-64x64.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
@ -1,4 +1,24 @@
|
|||||||
<Project>
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Company>Kyoo</Company>
|
||||||
|
<Authors>Kyoo</Authors>
|
||||||
|
<Copyright>Copyright (c) Kyoo</Copyright>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
|
||||||
|
<RequireLicenseAcceptance>true</RequireLicenseAcceptance>
|
||||||
|
|
||||||
|
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<PackageProjectUrl>https://github.com/AnonymusRaccoon/Kyoo</PackageProjectUrl>
|
||||||
|
|
||||||
|
<PackageVersion>1.0.0</PackageVersion>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
|
||||||
|
<ApplicationIcon>$(MSBuildThisFileDirectory)../icons/icon-256x256.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<IsWindows Condition="$([MSBuild]::IsOSPlatform('Windows'))">true</IsWindows>
|
<IsWindows Condition="$([MSBuild]::IsOSPlatform('Windows'))">true</IsWindows>
|
||||||
<IsOSX Condition="$([MSBuild]::IsOSPlatform('OSX'))">true</IsOSX>
|
<IsOSX Condition="$([MSBuild]::IsOSPlatform('OSX'))">true</IsOSX>
|
||||||
@ -11,6 +31,10 @@
|
|||||||
<CheckCodingStyle Condition="$(CheckCodingStyle) == ''">true</CheckCodingStyle>
|
<CheckCodingStyle Condition="$(CheckCodingStyle) == ''">true</CheckCodingStyle>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02" PrivateAssets="All" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="$(CheckCodingStyle) == true">
|
<ItemGroup Condition="$(CheckCodingStyle) == true">
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.354" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.354" PrivateAssets="All" />
|
||||||
@ -21,7 +45,6 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="$(CheckCodingStyle) == true">
|
<PropertyGroup Condition="$(CheckCodingStyle) == true">
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>CS1591;SA1600;SA1601</NoWarn>
|
|
||||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)../Kyoo.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)../Kyoo.ruleset</CodeAnalysisRuleSet>
|
||||||
<!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode>-->
|
<!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode>-->
|
||||||
|
@ -31,8 +31,6 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IFileSystem
|
public interface IFileSystem
|
||||||
{
|
{
|
||||||
// TODO find a way to handle Transmux/Transcode with this system.
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for http queries returning a file. This should be used to return local files
|
/// Used for http queries returning a file. This should be used to return local files
|
||||||
/// or proxy them from a distant server.
|
/// or proxy them from a distant server.
|
||||||
@ -51,7 +49,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// If the type is not specified, it will be deduced automatically (from the extension or by sniffing the file).
|
/// If the type is not specified, it will be deduced automatically (from the extension or by sniffing the file).
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>An <see cref="IActionResult"/> representing the file returned.</returns>
|
/// <returns>An <see cref="IActionResult"/> representing the file returned.</returns>
|
||||||
public IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false, string type = null);
|
IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false, string type = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
||||||
@ -60,7 +58,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="path">The path of the file</param>
|
/// <param name="path">The path of the file</param>
|
||||||
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
||||||
/// <returns>A reader to read the file.</returns>
|
/// <returns>A reader to read the file.</returns>
|
||||||
public Task<Stream> GetReader([NotNull] string path);
|
Task<Stream> GetReader([NotNull] string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
||||||
@ -70,28 +68,28 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="mime">The mime type of the opened file.</param>
|
/// <param name="mime">The mime type of the opened file.</param>
|
||||||
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
||||||
/// <returns>A reader to read the file.</returns>
|
/// <returns>A reader to read the file.</returns>
|
||||||
public Task<Stream> GetReader([NotNull] string path, AsyncRef<string> mime);
|
Task<Stream> GetReader([NotNull] string path, AsyncRef<string> mime);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new file at <paramref name="path"></paramref>.
|
/// Create a new file at <paramref name="path"></paramref>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path of the new file.</param>
|
/// <param name="path">The path of the new file.</param>
|
||||||
/// <returns>A writer to write to the new file.</returns>
|
/// <returns>A writer to write to the new file.</returns>
|
||||||
public Task<Stream> NewFile([NotNull] string path);
|
Task<Stream> NewFile([NotNull] string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new directory at the given path
|
/// Create a new directory at the given path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path of the directory</param>
|
/// <param name="path">The path of the directory</param>
|
||||||
/// <returns>The path of the newly created directory is returned.</returns>
|
/// <returns>The path of the newly created directory is returned.</returns>
|
||||||
public Task<string> CreateDirectory([NotNull] string path);
|
Task<string> CreateDirectory([NotNull] string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Combine multiple paths.
|
/// Combine multiple paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="paths">The paths to combine</param>
|
/// <param name="paths">The paths to combine</param>
|
||||||
/// <returns>The combined path.</returns>
|
/// <returns>The combined path.</returns>
|
||||||
public string Combine(params string[] paths);
|
string Combine(params string[] paths);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List files in a directory.
|
/// List files in a directory.
|
||||||
@ -99,7 +97,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="path">The path of the directory</param>
|
/// <param name="path">The path of the directory</param>
|
||||||
/// <param name="options">Should the search be recursive or not.</param>
|
/// <param name="options">Should the search be recursive or not.</param>
|
||||||
/// <returns>A list of files's path.</returns>
|
/// <returns>A list of files's path.</returns>
|
||||||
public Task<ICollection<string>> ListFiles([NotNull] string path,
|
Task<ICollection<string>> ListFiles([NotNull] string path,
|
||||||
SearchOption options = SearchOption.TopDirectoryOnly);
|
SearchOption options = SearchOption.TopDirectoryOnly);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -107,7 +105,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path to check</param>
|
/// <param name="path">The path to check</param>
|
||||||
/// <returns>True if the path exists, false otherwise</returns>
|
/// <returns>True if the path exists, false otherwise</returns>
|
||||||
public Task<bool> Exists([NotNull] string path);
|
Task<bool> Exists([NotNull] string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the extra directory of a resource <typeparamref name="T"/>.
|
/// Get the extra directory of a resource <typeparamref name="T"/>.
|
||||||
@ -117,6 +115,25 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="resource">The resource to proceed</param>
|
/// <param name="resource">The resource to proceed</param>
|
||||||
/// <typeparam name="T">The type of the resource.</typeparam>
|
/// <typeparam name="T">The type of the resource.</typeparam>
|
||||||
/// <returns>The extra directory of the resource.</returns>
|
/// <returns>The extra directory of the resource.</returns>
|
||||||
public Task<string> GetExtraDirectory<T>([NotNull] T resource);
|
Task<string> GetExtraDirectory<T>([NotNull] T resource);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve tracks for a specific episode.
|
||||||
|
/// Subtitles, chapters and fonts should also be extracted and cached when calling this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode to retrieve tracks for.</param>
|
||||||
|
/// <param name="reExtract">Should the cache be invalidated and subtitles and others be re-extracted?</param>
|
||||||
|
/// <returns>The list of tracks available for this episode.</returns>
|
||||||
|
Task<ICollection<Track>> ExtractInfos([NotNull] Episode episode, bool reExtract);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transmux the selected episode to hls.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode to transmux.</param>
|
||||||
|
/// <returns>The master file (m3u8) of the transmuxed hls file.</returns>
|
||||||
|
IActionResult Transmux([NotNull] Episode episode);
|
||||||
|
|
||||||
|
// Maybe add options for to select the codec.
|
||||||
|
// IActionResult Transcode(Episode episode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,10 +200,11 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// Get the resource by a filter function or null if it is not found.
|
/// Get the resource by a filter function or null if it is not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="where">The filter function.</param>
|
/// <param name="where">The filter function.</param>
|
||||||
|
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
||||||
/// <typeparam name="T">The type of the resource</typeparam>
|
/// <typeparam name="T">The type of the resource</typeparam>
|
||||||
/// <returns>The first resource found that match the where function</returns>
|
/// <returns>The first resource found that match the where function</returns>
|
||||||
[ItemCanBeNull]
|
[ItemCanBeNull]
|
||||||
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where)
|
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
|
||||||
where T : class, IResource;
|
where T : class, IResource;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -317,6 +318,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
|
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
|
||||||
Expression<Func<LibraryItem, bool>> where = null,
|
Expression<Func<LibraryItem, bool>> where = null,
|
||||||
@ -330,6 +332,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
|
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
|
||||||
[Optional] Expression<Func<LibraryItem, bool>> where,
|
[Optional] Expression<Func<LibraryItem, bool>> where,
|
||||||
@ -344,6 +347,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
|
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
|
||||||
Expression<Func<LibraryItem, bool>> where = null,
|
Expression<Func<LibraryItem, bool>> where = null,
|
||||||
@ -357,6 +361,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
|
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
|
||||||
[Optional] Expression<Func<LibraryItem, bool>> where,
|
[Optional] Expression<Func<LibraryItem, bool>> where,
|
||||||
@ -371,6 +376,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
|
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -384,6 +390,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
|
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
@ -398,6 +405,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
|
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -411,6 +419,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
|
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
@ -425,6 +434,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
|
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -438,6 +448,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
|
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
@ -452,6 +463,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
|
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -465,6 +477,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
|
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
|
@ -81,9 +81,10 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// Get the first resource that match the predicate or null if it is not found.
|
/// Get the first resource that match the predicate or null if it is not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="where">A predicate to filter the resource.</param>
|
/// <param name="where">A predicate to filter the resource.</param>
|
||||||
|
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
|
||||||
/// <returns>The resource found</returns>
|
/// <returns>The resource found</returns>
|
||||||
[ItemCanBeNull]
|
[ItemCanBeNull]
|
||||||
Task<T> GetOrDefault(Expression<Func<T, bool>> where);
|
Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Search for resources.
|
/// Search for resources.
|
||||||
@ -179,7 +180,6 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// Delete all resources that match the predicate.
|
/// Delete all resources that match the predicate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
|
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
|
||||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
|
||||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
Task DeleteAll([NotNull] Expression<Func<T, bool>> where);
|
Task DeleteAll([NotNull] Expression<Func<T, bool>> where);
|
||||||
}
|
}
|
||||||
@ -264,6 +264,8 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IEpisodeRepository : IRepository<Episode>
|
public interface IEpisodeRepository : IRepository<Episode>
|
||||||
{
|
{
|
||||||
|
// TODO replace the next methods with extension methods.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a episode from it's showID, it's seasonNumber and it's episode number.
|
/// Get a episode from it's showID, it's seasonNumber and it's episode number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -343,6 +345,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
||||||
Expression<Func<LibraryItem, bool>> where = null,
|
Expression<Func<LibraryItem, bool>> where = null,
|
||||||
@ -356,6 +359,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
||||||
[Optional] Expression<Func<LibraryItem, bool>> where,
|
[Optional] Expression<Func<LibraryItem, bool>> where,
|
||||||
@ -370,6 +374,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
||||||
Expression<Func<LibraryItem, bool>> where = null,
|
Expression<Func<LibraryItem, bool>> where = null,
|
||||||
@ -383,6 +388,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
||||||
[Optional] Expression<Func<LibraryItem, bool>> where,
|
[Optional] Expression<Func<LibraryItem, bool>> where,
|
||||||
@ -418,6 +424,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromShow(int showID,
|
Task<ICollection<PeopleRole>> GetFromShow(int showID,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -431,6 +438,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromShow(int showID,
|
Task<ICollection<PeopleRole>> GetFromShow(int showID,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
@ -445,6 +453,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
|
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -458,6 +467,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
|
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
@ -472,6 +482,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromPeople(int id,
|
Task<ICollection<PeopleRole>> GetFromPeople(int id,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -485,6 +496,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromPeople(int id,
|
Task<ICollection<PeopleRole>> GetFromPeople(int id,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
@ -499,6 +511,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">Sort information (sort order and sort by)</param>
|
/// <param name="sort">Sort information (sort order and sort by)</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
|
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
|
||||||
Expression<Func<PeopleRole, bool>> where = null,
|
Expression<Func<PeopleRole, bool>> where = null,
|
||||||
@ -512,6 +525,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <param name="where">A filter function</param>
|
/// <param name="where">A filter function</param>
|
||||||
/// <param name="sort">A sort by method</param>
|
/// <param name="sort">A sort by method</param>
|
||||||
/// <param name="limit">How many items to return and where to start</param>
|
/// <param name="limit">How many items to return and where to start</param>
|
||||||
|
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
|
||||||
/// <returns>A list of items that match every filters</returns>
|
/// <returns>A list of items that match every filters</returns>
|
||||||
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
|
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
|
||||||
[Optional] Expression<Func<PeopleRole, bool>> where,
|
[Optional] Expression<Func<PeopleRole, bool>> where,
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<Title>Kyoo.Abstractions</Title>
|
|
||||||
<Authors>Zoe Roux</Authors>
|
|
||||||
<Description>Base package to create plugins for Kyoo.</Description>
|
|
||||||
<PackageProjectUrl>https://github.com/AnonymusRaccoon/Kyoo</PackageProjectUrl>
|
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
|
||||||
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
|
||||||
<Company>SDG</Company>
|
|
||||||
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
|
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
|
||||||
<PackageVersion>1.0.0</PackageVersion>
|
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
|
||||||
<LangVersion>default</LangVersion>
|
<LangVersion>default</LangVersion>
|
||||||
|
<Title>Kyoo.Abstractions</Title>
|
||||||
|
<Description>Base package to create plugins for Kyoo.</Description>
|
||||||
<RootNamespace>Kyoo.Abstractions</RootNamespace>
|
<RootNamespace>Kyoo.Abstractions</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@ -24,8 +13,6 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Kyoo.Abstractions.Models.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An attribute to specify on apis to specify it's documentation's name and category.
|
||||||
|
/// If this is applied on a method, the specified method will be exploded from the controller's page and be
|
||||||
|
/// included on the specified tag page.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||||
|
public class ApiDefinitionAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The public name of this api.
|
||||||
|
/// </summary>
|
||||||
|
[NotNull] public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the group in witch this API is. You can also specify a custom sort order using the following
|
||||||
|
/// format: <code>order:name</code>. Everything before the first <c>:</c> will be removed but kept for
|
||||||
|
/// th alphabetical ordering.
|
||||||
|
/// </summary>
|
||||||
|
public string Group { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ApiDefinitionAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the api that will be used on the documentation page.</param>
|
||||||
|
public ApiDefinitionAttribute([NotNull] string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
throw new ArgumentNullException(nameof(name));
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,11 @@ namespace Kyoo.Abstractions.Models.Permissions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Kind Kind { get; }
|
public Kind Kind { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The group of this permission.
|
||||||
|
/// </summary>
|
||||||
|
public Group Group { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ask a permission to run an action.
|
/// Ask a permission to run an action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -49,14 +54,9 @@ namespace Kyoo.Abstractions.Models.Permissions
|
|||||||
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
|
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
|
||||||
/// lead to unspecified behaviors.
|
/// lead to unspecified behaviors.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="type">
|
/// <param name="type">The type of the action</param>
|
||||||
/// The type of the action
|
|
||||||
/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
|
|
||||||
/// </param>
|
|
||||||
public PartialPermissionAttribute(string type)
|
public PartialPermissionAttribute(string type)
|
||||||
{
|
{
|
||||||
if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
|
|
||||||
type = type[..^3];
|
|
||||||
Type = type.ToLower();
|
Type = type.ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,17 +91,16 @@ namespace Kyoo.Abstractions.Models.Permissions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">
|
/// <param name="type">
|
||||||
/// The type of the action
|
/// The type of the action
|
||||||
/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
|
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="permission">The kind of permission needed.</param>
|
/// <param name="permission">
|
||||||
|
/// The kind of permission needed.
|
||||||
|
/// </param>
|
||||||
/// <param name="group">
|
/// <param name="group">
|
||||||
/// The group of this permission (allow grouped permission like overall.read
|
/// The group of this permission (allow grouped permission like overall.read
|
||||||
/// for all read permissions of this group).
|
/// for all read permissions of this group).
|
||||||
/// </param>
|
/// </param>
|
||||||
public PermissionAttribute(string type, Kind permission, Group group = Group.Overall)
|
public PermissionAttribute(string type, Kind permission, Group group = Group.Overall)
|
||||||
{
|
{
|
||||||
if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
|
|
||||||
type = type[..^3];
|
|
||||||
Type = type.ToLower();
|
Type = type.ToLower();
|
||||||
Kind = permission;
|
Kind = permission;
|
||||||
Group = group;
|
Group = group;
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Kyoo.Abstractions.Models.Attributes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
|
||||||
public class SerializeAsAttribute : Attribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The format string to use.
|
|
||||||
/// </summary>
|
|
||||||
public string Format { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new <see cref="SerializeAsAttribute"/> with the selected format.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 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.
|
|
||||||
/// </remarks>
|
|
||||||
/// <example>
|
|
||||||
/// The show's poster serialized uses this format string: <code>{HOST}/api/shows/{Slug}/poster</code>
|
|
||||||
/// </example>
|
|
||||||
/// <param name="format">The format to use</param>
|
|
||||||
public SerializeAsAttribute(string format)
|
|
||||||
{
|
|
||||||
Format = format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -105,9 +105,17 @@ namespace Kyoo.Abstractions.Models
|
|||||||
return CreateReference(path, typeof(T));
|
return CreateReference(path, typeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return a <see cref="ConfigurationReference"/> meaning that the given path is of any type.
|
||||||
|
/// It means that the type can't be edited.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">
|
||||||
|
/// The path that will be untyped (separated by ':' or "__". If empty, it will start at root).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>A configuration reference representing a path of any type.</returns>
|
||||||
public static ConfigurationReference CreateUntyped(string path)
|
public static ConfigurationReference CreateUntyped(string path)
|
||||||
{
|
{
|
||||||
return new(path, null);
|
return new ConfigurationReference(path, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
|
||||||
|
|
||||||
namespace Kyoo.Abstractions.Models
|
namespace Kyoo.Abstractions.Models
|
||||||
{
|
{
|
||||||
@ -34,7 +34,8 @@ namespace Kyoo.Abstractions.Models
|
|||||||
Show,
|
Show,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="LibraryItem"/> is a Movie (a <see cref="Show"/> with <see cref="Models.Show.IsMovie"/> equals to true).
|
/// The <see cref="LibraryItem"/> is a Movie (a <see cref="Show"/> with
|
||||||
|
/// <see cref="Models.Show.IsMovie"/> equals to true).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Movie,
|
Movie,
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
|
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
|
||||||
/// This is used to list content put inside a library.
|
/// This is used to list content put inside a library.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryItem : IResource, IThumbnails
|
public class LibraryItem : CustomTypeDescriptor, IResource, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -86,14 +87,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")]
|
|
||||||
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of this item (ether a collection, a show or a movie).
|
/// The type of this item (ether a collection, a show or a movie).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -169,5 +162,11 @@ namespace Kyoo.Abstractions.Models
|
|||||||
Images = x.Images,
|
Images = x.Images,
|
||||||
Type = ItemType.Collection
|
Type = ItemType.Collection
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GetClassName()
|
||||||
|
{
|
||||||
|
return Type.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
|
||||||
@ -42,15 +41,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this poster.
|
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/collection/{Slug}/poster")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The description of this collection.
|
/// The description of this collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -127,15 +127,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this episode's thumbnail.
|
|
||||||
/// By default, the http path for the thumbnail is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/episodes/{Slug}/thumbnail")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Thumb => Images?.GetValueOrDefault(Models.Images.Thumbnail);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The title of this episode.
|
/// The title of this episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -33,9 +33,8 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// An arbitrary index should not be used, instead use indexes from <see cref="Models.Images"/>
|
/// An arbitrary index should not be used, instead use indexes from <see cref="Models.Images"/>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
/// <example>{"0": "example.com/dune/poster"}</example>
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
// TODO remove Posters properties add them via the json serializer for every IThumbnails
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -63,5 +62,17 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// A video of a few minutes that tease the content.
|
/// A video of a few minutes that tease the content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int Trailer = 3;
|
public const int Trailer = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the name of an image using it's ID. It is also used by the serializer to retrieve all named images.
|
||||||
|
/// If a plugin adds a new image type, it should add it's value and name here to allow the serializer to add it.
|
||||||
|
/// </summary>
|
||||||
|
public static Dictionary<int, string> ImageName { get; } = new()
|
||||||
|
{
|
||||||
|
[Poster] = nameof(Poster),
|
||||||
|
[Thumbnail] = nameof(Thumbnail),
|
||||||
|
[Logo] = nameof(Logo),
|
||||||
|
[Trailer] = nameof(Trailer)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
|
||||||
@ -41,15 +40,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this poster.
|
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/people/{Slug}/poster")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
@ -44,15 +43,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this provider'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.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/providers/{Slug}/logo")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Logo => Images?.GetValueOrDefault(Models.Images.Logo);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of libraries that uses this provider.
|
/// The list of libraries that uses this provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -98,15 +98,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this poster.
|
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
|
|
||||||
|
@ -82,33 +82,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Dictionary<int, string> Images { get; set; }
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/shows/{Slug}/poster")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/shows/{Slug}/logo")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Logo => Images?.GetValueOrDefault(Models.Images.Logo);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")]
|
|
||||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
|
||||||
public string Backdrop => Images?.GetValueOrDefault(Models.Images.Thumbnail);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if this show represent a movie, false otherwise.
|
/// True if this show represent a movie, false otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -52,7 +52,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
Subtitle = 3,
|
Subtitle = 3,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The stream is an attachement (a font, an image or something else).
|
/// The stream is an attachment (a font, an image or something else).
|
||||||
/// Only fonts are handled by kyoo but they are not saved to the database.
|
/// Only fonts are handled by kyoo but they are not saved to the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Attachment = 4
|
Attachment = 4
|
||||||
@ -73,7 +73,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
{
|
{
|
||||||
string type = Type.ToString().ToLower();
|
string type = Type.ToString().ToLower();
|
||||||
string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty;
|
string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty;
|
||||||
string episode = EpisodeSlug ?? Episode?.Slug ?? EpisodeID.ToString();
|
string episode = _episodeSlug ?? Episode?.Slug ?? EpisodeID.ToString();
|
||||||
return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : string.Empty)}.{type}";
|
return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : string.Empty)}.{type}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
"Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]");
|
"Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
EpisodeSlug = match.Groups["ep"].Value;
|
_episodeSlug = match.Groups["ep"].Value;
|
||||||
Language = match.Groups["lang"].Value;
|
Language = match.Groups["lang"].Value;
|
||||||
if (Language == "und")
|
if (Language == "und")
|
||||||
Language = null;
|
Language = null;
|
||||||
@ -100,11 +100,6 @@ namespace Kyoo.Abstractions.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The slug of the episode that contain this track. If this is not set, this track is ill-formed.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeIgnore] public string EpisodeSlug { private get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The title of the stream.
|
/// The title of the stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -153,7 +148,16 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The episode that uses this track.
|
/// The episode that uses this track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation(nameof(EpisodeID))] public Episode Episode { get; set; }
|
[LoadableRelation(nameof(EpisodeID))] public Episode Episode
|
||||||
|
{
|
||||||
|
get => _episode;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_episode = value;
|
||||||
|
if (_episode != null)
|
||||||
|
_episodeSlug = _episode.Slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The index of this track on the episode.
|
/// The index of this track on the episode.
|
||||||
@ -184,6 +188,17 @@ namespace Kyoo.Abstractions.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The slug of the episode that contain this track. If this is not set, this track is ill-formed.
|
||||||
|
/// </summary>
|
||||||
|
[SerializeIgnore] private string _episodeSlug;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The episode that uses this track.
|
||||||
|
/// This is the baking field of <see cref="Episode"/>.
|
||||||
|
/// </summary>
|
||||||
|
[SerializeIgnore] private Episode _episode;
|
||||||
|
|
||||||
// Converting mkv track language to c# system language tag.
|
// Converting mkv track language to c# system language tag.
|
||||||
private static string _GetLanguage(string mkvLanguage)
|
private static string _GetLanguage(string mkvLanguage)
|
||||||
{
|
{
|
||||||
|
55
src/Kyoo.Abstractions/Models/Utils/Constants.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
|
||||||
|
namespace Kyoo.Abstractions.Models.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing constant numbers.
|
||||||
|
/// </summary>
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A property to use on a Microsoft.AspNet.MVC.Route.Order property to mark it as an alternative route
|
||||||
|
/// that won't be included on the swagger.
|
||||||
|
/// </summary>
|
||||||
|
public const int AlternativeRoute = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for main resources of kyoo.
|
||||||
|
/// </summary>
|
||||||
|
public const string ResourcesGroup = "0:Resources";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A group name for <see cref="ApiDefinitionAttribute"/>.
|
||||||
|
/// It should be used for sub resources of kyoo that help define the main resources.
|
||||||
|
/// </summary>
|
||||||
|
public const string MetadataGroup = "1:Metadata";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints useful for playback.
|
||||||
|
/// </summary>
|
||||||
|
public const string WatchGroup = "2:Watch";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for endpoints used by admins.
|
||||||
|
/// </summary>
|
||||||
|
public const string AdminGroup = "3:Admin";
|
||||||
|
}
|
||||||
|
}
|
215
src/Kyoo.Abstractions/Models/Utils/Identifier.cs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Kyoo.Abstractions.Models.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class that represent a resource. It is made to be used as a parameter in a query and not used somewhere else
|
||||||
|
/// on the application.
|
||||||
|
/// This class allow routes to be used via ether IDs or Slugs, this is suitable for every <see cref="IResource"/>.
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(IdentifierConvertor))]
|
||||||
|
public class Identifier
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the resource or null if the slug is specified.
|
||||||
|
/// </summary>
|
||||||
|
private readonly int? _id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The slug of the resource or null if the id is specified.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string _slug;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="Identifier"/> for the given id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The id of the resource.</param>
|
||||||
|
public Identifier(int id)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="Identifier"/> for the given slug.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slug">The slug of the resource.</param>
|
||||||
|
public Identifier([NotNull] string slug)
|
||||||
|
{
|
||||||
|
if (slug == null)
|
||||||
|
throw new ArgumentNullException(nameof(slug));
|
||||||
|
_slug = slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pattern match out of the identifier to a resource.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idFunc">The function to match the ID to a type <typeparamref name="T"/>.</param>
|
||||||
|
/// <param name="slugFunc">The function to match the slug to a type <typeparamref name="T"/>.</param>
|
||||||
|
/// <typeparam name="T">The return type that will be converted to from an ID or a slug.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// The result of the <paramref name="idFunc"/> or <paramref name="slugFunc"/> depending on the pattern.
|
||||||
|
/// </returns>
|
||||||
|
/// <example>
|
||||||
|
/// Example usage:
|
||||||
|
/// <code lang="csharp">
|
||||||
|
/// T ret = await identifier.Match(
|
||||||
|
/// id => _repository.GetOrDefault(id),
|
||||||
|
/// slug => _repository.GetOrDefault(slug)
|
||||||
|
/// );
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public T Match<T>(Func<int, T> idFunc, Func<string, T> slugFunc)
|
||||||
|
{
|
||||||
|
return _id.HasValue
|
||||||
|
? idFunc(_id.Value)
|
||||||
|
: slugFunc(_slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match a custom type to an identifier. This can be used for wrapped resources (see example for more details).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>
|
||||||
|
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
|
||||||
|
/// <typeparam name="T">The type to match against this identifier.</typeparam>
|
||||||
|
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
|
||||||
|
/// <example>
|
||||||
|
/// <code lang="csharp">
|
||||||
|
/// identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public Expression<Func<T, bool>> Matcher<T>(Expression<Func<T, int>> idGetter,
|
||||||
|
Expression<Func<T, string>> slugGetter)
|
||||||
|
{
|
||||||
|
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
|
||||||
|
BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self);
|
||||||
|
ICollection<ParameterExpression> parameters = _id.HasValue ? idGetter.Parameters : slugGetter.Parameters;
|
||||||
|
return Expression.Lambda<Func<T, bool>>(equal, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A matcher overload for nullable IDs. See
|
||||||
|
/// <see cref="Matcher{T}(System.Linq.Expressions.Expression{System.Func{T,int}},System.Linq.Expressions.Expression{System.Func{T,string}})"/>
|
||||||
|
/// for more details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>
|
||||||
|
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
|
||||||
|
/// <typeparam name="T">The type to match against this identifier.</typeparam>
|
||||||
|
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
|
||||||
|
public Expression<Func<T, bool>> Matcher<T>(Expression<Func<T, int?>> idGetter,
|
||||||
|
Expression<Func<T, string>> slugGetter)
|
||||||
|
{
|
||||||
|
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
|
||||||
|
BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self);
|
||||||
|
ICollection<ParameterExpression> parameters = _id.HasValue ? idGetter.Parameters : slugGetter.Parameters;
|
||||||
|
return Expression.Lambda<Func<T, bool>>(equal, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return true if this <see cref="Identifier"/> match a resource.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resource">The resource to match</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if the <paramref name="resource"/> match this identifier, <c>false</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsSame(IResource resource)
|
||||||
|
{
|
||||||
|
return Match(
|
||||||
|
id => resource.ID == id,
|
||||||
|
slug => resource.Slug == slug
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return an expression that return true if this <see cref="Identifier"/> match a given resource.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of resource to match against.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if the given resource match this identifier, <c>false</c> otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public Expression<Func<T, bool>> IsSame<T>()
|
||||||
|
where T : IResource
|
||||||
|
{
|
||||||
|
return _id.HasValue
|
||||||
|
? x => x.ID == _id.Value
|
||||||
|
: x => x.Slug == _slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return an expression that return true if this <see cref="Identifier"/> is containing in a collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listGetter">An expression to retrieve the list to check.</param>
|
||||||
|
/// <typeparam name="T">The type that contain the list to check.</typeparam>
|
||||||
|
/// <typeparam name="T2">The type of resource to check this identifier against.</typeparam>
|
||||||
|
/// <returns>An expression to check if this <see cref="Identifier"/> is contained.</returns>
|
||||||
|
public Expression<Func<T, bool>> IsContainedIn<T, T2>(Expression<Func<T, IEnumerable<T2>>> listGetter)
|
||||||
|
where T2 : IResource
|
||||||
|
{
|
||||||
|
MethodInfo method = typeof(Enumerable)
|
||||||
|
.GetMethods()
|
||||||
|
.Where(x => x.Name == nameof(Enumerable.Any))
|
||||||
|
.FirstOrDefault(x => x.GetParameters().Length == 2)!
|
||||||
|
.MakeGenericMethod(typeof(T2));
|
||||||
|
MethodCallExpression call = Expression.Call(null, method!, listGetter.Body, IsSame<T2>());
|
||||||
|
return Expression.Lambda<Func<T, bool>>(call, listGetter.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return _id.HasValue
|
||||||
|
? _id.Value.ToString()
|
||||||
|
: _slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A custom <see cref="TypeConverter"/> used to convert int or strings to an <see cref="Identifier"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class IdentifierConvertor : TypeConverter
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
|
||||||
|
{
|
||||||
|
if (sourceType == typeof(int) || sourceType == typeof(string))
|
||||||
|
return true;
|
||||||
|
return base.CanConvertFrom(context, sourceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||||
|
{
|
||||||
|
if (value is int id)
|
||||||
|
return new Identifier(id);
|
||||||
|
if (value is not string slug)
|
||||||
|
return base.ConvertFrom(context, culture, value);
|
||||||
|
return int.TryParse(slug, out id)
|
||||||
|
? new Identifier(id)
|
||||||
|
: new Identifier(slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,14 +31,14 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where to start? Using the given sort.
|
/// Where to start? Using the given sort.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int AfterID { get; }
|
public int? AfterID { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="Pagination"/> instance.
|
/// Create a new <see cref="Pagination"/> instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="count">Set the <see cref="Count"/> value</param>
|
/// <param name="count">Set the <see cref="Count"/> value</param>
|
||||||
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
|
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
|
||||||
public Pagination(int count, int afterID = 0)
|
public Pagination(int count, int? afterID = null)
|
||||||
{
|
{
|
||||||
Count = count;
|
Count = count;
|
||||||
AfterID = afterID;
|
AfterID = afterID;
|
||||||
|
58
src/Kyoo.Abstractions/Models/Utils/RequestError.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Kyoo.Abstractions.Models.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of errors that where made in the request.
|
||||||
|
/// </summary>
|
||||||
|
public class RequestError
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of errors that where made in the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <example><c>["InvalidFilter: no field 'startYear' on a collection"]</c></example>
|
||||||
|
[NotNull] public string[] Errors { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="RequestError"/> with one error.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="error">The error to specify in the response.</param>
|
||||||
|
public RequestError([NotNull] string error)
|
||||||
|
{
|
||||||
|
if (error == null)
|
||||||
|
throw new ArgumentNullException(nameof(error));
|
||||||
|
Errors = new[] { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="RequestError"/> with multiple errors.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="errors">The errors to specify in the response.</param>
|
||||||
|
public RequestError([NotNull] string[] errors)
|
||||||
|
{
|
||||||
|
if (errors == null || !errors.Any())
|
||||||
|
throw new ArgumentException("Errors must be non null and not empty", nameof(errors));
|
||||||
|
Errors = errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -32,7 +33,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// Information about tracks and display information that could be used by the player.
|
/// Information about tracks and display information that could be used by the player.
|
||||||
/// This contains mostly data from an <see cref="Episode"/> with another form.
|
/// This contains mostly data from an <see cref="Episode"/> with another form.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WatchItem
|
public class WatchItem : CustomTypeDescriptor, IThumbnails
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the episode associated with this item.
|
/// The ID of the episode associated with this item.
|
||||||
@ -101,26 +102,8 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMovie { get; set; }
|
public bool IsMovie { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The path of this item's poster.
|
public Dictionary<int, string> Images { get; set; }
|
||||||
/// By default, the http path for the poster is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/show/{ShowSlug}/poster")] public string Poster { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this item's logo.
|
|
||||||
/// By default, the http path for the logo is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/show/{ShowSlug}/logo")] public string Logo { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of this item's backdrop.
|
|
||||||
/// By default, the http path for the backdrop is returned from the public API.
|
|
||||||
/// This can be disabled using the internal query flag.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeAs("{HOST}/api/show/{ShowSlug}/backdrop")] public string Backdrop { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The container of the video file of this episode.
|
/// The container of the video file of this episode.
|
||||||
@ -158,36 +141,50 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <returns>A new WatchItem representing the given episode.</returns>
|
/// <returns>A new WatchItem representing the given episode.</returns>
|
||||||
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library)
|
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library)
|
||||||
{
|
{
|
||||||
Episode previous = null;
|
|
||||||
Episode next = null;
|
|
||||||
|
|
||||||
await library.Load(ep, x => x.Show);
|
await library.Load(ep, x => x.Show);
|
||||||
await library.Load(ep, x => x.Tracks);
|
await library.Load(ep, x => x.Tracks);
|
||||||
|
|
||||||
if (!ep.Show.IsMovie && ep.SeasonNumber != null && ep.EpisodeNumber != null)
|
Episode previous = null;
|
||||||
|
Episode next = null;
|
||||||
|
if (!ep.Show.IsMovie)
|
||||||
{
|
{
|
||||||
if (ep.EpisodeNumber > 1)
|
if (ep.AbsoluteNumber != null)
|
||||||
previous = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value - 1);
|
|
||||||
else if (ep.SeasonNumber > 1)
|
|
||||||
{
|
{
|
||||||
previous = (await library.GetAll(x => x.ShowID == ep.ShowID
|
previous = await library.GetOrDefault(
|
||||||
&& x.SeasonNumber == ep.SeasonNumber.Value - 1,
|
x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber,
|
||||||
limit: 1,
|
new Sort<Episode>(x => x.AbsoluteNumber, true)
|
||||||
sort: new Sort<Episode>(x => x.EpisodeNumber, true))
|
);
|
||||||
).FirstOrDefault();
|
next = await library.GetOrDefault(
|
||||||
|
x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber,
|
||||||
|
new Sort<Episode>(x => x.AbsoluteNumber)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
else if (ep.SeasonNumber != null && ep.EpisodeNumber != null)
|
||||||
|
{
|
||||||
|
previous = await library.GetOrDefault(
|
||||||
|
x => x.ShowID == ep.ShowID
|
||||||
|
&& x.SeasonNumber == ep.SeasonNumber
|
||||||
|
&& x.EpisodeNumber < ep.EpisodeNumber,
|
||||||
|
new Sort<Episode>(x => x.EpisodeNumber, true)
|
||||||
|
);
|
||||||
|
previous ??= await library.GetOrDefault(
|
||||||
|
x => x.ShowID == ep.ShowID
|
||||||
|
&& x.SeasonNumber == ep.SeasonNumber - 1,
|
||||||
|
new Sort<Episode>(x => x.EpisodeNumber, true)
|
||||||
|
);
|
||||||
|
|
||||||
if (ep.EpisodeNumber >= await library.GetCount<Episode>(x => x.SeasonID == ep.SeasonID))
|
next = await library.GetOrDefault(
|
||||||
next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value + 1, 1);
|
x => x.ShowID == ep.ShowID
|
||||||
else
|
&& x.SeasonNumber == ep.SeasonNumber
|
||||||
next = await library.GetOrDefault(ep.ShowID, ep.SeasonNumber.Value, ep.EpisodeNumber.Value + 1);
|
&& x.EpisodeNumber > ep.EpisodeNumber,
|
||||||
|
new Sort<Episode>(x => x.EpisodeNumber)
|
||||||
|
);
|
||||||
|
next ??= await library.GetOrDefault(
|
||||||
|
x => x.ShowID == ep.ShowID
|
||||||
|
&& x.SeasonNumber == ep.SeasonNumber + 1,
|
||||||
|
new Sort<Episode>(x => x.EpisodeNumber)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else if (!ep.Show.IsMovie && ep.AbsoluteNumber != null)
|
|
||||||
{
|
|
||||||
previous = await library.GetOrDefault<Episode>(x => x.ShowID == ep.ShowID
|
|
||||||
&& x.AbsoluteNumber == ep.EpisodeNumber + 1);
|
|
||||||
next = await library.GetOrDefault<Episode>(x => x.ShowID == ep.ShowID
|
|
||||||
&& x.AbsoluteNumber == ep.AbsoluteNumber + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WatchItem
|
return new WatchItem
|
||||||
@ -202,6 +199,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
Title = ep.Title,
|
Title = ep.Title,
|
||||||
ReleaseDate = ep.ReleaseDate,
|
ReleaseDate = ep.ReleaseDate,
|
||||||
Path = ep.Path,
|
Path = ep.Path,
|
||||||
|
Images = ep.Show.Images,
|
||||||
Container = PathIO.GetExtension(ep.Path)![1..],
|
Container = PathIO.GetExtension(ep.Path)![1..],
|
||||||
Video = ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video),
|
Video = ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video),
|
||||||
Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(),
|
Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(),
|
||||||
@ -239,5 +237,17 @@ namespace Kyoo.Abstractions.Models
|
|||||||
return Array.Empty<Chapter>();
|
return Array.Empty<Chapter>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GetClassName()
|
||||||
|
{
|
||||||
|
return nameof(Show);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GetComponentName()
|
||||||
|
{
|
||||||
|
return ShowSlug;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using Autofac.Builder;
|
using Autofac.Builder;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
@ -96,9 +97,10 @@ namespace Kyoo.Abstractions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configuration">The configuration instance</param>
|
/// <param name="configuration">The configuration instance</param>
|
||||||
/// <returns>The public URl of kyoo (without a slash at the end)</returns>
|
/// <returns>The public URl of kyoo (without a slash at the end)</returns>
|
||||||
public static string GetPublicUrl(this IConfiguration configuration)
|
public static Uri GetPublicUrl(this IConfiguration configuration)
|
||||||
{
|
{
|
||||||
return configuration["basics:publicUrl"]?.TrimEnd('/') ?? "http://localhost:5000";
|
string uri = configuration["basics:publicUrl"]?.TrimEnd('/') ?? "http://localhost:5000";
|
||||||
|
return new Uri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -425,6 +425,11 @@ namespace Kyoo.Utils
|
|||||||
return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray());
|
return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a dictionary to a query string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The list of query parameters.</param>
|
||||||
|
/// <returns>A valid query string with all items in the dictionary.</returns>
|
||||||
public static string ToQueryString(this Dictionary<string, string> query)
|
public static string ToQueryString(this Dictionary<string, string> query)
|
||||||
{
|
{
|
||||||
if (!query.Any())
|
if (!query.Any())
|
||||||
@ -432,6 +437,11 @@ namespace Kyoo.Utils
|
|||||||
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
|
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rethrow the exception without modifying the stack trace.
|
||||||
|
/// This is similar to the <c>rethrow;</c> code but is useful when the exception is not in a catch block.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ex">The exception to rethrow.</param>
|
||||||
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
|
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
|
||||||
public static void ReThrow([NotNull] this Exception ex)
|
public static void ReThrow([NotNull] this Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -104,7 +104,7 @@ namespace Kyoo.Authentication
|
|||||||
|
|
||||||
DefaultCorsPolicyService cors = new(_logger)
|
DefaultCorsPolicyService cors = new(_logger)
|
||||||
{
|
{
|
||||||
AllowedOrigins = { new Uri(_configuration.GetPublicUrl()).GetLeftPart(UriPartial.Authority) }
|
AllowedOrigins = { _configuration.GetPublicUrl().GetLeftPart(UriPartial.Authority) }
|
||||||
};
|
};
|
||||||
builder.RegisterInstance(cors).As<ICorsPolicyService>().SingleInstance();
|
builder.RegisterInstance(cors).As<ICorsPolicyService>().SingleInstance();
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ namespace Kyoo.Authentication
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Configure(IServiceCollection services)
|
public void Configure(IServiceCollection services)
|
||||||
{
|
{
|
||||||
string publicUrl = _configuration.GetPublicUrl();
|
Uri publicUrl = _configuration.GetPublicUrl();
|
||||||
|
|
||||||
if (_environment.IsDevelopment())
|
if (_environment.IsDevelopment())
|
||||||
IdentityModelEventSource.ShowPII = true;
|
IdentityModelEventSource.ShowPII = true;
|
||||||
@ -136,7 +136,7 @@ namespace Kyoo.Authentication
|
|||||||
|
|
||||||
services.AddIdentityServer(options =>
|
services.AddIdentityServer(options =>
|
||||||
{
|
{
|
||||||
options.IssuerUri = publicUrl;
|
options.IssuerUri = publicUrl.ToString();
|
||||||
options.UserInteraction.LoginUrl = $"{publicUrl}/login";
|
options.UserInteraction.LoginUrl = $"{publicUrl}/login";
|
||||||
options.UserInteraction.ErrorUrl = $"{publicUrl}/error";
|
options.UserInteraction.ErrorUrl = $"{publicUrl}/error";
|
||||||
options.UserInteraction.LogoutUrl = $"{publicUrl}/logout";
|
options.UserInteraction.LogoutUrl = $"{publicUrl}/logout";
|
||||||
@ -151,7 +151,7 @@ namespace Kyoo.Authentication
|
|||||||
services.AddAuthentication()
|
services.AddAuthentication()
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
options.Authority = publicUrl;
|
options.Authority = publicUrl.ToString();
|
||||||
options.Audience = "kyoo";
|
options.Audience = "kyoo";
|
||||||
options.RequireHttpsMetadata = false;
|
options.RequireHttpsMetadata = false;
|
||||||
});
|
});
|
||||||
@ -189,7 +189,7 @@ namespace Kyoo.Authentication
|
|||||||
{
|
{
|
||||||
app.Use((ctx, next) =>
|
app.Use((ctx, next) =>
|
||||||
{
|
{
|
||||||
ctx.SetIdentityServerOrigin(_configuration.GetPublicUrl());
|
ctx.SetIdentityServerOrigin(_configuration.GetPublicUrl().ToString());
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
app.UseIdentityServer();
|
app.UseIdentityServer();
|
||||||
|
@ -23,6 +23,9 @@ using IdentityModel;
|
|||||||
|
|
||||||
namespace Kyoo.Authentication
|
namespace Kyoo.Authentication
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Some functions to handle password management.
|
||||||
|
/// </summary>
|
||||||
public static class PasswordUtils
|
public static class PasswordUtils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -61,7 +61,7 @@ namespace Kyoo.Authentication
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IFilterMetadata Create(PartialPermissionAttribute attribute)
|
public IFilterMetadata Create(PartialPermissionAttribute attribute)
|
||||||
{
|
{
|
||||||
return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, _options);
|
return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, attribute.Group, _options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -109,15 +109,24 @@ namespace Kyoo.Authentication
|
|||||||
/// Create a new permission validator with the given options.
|
/// Create a new permission validator with the given options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="partialInfo">The partial permission to validate.</param>
|
/// <param name="partialInfo">The partial permission to validate.</param>
|
||||||
|
/// <param name="group">The group of the permission.</param>
|
||||||
/// <param name="options">The option containing default values.</param>
|
/// <param name="options">The option containing default values.</param>
|
||||||
public PermissionValidatorFilter(object partialInfo, IOptionsMonitor<PermissionOption> options)
|
public PermissionValidatorFilter(object partialInfo, Group? group, IOptionsMonitor<PermissionOption> options)
|
||||||
{
|
{
|
||||||
if (partialInfo is Kind kind)
|
switch (partialInfo)
|
||||||
|
{
|
||||||
|
case Kind kind:
|
||||||
_kind = kind;
|
_kind = kind;
|
||||||
else if (partialInfo is string perm)
|
break;
|
||||||
|
case string perm:
|
||||||
_permission = perm;
|
_permission = perm;
|
||||||
else
|
break;
|
||||||
|
default:
|
||||||
throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind.");
|
throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group != null)
|
||||||
|
_group = group.Value;
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
src/Kyoo.Authentication/Models/DTO/OtacResponse.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
namespace Kyoo.Authentication.Models.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A one time access token
|
||||||
|
/// </summary>
|
||||||
|
public class OtacResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The One Time Access Token that allow one to connect to an account without typing a password or without
|
||||||
|
/// any kind of verification. This is valid only one time and only for a short period of time.
|
||||||
|
/// </summary>
|
||||||
|
public string OTAC { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="OtacResponse"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="otac">The one time access token.</param>
|
||||||
|
public OtacResponse(string otac)
|
||||||
|
{
|
||||||
|
OTAC = otac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,9 @@ using IdentityServer4.Models;
|
|||||||
using IdentityServer4.Services;
|
using IdentityServer4.Services;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Authentication.Models;
|
using Kyoo.Authentication.Models;
|
||||||
using Kyoo.Authentication.Models.DTO;
|
using Kyoo.Authentication.Models.DTO;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
@ -36,15 +38,19 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
namespace Kyoo.Authentication.Views
|
namespace Kyoo.Authentication.Views
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The class responsible for login, logout, permissions and claims of a user.
|
/// The endpoint responsible for login, logout, permissions and claims of a user.
|
||||||
|
/// Documentation of this endpoint is a work in progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("api/account")]
|
/// TODO document this well.
|
||||||
[Route("api/accounts")]
|
[Route("api/accounts")]
|
||||||
|
[Route("api/account", Order = AlternativeRoute)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
|
[ApiDefinition("Account")]
|
||||||
public class AccountApi : Controller, IProfileService
|
public class AccountApi : Controller, IProfileService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -78,12 +84,17 @@ namespace Kyoo.Authentication.Views
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register a new user and return a OTAC to connect to it.
|
/// Register
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Register a new user and return a OTAC to connect to it.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="request">The DTO register request</param>
|
/// <param name="request">The DTO register request</param>
|
||||||
/// <returns>A OTAC to connect to this new account</returns>
|
/// <returns>A OTAC to connect to this new account</returns>
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(RequestError))]
|
||||||
|
public async Task<ActionResult<OtacResponse>> Register([FromBody] RegisterRequest request)
|
||||||
{
|
{
|
||||||
User user = request.ToUser();
|
User user = request.ToUser();
|
||||||
user.Permissions = _options.Value.Permissions.NewUser;
|
user.Permissions = _options.Value.Permissions.NewUser;
|
||||||
@ -96,10 +107,10 @@ namespace Kyoo.Authentication.Views
|
|||||||
}
|
}
|
||||||
catch (DuplicatedItemException)
|
catch (DuplicatedItemException)
|
||||||
{
|
{
|
||||||
return Conflict(new { Errors = new { Duplicate = new[] { "A user with this name already exists" } } });
|
return Conflict(new RequestError("A user with this name already exists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new { Otac = user.ExtraData["otac"] });
|
return Ok(new OtacResponse(user.ExtraData["otac"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -119,8 +130,11 @@ namespace Kyoo.Authentication.Views
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Login the user.
|
/// Login
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Login the current session.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="login">The DTO login request</param>
|
/// <param name="login">The DTO login request</param>
|
||||||
/// <returns>TODO</returns>
|
/// <returns>TODO</returns>
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
@ -177,6 +191,7 @@ namespace Kyoo.Authentication.Views
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
|
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
|
||||||
{
|
{
|
||||||
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
|
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
|
||||||
@ -187,6 +202,7 @@ namespace Kyoo.Authentication.Views
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public async Task IsActiveAsync(IsActiveContext context)
|
public async Task IsActiveAsync(IsActiveContext context)
|
||||||
{
|
{
|
||||||
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
|
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
|
||||||
|
@ -283,7 +283,7 @@ namespace Kyoo.Core
|
|||||||
builder.ReadFrom.Services(services);
|
builder.ReadFrom.Services(services);
|
||||||
|
|
||||||
const string template =
|
const string template =
|
||||||
"[{@t:HH:mm:ss} {@l:u3} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1), 15} "
|
"[{@t:HH:mm:ss} {@l:u3} {Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1), 25} "
|
||||||
+ "({@i:D10})] {@m}{#if not EndsWith(@m, '\n')}\n{#end}{@x}";
|
+ "({@i:D10})] {@m}{#if not EndsWith(@m, '\n')}\n{#end}{@x}";
|
||||||
|
|
||||||
if (SystemdHelpers.IsSystemdService())
|
if (SystemdHelpers.IsSystemdService())
|
||||||
|
@ -32,6 +32,11 @@ using Newtonsoft.Json.Linq;
|
|||||||
|
|
||||||
namespace Kyoo.Core.Controllers
|
namespace Kyoo.Core.Controllers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class to ease configuration management. This work WITH Microsoft's package, you can still use IOptions patterns
|
||||||
|
/// to access your options, this manager ease dynamic work and editing.
|
||||||
|
/// It works with <see cref="ConfigurationReference"/>.
|
||||||
|
/// </summary>
|
||||||
public class ConfigurationManager : IConfigurationManager
|
public class ConfigurationManager : IConfigurationManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -214,5 +214,19 @@ namespace Kyoo.Core.Controllers
|
|||||||
};
|
};
|
||||||
return await CreateDirectory(path);
|
return await CreateDirectory(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||||
|
{
|
||||||
|
IFileSystem fs = _GetFileSystemForPath(episode.Path, out string _);
|
||||||
|
return fs.ExtractInfos(episode, reExtract);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IActionResult Transmux(Episode episode)
|
||||||
|
{
|
||||||
|
IFileSystem fs = _GetFileSystemForPath(episode.Path, out string _);
|
||||||
|
return fs.Transmux(episode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,18 @@ namespace Kyoo.Core.Controllers
|
|||||||
throw new NotSupportedException("Extras can not be stored inside an http filesystem.");
|
throw new NotSupportedException("Extras can not be stored inside an http filesystem.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Extracting infos is not supported on an http filesystem.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IActionResult Transmux(Episode episode)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Transmuxing is not supported on an http filesystem.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IActionResult"/> to proxy an http request.
|
/// An <see cref="IActionResult"/> to proxy an http request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -41,6 +41,11 @@ namespace Kyoo.Core.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IContentTypeProvider _provider;
|
private readonly IContentTypeProvider _provider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The transcoder of local files.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ITranscoder _transcoder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Options to check if the metadata should be kept in the show directory or in a kyoo's directory.
|
/// Options to check if the metadata should be kept in the show directory or in a kyoo's directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -51,10 +56,14 @@ namespace Kyoo.Core.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The options to use.</param>
|
/// <param name="options">The options to use.</param>
|
||||||
/// <param name="provider">An extension provider to get content types from files extensions.</param>
|
/// <param name="provider">An extension provider to get content types from files extensions.</param>
|
||||||
public LocalFileSystem(IOptionsMonitor<BasicOptions> options, IContentTypeProvider provider)
|
/// <param name="transcoder">The transcoder of local files.</param>
|
||||||
|
public LocalFileSystem(IOptionsMonitor<BasicOptions> options,
|
||||||
|
IContentTypeProvider provider,
|
||||||
|
ITranscoder transcoder)
|
||||||
{
|
{
|
||||||
_options = options;
|
_options = options;
|
||||||
_provider = provider;
|
_provider = provider;
|
||||||
|
_transcoder = transcoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -155,5 +164,17 @@ namespace Kyoo.Core.Controllers
|
|||||||
_ => null
|
_ => null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||||
|
{
|
||||||
|
return _transcoder.ExtractInfos(episode, reExtract);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IActionResult Transmux(Episode episode)
|
||||||
|
{
|
||||||
|
return _transcoder.Transmux(episode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,25 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Abstractions.Models;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
namespace Kyoo.Abstractions.Controllers
|
namespace Kyoo.Core.Controllers
|
||||||
{
|
{
|
||||||
public interface ITranscoder
|
/// <summary>
|
||||||
|
/// The route constraint that goes with the <see cref="Identifier"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class IdentifierRouteConstraint : IRouteConstraint
|
||||||
{
|
{
|
||||||
Task<Track[]> ExtractInfos(Episode episode, bool reextract);
|
/// <inheritdoc />
|
||||||
|
public bool Match(HttpContext httpContext,
|
||||||
Task<string> Transmux(Episode episode);
|
IRouter route,
|
||||||
|
string routeKey,
|
||||||
Task<string> Transcode(Episode episode);
|
RouteValueDictionary values,
|
||||||
|
RouteDirection routeDirection)
|
||||||
|
{
|
||||||
|
return values.ContainsKey(routeKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,6 +29,9 @@ using Kyoo.Utils;
|
|||||||
|
|
||||||
namespace Kyoo.Core.Controllers
|
namespace Kyoo.Core.Controllers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An class to interact with the database. Every repository is mapped through here.
|
||||||
|
/// </summary>
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -163,10 +166,10 @@ namespace Kyoo.Core.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where)
|
public async Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy)
|
||||||
where T : class, IResource
|
where T : class, IResource
|
||||||
{
|
{
|
||||||
return await GetRepository<T>().GetOrDefault(where);
|
return await GetRepository<T>().GetOrDefault(where, sortBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -29,6 +29,10 @@ namespace Kyoo.Core.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PassthroughPermissionValidator : IPermissionValidator
|
public class PassthroughPermissionValidator : IPermissionValidator
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="PassthroughPermissionValidator"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger used to warn that no real permission validator exists.</param>
|
||||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor",
|
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor",
|
||||||
Justification = "ILogger should include the typeparam for context.")]
|
Justification = "ILogger should include the typeparam for context.")]
|
||||||
public PassthroughPermissionValidator(ILogger<PassthroughPermissionValidator> logger)
|
public PassthroughPermissionValidator(ILogger<PassthroughPermissionValidator> logger)
|
||||||
|
@ -169,7 +169,6 @@ namespace Kyoo.Core.Controllers
|
|||||||
resource.Tracks = await resource.Tracks.SelectAsync(x =>
|
resource.Tracks = await resource.Tracks.SelectAsync(x =>
|
||||||
{
|
{
|
||||||
x.Episode = resource;
|
x.Episode = resource;
|
||||||
x.EpisodeSlug = resource.Slug;
|
|
||||||
return _tracks.Create(x);
|
return _tracks.Create(x);
|
||||||
}).ToListAsync();
|
}).ToListAsync();
|
||||||
_database.Tracks.AttachRange(resource.Tracks);
|
_database.Tracks.AttachRange(resource.Tracks);
|
||||||
|
@ -115,9 +115,12 @@ namespace Kyoo.Core.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where)
|
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
|
||||||
{
|
{
|
||||||
return Database.Set<T>().FirstOrDefaultAsync(where);
|
IQueryable<T> query = Database.Set<T>();
|
||||||
|
Expression<Func<T, object>> sortKey = sortBy.Key ?? DefaultSort;
|
||||||
|
query = sortBy.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
||||||
|
return query.FirstOrDefaultAsync(where);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -179,9 +182,9 @@ namespace Kyoo.Core.Controllers
|
|||||||
|
|
||||||
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
||||||
|
|
||||||
if (limit.AfterID != 0)
|
if (limit.AfterID != null)
|
||||||
{
|
{
|
||||||
TValue after = await get(limit.AfterID);
|
TValue after = await get(limit.AfterID.Value);
|
||||||
Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type);
|
Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type);
|
||||||
query = query.Where(Expression.Lambda<Func<TValue, bool>>(
|
query = query.Where(Expression.Lambda<Func<TValue, bool>>(
|
||||||
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key),
|
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key),
|
||||||
|
@ -17,34 +17,70 @@
|
|||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Core.Models.Options;
|
using Kyoo.Core.Models.Options;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
// We use threads so tasks are not always awaited.
|
|
||||||
#pragma warning disable 4014
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Controllers
|
namespace Kyoo.Core.Controllers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The transcoder used by the <see cref="LocalFileSystem"/>.
|
||||||
|
/// </summary>
|
||||||
public class Transcoder : ITranscoder
|
public class Transcoder : ITranscoder
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The class that interact with the transcoder written in C.
|
||||||
|
/// </summary>
|
||||||
private static class TranscoderAPI
|
private static class TranscoderAPI
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the library. For windows '.dll' should be appended, on linux or macos it should be prefixed
|
||||||
|
/// by 'lib' and '.so' or '.dylib' should be appended.
|
||||||
|
/// </summary>
|
||||||
private const string TranscoderPath = "transcoder";
|
private const string TranscoderPath = "transcoder";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the C library, setup the logger and return the size of a <see cref="Models.Watch.Stream"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The size of a <see cref="Models.Watch.Stream"/></returns>
|
||||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
||||||
private static extern int init();
|
private static extern int init();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the C library, setup the logger and return the size of a <see cref="Models.Watch.Stream"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The size of a <see cref="Models.Watch.Stream"/></returns>
|
||||||
public static int Init() => init();
|
public static int Init() => init();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transmux the file at the specified path. The path must be a local one with '/' as a separator.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of a local file with '/' as a separators.</param>
|
||||||
|
/// <param name="outPath">The path of the hls output file.</param>
|
||||||
|
/// <param name="playableDuration">
|
||||||
|
/// The number of seconds currently playable. This is incremented as the file gets transmuxed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns><c>0</c> on success, non 0 on failure.</returns>
|
||||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl,
|
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl,
|
||||||
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||||
private static extern int transmux(string path, string outpath, out float playableDuration);
|
private static extern int transmux(string path, string outPath, out float playableDuration);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transmux the file at the specified path. The path must be a local one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of a local file.</param>
|
||||||
|
/// <param name="outPath">The path of the hls output file.</param>
|
||||||
|
/// <param name="playableDuration">
|
||||||
|
/// The number of seconds currently playable. This is incremented as the file gets transmuxed.
|
||||||
|
/// </param>
|
||||||
|
/// <returns><c>0</c> on success, non 0 on failure.</returns>
|
||||||
public static int Transmux(string path, string outPath, out float playableDuration)
|
public static int Transmux(string path, string outPath, out float playableDuration)
|
||||||
{
|
{
|
||||||
path = path.Replace('\\', '/');
|
path = path.Replace('\\', '/');
|
||||||
@ -52,24 +88,47 @@ namespace Kyoo.Core.Controllers
|
|||||||
return transmux(path, outPath, out playableDuration);
|
return transmux(path, outPath, out playableDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve tracks from a video file and extract subtitles, fonts and chapters to an external file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">
|
||||||
|
/// The path of the video file to analyse. This must be a local path with '/' as a separator.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="outPath">The directory that will be used to store extracted files.</param>
|
||||||
|
/// <param name="length">The size of the returned array.</param>
|
||||||
|
/// <param name="trackCount">The number of tracks in the returned array.</param>
|
||||||
|
/// <param name="reExtract">Should the cache be invalidated and information re-extracted or not?</param>
|
||||||
|
/// <returns>A pointer to an array of <see cref="Models.Watch.Stream"/></returns>
|
||||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl,
|
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl,
|
||||||
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||||
private static extern IntPtr extract_infos(string path,
|
private static extern IntPtr extract_infos(string path,
|
||||||
string outpath,
|
string outPath,
|
||||||
out uint length,
|
out uint length,
|
||||||
out uint trackCount,
|
out uint trackCount,
|
||||||
bool reextracct);
|
bool reExtract);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An helper method to free an array of <see cref="Models.Watch.Stream"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="streams">A pointer to the first element of the array</param>
|
||||||
|
/// <param name="count">The number of items in the array.</param>
|
||||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
||||||
private static extern void free_streams(IntPtr streams, uint count);
|
private static extern void free_streams(IntPtr streams, uint count);
|
||||||
|
|
||||||
public static Track[] ExtractInfos(string path, string outPath, bool reextract)
|
/// <summary>
|
||||||
|
/// Retrieve tracks from a video file and extract subtitles, fonts and chapters to an external file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of the video file to analyse. This must be a local path.</param>
|
||||||
|
/// <param name="outPath">The directory that will be used to store extracted files.</param>
|
||||||
|
/// <param name="reExtract">Should the cache be invalidated and information re-extracted or not?</param>
|
||||||
|
/// <returns>An array of <see cref="Track"/>.</returns>
|
||||||
|
public static Track[] ExtractInfos(string path, string outPath, bool reExtract)
|
||||||
{
|
{
|
||||||
path = path.Replace('\\', '/');
|
path = path.Replace('\\', '/');
|
||||||
outPath = outPath.Replace('\\', '/');
|
outPath = outPath.Replace('\\', '/');
|
||||||
|
|
||||||
int size = Marshal.SizeOf<Models.Watch.Stream>();
|
int size = Marshal.SizeOf<Models.Watch.Stream>();
|
||||||
IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reextract);
|
IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reExtract);
|
||||||
IntPtr streamsPtr = ptr;
|
IntPtr streamsPtr = ptr;
|
||||||
Track[] tracks;
|
Track[] tracks;
|
||||||
|
|
||||||
@ -98,67 +157,161 @@ namespace Kyoo.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BadTranscoderException : Exception { }
|
/// <summary>
|
||||||
|
/// The file system used to retrieve the extra directory of shows to know where to extract information.
|
||||||
|
/// </summary>
|
||||||
private readonly IFileSystem _files;
|
private readonly IFileSystem _files;
|
||||||
private readonly IOptions<BasicOptions> _options;
|
|
||||||
private readonly Lazy<ILibraryManager> _library;
|
|
||||||
|
|
||||||
public Transcoder(IFileSystem files, IOptions<BasicOptions> options, Lazy<ILibraryManager> library)
|
/// <summary>
|
||||||
|
/// Options to know where to cache transmuxed/transcoded episodes.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IOptions<BasicOptions> _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The logger to use. This is also used by the wrapped C library.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILogger<Transcoder> _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="Transcoder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="files">
|
||||||
|
/// The file system used to retrieve the extra directory of shows to know where to extract information.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="options">Options to know where to cache transmuxed/transcoded episodes.</param>
|
||||||
|
/// <param name="logger">The logger to use. This is also used by the wrapped C library.</param>
|
||||||
|
public Transcoder(IFileSystem files, IOptions<BasicOptions> options, ILogger<Transcoder> logger)
|
||||||
{
|
{
|
||||||
_files = files;
|
_files = files;
|
||||||
_options = options;
|
_options = options;
|
||||||
_library = library;
|
_logger = logger;
|
||||||
|
|
||||||
if (TranscoderAPI.Init() != Marshal.SizeOf<Models.Watch.Stream>())
|
if (TranscoderAPI.Init() != Marshal.SizeOf<Models.Watch.Stream>())
|
||||||
throw new BadTranscoderException();
|
_logger.LogCritical("The transcoder library could not be initialized correctly");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Track[]> ExtractInfos(Episode episode, bool reextract)
|
/// <inheritdoc />
|
||||||
|
public async Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||||
{
|
{
|
||||||
await _library.Value.Load(episode, x => x.Show);
|
string dir = await _files.GetExtraDirectory(episode);
|
||||||
string dir = await _files.GetExtraDirectory(episode.Show);
|
|
||||||
if (dir == null)
|
if (dir == null)
|
||||||
throw new ArgumentException("Invalid path.");
|
throw new ArgumentException("Invalid path.");
|
||||||
return await Task.Factory.StartNew(
|
return await Task.Factory.StartNew(
|
||||||
() => TranscoderAPI.ExtractInfos(episode.Path, dir, reextract),
|
() => TranscoderAPI.ExtractInfos(episode.Path, dir, reExtract),
|
||||||
TaskCreationOptions.LongRunning);
|
TaskCreationOptions.LongRunning
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> Transmux(Episode episode)
|
/// <inheritdoc />
|
||||||
|
public IActionResult Transmux(Episode episode)
|
||||||
{
|
{
|
||||||
if (!File.Exists(episode.Path))
|
|
||||||
throw new ArgumentException("Path does not exists. Can't transcode.");
|
|
||||||
|
|
||||||
string folder = Path.Combine(_options.Value.TransmuxPath, episode.Slug);
|
string folder = Path.Combine(_options.Value.TransmuxPath, episode.Slug);
|
||||||
string manifest = Path.Combine(folder, episode.Slug + ".m3u8");
|
string manifest = Path.GetFullPath(Path.Combine(folder, episode.Slug + ".m3u8"));
|
||||||
float playableDuration = 0;
|
|
||||||
bool transmuxFailed = false;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(folder);
|
Directory.CreateDirectory(folder);
|
||||||
if (File.Exists(manifest))
|
if (File.Exists(manifest))
|
||||||
return manifest;
|
return new PhysicalFileResult(manifest, "application/x-mpegurl");
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException)
|
catch (UnauthorizedAccessException)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Access to the path {manifest} is denied. Please change your transmux path in the config.");
|
_logger.LogCritical("Access to the path {Manifest} is denied. " +
|
||||||
return null;
|
"Please change your transmux path in the config", manifest);
|
||||||
|
return new StatusCodeResult(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new TransmuxResult(episode.Path, manifest, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An action result that runs the transcoder and return the created manifest file after a few seconds of
|
||||||
|
/// the video has been proceeded. If the transcoder fails, it returns a 500 error code.
|
||||||
|
/// </summary>
|
||||||
|
private class TransmuxResult : IActionResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The path of the episode to transmux. It must be a local one.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string _path;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path of the manifest file to create. It must be a local one.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string _manifest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The logger to use in case of issue.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TransmuxResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of the episode to transmux. It must be a local one.</param>
|
||||||
|
/// <param name="manifest">The path of the manifest file to create. It must be a local one.</param>
|
||||||
|
/// <param name="logger">The logger to use in case of issue.</param>
|
||||||
|
public TransmuxResult(string path, string manifest, ILogger logger)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
_manifest = Path.GetFullPath(manifest);
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use threads so tasks are not always awaited.
|
||||||
|
#pragma warning disable 4014
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task ExecuteResultAsync(ActionContext context)
|
||||||
|
{
|
||||||
|
float playableDuration = 0;
|
||||||
|
bool transmuxFailed = false;
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
transmuxFailed = TranscoderAPI.Transmux(episode.Path, manifest, out playableDuration) != 0;
|
transmuxFailed = TranscoderAPI.Transmux(_path, _manifest, out playableDuration) != 0;
|
||||||
}, TaskCreationOptions.LongRunning);
|
}, TaskCreationOptions.LongRunning);
|
||||||
while (playableDuration < 10 || (!File.Exists(manifest) && !transmuxFailed))
|
|
||||||
|
while (playableDuration < 10 || (!File.Exists(_manifest) && !transmuxFailed))
|
||||||
await Task.Delay(10);
|
await Task.Delay(10);
|
||||||
return transmuxFailed ? null : manifest;
|
|
||||||
|
if (!transmuxFailed)
|
||||||
|
{
|
||||||
|
new PhysicalFileResult(_manifest, "application/x-mpegurl")
|
||||||
|
.ExecuteResultAsync(context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogCritical("The transmuxing failed on the C library");
|
||||||
|
new StatusCodeResult(500)
|
||||||
|
.ExecuteResultAsync(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> Transcode(Episode episode)
|
#pragma warning restore 4014
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The transcoder used by the <see cref="LocalFileSystem"/>. This is on a different interface than the file system
|
||||||
|
/// to offset the work.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITranscoder
|
||||||
{
|
{
|
||||||
return Task.FromResult<string>(null); // Not implemented yet.
|
/// <summary>
|
||||||
}
|
/// Retrieve tracks for a specific episode.
|
||||||
|
/// Subtitles, chapters and fonts should also be extracted and cached when calling this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode to retrieve tracks for.</param>
|
||||||
|
/// <param name="reExtract">Should the cache be invalidated and subtitles and others be re-extracted?</param>
|
||||||
|
/// <returns>The list of tracks available for this episode.</returns>
|
||||||
|
Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transmux the selected episode to hls.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode to transmux.</param>
|
||||||
|
/// <returns>The master file (m3u8) of the transmuxed hls file.</returns>
|
||||||
|
IActionResult Transmux(Episode episode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,28 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using Autofac.Core;
|
using Autofac.Core;
|
||||||
using Autofac.Core.Registration;
|
using Autofac.Core.Registration;
|
||||||
using Autofac.Extras.AttributeMetadata;
|
using Autofac.Extras.AttributeMetadata;
|
||||||
using Kyoo.Abstractions;
|
using Kyoo.Abstractions;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Core.Api;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Core.Controllers;
|
using Kyoo.Core.Controllers;
|
||||||
using Kyoo.Core.Models.Options;
|
using Kyoo.Core.Models.Options;
|
||||||
using Kyoo.Core.Tasks;
|
using Kyoo.Core.Tasks;
|
||||||
using Kyoo.Database;
|
using Kyoo.Database;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using IMetadataProvider = Kyoo.Abstractions.Controllers.IMetadataProvider;
|
using IMetadataProvider = Kyoo.Abstractions.Controllers.IMetadataProvider;
|
||||||
|
using JsonOptions = Kyoo.Core.Api.JsonOptions;
|
||||||
|
|
||||||
namespace Kyoo.Core
|
namespace Kyoo.Core
|
||||||
{
|
{
|
||||||
@ -63,20 +67,6 @@ namespace Kyoo.Core
|
|||||||
{ "logging", null }
|
{ "logging", null }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The configuration to use.
|
|
||||||
/// </summary>
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new core module instance and use the given configuration.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configuration">The configuration to use</param>
|
|
||||||
public CoreModule(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Configure(ContainerBuilder builder)
|
public void Configure(ContainerBuilder builder)
|
||||||
{
|
{
|
||||||
@ -136,14 +126,29 @@ namespace Kyoo.Core
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Configure(IServiceCollection services)
|
public void Configure(IServiceCollection services)
|
||||||
{
|
{
|
||||||
string publicUrl = _configuration.GetPublicUrl();
|
services.AddTransient<IConfigureOptions<MvcNewtonsoftJsonOptions>, JsonOptions>();
|
||||||
|
|
||||||
services.AddMvc().AddControllersAsServices();
|
services.AddMvcCore()
|
||||||
services.AddControllers()
|
.AddNewtonsoftJson()
|
||||||
.AddNewtonsoftJson(x =>
|
.AddDataAnnotations()
|
||||||
|
.AddControllersAsServices()
|
||||||
|
.AddApiExplorer()
|
||||||
|
.ConfigureApiBehaviorOptions(options =>
|
||||||
{
|
{
|
||||||
x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl);
|
options.SuppressMapClientErrors = true;
|
||||||
x.SerializerSettings.Converters.Add(new PeopleRoleConverter());
|
options.InvalidModelStateResponseFactory = ctx =>
|
||||||
|
{
|
||||||
|
string[] errors = ctx.ModelState
|
||||||
|
.SelectMany(x => x.Value.Errors)
|
||||||
|
.Select(x => x.ErrorMessage)
|
||||||
|
.ToArray();
|
||||||
|
return new BadRequestObjectResult(new RequestError(errors));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.Configure<RouteOptions>(x =>
|
||||||
|
{
|
||||||
|
x.ConstraintMap.Add("id", typeof(IdentifierRouteConstraint));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddResponseCompression(x =>
|
services.AddResponseCompression(x =>
|
||||||
|
@ -22,6 +22,9 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Core
|
namespace Kyoo.Core
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing helper methods.
|
||||||
|
/// </summary>
|
||||||
public static class Helper
|
public static class Helper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<TranscoderRoot>../Kyoo.Transcoder/</TranscoderRoot>
|
|
||||||
|
|
||||||
<Company>SDG</Company>
|
|
||||||
<Authors>Zoe Roux</Authors>
|
|
||||||
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
|
||||||
<LangVersion>default</LangVersion>
|
<LangVersion>default</LangVersion>
|
||||||
|
<AssemblyName>Kyoo.Core</AssemblyName>
|
||||||
|
<RootNamespace>Kyoo.Core</RootNamespace>
|
||||||
|
<TranscoderRoot>../Kyoo.Transcoder/</TranscoderRoot>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -41,6 +38,7 @@
|
|||||||
<ProjectReference Include="../Kyoo.SqLite/Kyoo.SqLite.csproj" />
|
<ProjectReference Include="../Kyoo.SqLite/Kyoo.SqLite.csproj" />
|
||||||
<ProjectReference Include="../Kyoo.Authentication/Kyoo.Authentication.csproj" />
|
<ProjectReference Include="../Kyoo.Authentication/Kyoo.Authentication.csproj" />
|
||||||
<ProjectReference Include="../Kyoo.WebApp/Kyoo.WebApp.csproj" />
|
<ProjectReference Include="../Kyoo.WebApp/Kyoo.WebApp.csproj" />
|
||||||
|
<ProjectReference Include="../Kyoo.Swagger/Kyoo.Swagger.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="BuildTranscoder" BeforeTargets="BeforeBuild" Condition="'$(SkipTranscoder)' != 'true' And !Exists('$(TranscoderRoot)/build/$(TranscoderBinary)')">
|
<Target Name="BuildTranscoder" BeforeTargets="BeforeBuild" Condition="'$(SkipTranscoder)' != 'true' And !Exists('$(TranscoderRoot)/build/$(TranscoderBinary)')">
|
||||||
@ -62,6 +60,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="../../LICENSE" CopyToOutputDirectory="Always" Visible="false" />
|
<Content Include="../../LICENSE" CopyToOutputDirectory="Always" Visible="false" />
|
||||||
|
<Content Include="../../AUTHORS.md" CopyToOutputDirectory="Always" Visible="false" />
|
||||||
<Content Include="settings.json" CopyToOutputDirectory="PreserveNewest" />
|
<Content Include="settings.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using Kyoo.Abstractions;
|
using Kyoo.Abstractions;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
@ -28,6 +29,7 @@ using Kyoo.Core.Models.Options;
|
|||||||
using Kyoo.Core.Tasks;
|
using Kyoo.Core.Tasks;
|
||||||
using Kyoo.Postgresql;
|
using Kyoo.Postgresql;
|
||||||
using Kyoo.SqLite;
|
using Kyoo.SqLite;
|
||||||
|
using Kyoo.Swagger;
|
||||||
using Kyoo.TheMovieDb;
|
using Kyoo.TheMovieDb;
|
||||||
using Kyoo.TheTvdb;
|
using Kyoo.TheTvdb;
|
||||||
using Kyoo.Utils;
|
using Kyoo.Utils;
|
||||||
@ -75,7 +77,8 @@ namespace Kyoo.Core
|
|||||||
typeof(PostgresModule),
|
typeof(PostgresModule),
|
||||||
typeof(SqLiteModule),
|
typeof(SqLiteModule),
|
||||||
typeof(PluginTvdb),
|
typeof(PluginTvdb),
|
||||||
typeof(PluginTmdb)
|
typeof(PluginTmdb),
|
||||||
|
typeof(SwaggerModule)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +109,9 @@ namespace Kyoo.Core
|
|||||||
/// <param name="services">The service collection to fill.</param>
|
/// <param name="services">The service collection to fill.</param>
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
foreach (Assembly assembly in _plugins.GetAllPlugins().Select(x => x.GetType().Assembly))
|
||||||
|
services.AddMvcCore().AddApplicationPart(assembly);
|
||||||
|
|
||||||
foreach (IPlugin plugin in _plugins.GetAllPlugins())
|
foreach (IPlugin plugin in _plugins.GetAllPlugins())
|
||||||
plugin.Configure(services);
|
plugin.Configure(services);
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ namespace Kyoo.Core.Tasks
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public TaskParameters GetParameters()
|
public TaskParameters GetParameters()
|
||||||
{
|
{
|
||||||
return new();
|
return new TaskParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -56,7 +56,7 @@ namespace Kyoo.Core.Tasks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The transcoder used to extract subtitles and metadata.
|
/// The transcoder used to extract subtitles and metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ITranscoder _transcoder;
|
private readonly IFileSystem _transcoder;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="RegisterEpisode"/> task.
|
/// Create a new <see cref="RegisterEpisode"/> task.
|
||||||
@ -74,13 +74,13 @@ namespace Kyoo.Core.Tasks
|
|||||||
/// The thumbnail manager used to download images.
|
/// The thumbnail manager used to download images.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="transcoder">
|
/// <param name="transcoder">
|
||||||
/// The transcoder used to extract subtitles and metadata.
|
/// The file manager used to retrieve episodes metadata.
|
||||||
/// </param>
|
/// </param>
|
||||||
public RegisterEpisode(IIdentifier identifier,
|
public RegisterEpisode(IIdentifier identifier,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
AProviderComposite metadataProvider,
|
AProviderComposite metadataProvider,
|
||||||
IThumbnailsManager thumbnailsManager,
|
IThumbnailsManager thumbnailsManager,
|
||||||
ITranscoder transcoder)
|
IFileSystem transcoder)
|
||||||
{
|
{
|
||||||
_identifier = identifier;
|
_identifier = identifier;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -19,18 +19,23 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An API to retrieve or edit configuration settings
|
/// An API to retrieve or edit configuration settings
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("api/config")]
|
|
||||||
[Route("api/configuration")]
|
[Route("api/configuration")]
|
||||||
|
[Route("api/config", Order = AlternativeRoute)]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
|
[PartialPermission("Configuration", Group = Group.Admin)]
|
||||||
|
[ApiDefinition("Configuration", Group = AdminGroup)]
|
||||||
public class ConfigurationApi : Controller
|
public class ConfigurationApi : Controller
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -48,14 +53,19 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a permission from it's slug.
|
/// Get config value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Retrieve a configuration's value from it's slug.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="slug">The permission to retrieve. You can use ':' or "__" to get a child value.</param>
|
/// <param name="slug">The permission to retrieve. You can use ':' or "__" to get a child value.</param>
|
||||||
/// <returns>The associate value or list of values.</returns>
|
/// <returns>The associate value or list of values.</returns>
|
||||||
/// <response code="200">Return the configuration value or the list of configurations</response>
|
/// <response code="200">Return the configuration value or the list of configurations</response>
|
||||||
/// <response code="404">No configuration exists for the given slug</response>
|
/// <response code="404">No configuration exists for the given slug</response>
|
||||||
[HttpGet("{slug}")]
|
[HttpGet("{slug}")]
|
||||||
[Permission(nameof(ConfigurationApi), Kind.Read, Group.Admin)]
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public ActionResult<object> GetConfiguration(string slug)
|
public ActionResult<object> GetConfiguration(string slug)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -69,15 +79,20 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Edit a permission from it's slug.
|
/// Edit config
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Edit a configuration's value from it's slug.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="slug">The permission to edit. You can use ':' or "__" to get a child value.</param>
|
/// <param name="slug">The permission to edit. You can use ':' or "__" to get a child value.</param>
|
||||||
/// <param name="newValue">The new value of the configuration</param>
|
/// <param name="newValue">The new value of the configuration</param>
|
||||||
/// <returns>The edited value.</returns>
|
/// <returns>The edited value.</returns>
|
||||||
/// <response code="200">Return the edited value</response>
|
/// <response code="200">Return the edited value</response>
|
||||||
/// <response code="404">No configuration exists for the given slug</response>
|
/// <response code="404">No configuration exists for the given slug</response>
|
||||||
[HttpPut("{slug}")]
|
[HttpPut("{slug}")]
|
||||||
[Permission(nameof(ConfigurationApi), Kind.Write, Group.Admin)]
|
[PartialPermission(Kind.Write)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult<object>> EditConfiguration(string slug, [FromBody] object newValue)
|
public async Task<ActionResult<object>> EditConfiguration(string slug, [FromBody] object newValue)
|
||||||
{
|
{
|
||||||
try
|
try
|
108
src/Kyoo.Core/Views/Admin/TaskApi.cs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An endpoint to list and run tasks in the background.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/tasks")]
|
||||||
|
[Route("api/task", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission("Task", Group = Group.Admin)]
|
||||||
|
[ApiDefinition("Tasks", Group = AdminGroup)]
|
||||||
|
public class TaskApi : ControllerBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The task manager used to retrieve and start tasks.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ITaskManager _taskManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TaskApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="taskManager">The task manager used to start tasks.</param>
|
||||||
|
public TaskApi(ITaskManager taskManager)
|
||||||
|
{
|
||||||
|
_taskManager = taskManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all tasks
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Retrieve all tasks available in this instance of Kyoo.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>A list of every tasks that this instance know.</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public ActionResult<ICollection<ITask>> GetTasks()
|
||||||
|
{
|
||||||
|
return Ok(_taskManager.GetAllTasks());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start task
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Start a task with the given arguments. If a task is already running, it may be queued and started only when
|
||||||
|
/// a runner become available.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="taskSlug">The slug of the task to start.</param>
|
||||||
|
/// <param name="args">The list of arguments to give to the task.</param>
|
||||||
|
/// <returns>The task has been started or is queued.</returns>
|
||||||
|
/// <response code="400">The task misses an argument or an argument is invalid.</response>
|
||||||
|
/// <response code="404">No task could be found with the given slug.</response>
|
||||||
|
[HttpPut("{taskSlug}")]
|
||||||
|
[HttpGet("{taskSlug}", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Create)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public IActionResult RunTask(string taskSlug,
|
||||||
|
[FromQuery] Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_taskManager.StartTask(taskSlug, new Progress<float>(), args);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,201 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/collection")]
|
|
||||||
[Route("api/collections")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(CollectionApi))]
|
|
||||||
public class CollectionApi : CrudApi<Collection>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
private readonly IThumbnailsManager _thumbs;
|
|
||||||
|
|
||||||
public CollectionApi(ILibraryManager libraryManager,
|
|
||||||
IFileSystem files,
|
|
||||||
IThumbnailsManager thumbs,
|
|
||||||
IOptions<BasicOptions> options)
|
|
||||||
: base(libraryManager.CollectionRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_files = files;
|
|
||||||
_thumbs = thumbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/show")]
|
|
||||||
[HttpGet("{id:int}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Collections.Any(y => y.ID == id)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/show")]
|
|
||||||
[HttpGet("{slug}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Collections.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/library")]
|
|
||||||
[HttpGet("{id:int}/libraries")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Library>>> GetLibraries(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Library> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Library>(where, x => x.Collections.Any(y => y.ID == id)),
|
|
||||||
new Sort<Library>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/library")]
|
|
||||||
[HttpGet("{slug}/libraries")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Library>>> GetLibraries(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Library> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Library>(where, x => x.Collections.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Library>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/poster")]
|
|
||||||
public async Task<IActionResult> GetPoster(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Collection collection = await _libraryManager.Get<Collection>(slug);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Poster));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/logo")]
|
|
||||||
public async Task<IActionResult> GetLogo(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Collection collection = await _libraryManager.Get<Collection>(slug);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Logo));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/backdrop")]
|
|
||||||
[HttpGet("{slug}/thumbnail")]
|
|
||||||
public async Task<IActionResult> GetBackdrop(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Collection collection = await _libraryManager.Get<Collection>(slug);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Thumbnail));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,238 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/episode")]
|
|
||||||
[Route("api/episodes")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(EpisodeApi))]
|
|
||||||
public class EpisodeApi : CrudApi<Episode>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IThumbnailsManager _thumbnails;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
|
|
||||||
public EpisodeApi(ILibraryManager libraryManager,
|
|
||||||
IOptions<BasicOptions> options,
|
|
||||||
IFileSystem files,
|
|
||||||
IThumbnailsManager thumbnails)
|
|
||||||
: base(libraryManager.EpisodeRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_files = files;
|
|
||||||
_thumbnails = thumbnails;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{episodeID:int}/show")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(int episodeID)
|
|
||||||
{
|
|
||||||
Show ret = await _libraryManager.GetOrDefault<Show>(x => x.Episodes.Any(y => y.ID == episodeID));
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/show")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(string showSlug, int seasonNumber, int episodeNumber)
|
|
||||||
{
|
|
||||||
Show ret = await _libraryManager.GetOrDefault<Show>(showSlug);
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/show")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber, int episodeNumber)
|
|
||||||
{
|
|
||||||
Show ret = await _libraryManager.GetOrDefault<Show>(showID);
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{episodeID:int}/season")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Season>> GetSeason(int episodeID)
|
|
||||||
{
|
|
||||||
Season ret = await _libraryManager.GetOrDefault<Season>(x => x.Episodes.Any(y => y.ID == episodeID));
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Season>> GetSeason(string showSlug, int seasonNumber, int episodeNumber)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _libraryManager.Get(showSlug, seasonNumber);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Season>> GetSeason(int showID, int seasonNumber, int episodeNumber)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _libraryManager.Get(showID, seasonNumber);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{episodeID:int}/track")]
|
|
||||||
[HttpGet("{episodeID:int}/tracks")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Track>>> GetEpisode(int episodeID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Track> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Track>(where, x => x.Episode.ID == episodeID),
|
|
||||||
new Sort<Track>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Episode>(episodeID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/track")]
|
|
||||||
[HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/tracks")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Track>>> GetEpisode(int showID,
|
|
||||||
int seasonNumber,
|
|
||||||
int episodeNumber,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Track> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Track>(where, x => x.Episode.ShowID == showID
|
|
||||||
&& x.Episode.SeasonNumber == seasonNumber
|
|
||||||
&& x.Episode.EpisodeNumber == episodeNumber),
|
|
||||||
new Sort<Track>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber, episodeNumber) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/track")]
|
|
||||||
[HttpGet("{slug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Track>>> GetEpisode(string slug,
|
|
||||||
int seasonNumber,
|
|
||||||
int episodeNumber,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Track> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Track>(where, x => x.Episode.Show.Slug == slug
|
|
||||||
&& x.Episode.SeasonNumber == seasonNumber
|
|
||||||
&& x.Episode.EpisodeNumber == episodeNumber),
|
|
||||||
new Sort<Track>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault(slug, seasonNumber, episodeNumber) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/thumbnail")]
|
|
||||||
[HttpGet("{id:int}/backdrop")]
|
|
||||||
public async Task<IActionResult> GetThumb(int id)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Episode episode = await _libraryManager.Get<Episode>(id);
|
|
||||||
return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/thumbnail")]
|
|
||||||
[HttpGet("{slug}/backdrop")]
|
|
||||||
public async Task<IActionResult> GetThumb(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Episode episode = await _libraryManager.Get<Episode>(slug);
|
|
||||||
return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/genre")]
|
|
||||||
[Route("api/genres")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(GenreApi))]
|
|
||||||
public class GenreApi : CrudApi<Genre>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
public GenreApi(ILibraryManager libraryManager, IOptions<BasicOptions> options)
|
|
||||||
: base(libraryManager.GenreRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/show")]
|
|
||||||
[HttpGet("{id:int}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Genres.Any(y => y.ID == id)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Genre>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/show")]
|
|
||||||
[HttpGet("{slug}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Genres.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Genre>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,24 +22,53 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A static class containing methods to parse the <c>where</c> query string.
|
||||||
|
/// </summary>
|
||||||
public static class ApiHelper
|
public static class ApiHelper
|
||||||
{
|
{
|
||||||
public static Expression StringCompatibleExpression(Func<Expression, Expression, BinaryExpression> operand,
|
/// <summary>
|
||||||
Expression left,
|
/// Make an expression (like
|
||||||
Expression right)
|
/// <see cref="Expression.LessThan(System.Linq.Expressions.Expression,System.Linq.Expressions.Expression)"/>
|
||||||
{
|
/// compatible with strings). If the expressions are not strings, the given <paramref name="operand"/> is
|
||||||
if (left is MemberExpression member && ((PropertyInfo)member.Member).PropertyType == typeof(string))
|
/// constructed but if the expressions are strings, this method make the <paramref name="operand"/> compatible with
|
||||||
|
/// strings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="operand">
|
||||||
|
/// The expression to make compatible. It should be something like
|
||||||
|
/// <see cref="Expression.LessThan(System.Linq.Expressions.Expression,System.Linq.Expressions.Expression)"/> or
|
||||||
|
/// <see cref="Expression.Equal(System.Linq.Expressions.Expression,System.Linq.Expressions.Expression)"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="left">The first parameter to compare.</param>
|
||||||
|
/// <param name="right">The second parameter to compare.</param>
|
||||||
|
/// <returns>A comparison expression compatible with strings</returns>
|
||||||
|
public static BinaryExpression StringCompatibleExpression(
|
||||||
|
[NotNull] Func<Expression, Expression, BinaryExpression> operand,
|
||||||
|
[NotNull] Expression left,
|
||||||
|
[NotNull] Expression right)
|
||||||
{
|
{
|
||||||
|
if (left is not MemberExpression member || ((PropertyInfo)member.Member).PropertyType != typeof(string))
|
||||||
|
return operand(left, right);
|
||||||
MethodCallExpression call = Expression.Call(typeof(string), "Compare", null, left, right);
|
MethodCallExpression call = Expression.Call(typeof(string), "Compare", null, left, right);
|
||||||
return operand(call, Expression.Constant(0));
|
return operand(call, Expression.Constant(0));
|
||||||
}
|
}
|
||||||
return operand(left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a <c>where</c> query for the given <typeparamref name="T"/>. Items can be filtered by any property
|
||||||
|
/// of the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="where">The list of filters.</param>
|
||||||
|
/// <param name="defaultWhere">
|
||||||
|
/// A custom expression to initially filter a collection. It will be combined with the parsed expression.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="T">The type to create filters for.</typeparam>
|
||||||
|
/// <exception cref="ArgumentException">A filter is invalid.</exception>
|
||||||
|
/// <returns>An expression representing the filters that can be used anywhere or compiled</returns>
|
||||||
public static Expression<Func<T, bool>> ParseWhere<T>(Dictionary<string, string> where,
|
public static Expression<Func<T, bool>> ParseWhere<T>(Dictionary<string, string> where,
|
||||||
Expression<Func<T, bool>> defaultWhere = null)
|
Expression<Func<T, bool>> defaultWhere = null)
|
||||||
{
|
{
|
||||||
@ -96,18 +125,17 @@ namespace Kyoo.Core.Api
|
|||||||
"not" when valueExpr == null => _ResourceEqual(propertyExpr, value, true),
|
"not" when valueExpr == null => _ResourceEqual(propertyExpr, value, true),
|
||||||
|
|
||||||
"eq" => Expression.Equal(propertyExpr, valueExpr),
|
"eq" => Expression.Equal(propertyExpr, valueExpr),
|
||||||
"not" => Expression.NotEqual(propertyExpr, valueExpr!),
|
"not" => Expression.NotEqual(propertyExpr, valueExpr),
|
||||||
"lt" => StringCompatibleExpression(Expression.LessThan, propertyExpr, valueExpr),
|
"lt" => StringCompatibleExpression(Expression.LessThan, propertyExpr, valueExpr!),
|
||||||
"lte" => StringCompatibleExpression(Expression.LessThanOrEqual, propertyExpr, valueExpr),
|
"lte" => StringCompatibleExpression(Expression.LessThanOrEqual, propertyExpr, valueExpr!),
|
||||||
"gt" => StringCompatibleExpression(Expression.GreaterThan, propertyExpr, valueExpr),
|
"gt" => StringCompatibleExpression(Expression.GreaterThan, propertyExpr, valueExpr!),
|
||||||
"gte" => StringCompatibleExpression(Expression.GreaterThanOrEqual, propertyExpr, valueExpr),
|
"gte" => StringCompatibleExpression(Expression.GreaterThanOrEqual, propertyExpr, valueExpr!),
|
||||||
_ => throw new ArgumentException($"Invalid operand: {operand}")
|
_ => throw new ArgumentException($"Invalid operand: {operand}")
|
||||||
};
|
};
|
||||||
|
|
||||||
if (expression != null)
|
expression = expression != null
|
||||||
expression = Expression.AndAlso(expression, condition);
|
? Expression.AndAlso(expression, condition)
|
||||||
else
|
: condition;
|
||||||
expression = condition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Expression.Lambda<Func<T, bool>>(expression!, param);
|
return Expression.Lambda<Func<T, bool>>(expression!, param);
|
||||||
|
63
src/Kyoo.Core/Views/Helper/BaseApi.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Kyoo.Abstractions;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A common API containing custom methods to help inheritors.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseApi : ControllerBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Construct and return a page from an api.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resources">The list of resources that should be included in the current page.</param>
|
||||||
|
/// <param name="limit">
|
||||||
|
/// The max number of items that should be present per page. This should be the same as in the request,
|
||||||
|
/// it is used to calculate if this is the last page and so on.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="TResult">The type of items on the page.</typeparam>
|
||||||
|
/// <returns>A Page representing the response.</returns>
|
||||||
|
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
||||||
|
where TResult : IResource
|
||||||
|
{
|
||||||
|
Uri publicUrl = HttpContext.RequestServices
|
||||||
|
.GetRequiredService<IConfiguration>()
|
||||||
|
.GetPublicUrl();
|
||||||
|
return new Page<TResult>(
|
||||||
|
resources,
|
||||||
|
new Uri(publicUrl, Request.Path),
|
||||||
|
Request.Query.ToDictionary(
|
||||||
|
x => x.Key,
|
||||||
|
x => x.Value.ToString(),
|
||||||
|
StringComparer.InvariantCultureIgnoreCase
|
||||||
|
),
|
||||||
|
limit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,126 +18,190 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A base class to handle CRUD operations on a specific resource type <typeparamref name="T"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of resource to make CRUD apis for.</typeparam>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[ResourceView]
|
[ResourceView]
|
||||||
public class CrudApi<T> : ControllerBase
|
public class CrudApi<T> : BaseApi
|
||||||
where T : class, IResource
|
where T : class, IResource
|
||||||
{
|
{
|
||||||
private readonly IRepository<T> _repository;
|
/// <summary>
|
||||||
|
/// The repository of the resource, used to retrieve, save and do operations on the baking store.
|
||||||
|
/// </summary>
|
||||||
|
protected IRepository<T> Repository { get; }
|
||||||
|
|
||||||
protected Uri BaseURL { get; }
|
/// <summary>
|
||||||
|
/// Create a new <see cref="CrudApi{T}"/> using the given repository and base url.
|
||||||
public CrudApi(IRepository<T> repository, Uri baseURL)
|
/// </summary>
|
||||||
|
/// <param name="repository">
|
||||||
|
/// The repository to use as a baking store for the type <typeparamref name="T"/>.
|
||||||
|
/// </param>
|
||||||
|
public CrudApi(IRepository<T> repository)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
Repository = repository;
|
||||||
BaseURL = baseURL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
/// <summary>
|
||||||
|
/// Get item
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get a specific resource via it's ID or it's slug.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the resource to retrieve.</param>
|
||||||
|
/// <returns>The retrieved resource.</returns>
|
||||||
|
/// <response code="404">A resource with the given ID or slug does not exist.</response>
|
||||||
|
[HttpGet("{identifier:id}")]
|
||||||
[PartialPermission(Kind.Read)]
|
[PartialPermission(Kind.Read)]
|
||||||
public virtual async Task<ActionResult<T>> Get(int id)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<T>> Get(Identifier identifier)
|
||||||
{
|
{
|
||||||
T ret = await _repository.GetOrDefault(id);
|
T ret = await identifier.Match(
|
||||||
if (ret == null)
|
id => Repository.GetOrDefault(id),
|
||||||
return NotFound();
|
slug => Repository.GetOrDefault(slug)
|
||||||
return ret;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public virtual async Task<ActionResult<T>> Get(string slug)
|
|
||||||
{
|
|
||||||
T ret = await _repository.GetOrDefault(slug);
|
|
||||||
if (ret == null)
|
if (ret == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get count
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the number of resources that match the filters.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="where">A list of filters to respect.</param>
|
||||||
|
/// <returns>How many resources matched that filter.</returns>
|
||||||
|
/// <response code="400">Invalid filters.</response>
|
||||||
[HttpGet("count")]
|
[HttpGet("count")]
|
||||||
[PartialPermission(Kind.Read)]
|
[PartialPermission(Kind.Read)]
|
||||||
public virtual async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
public async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _repository.GetCount(ApiHelper.ParseWhere<T>(where));
|
return await Repository.GetCount(ApiHelper.ParseWhere<T>(where));
|
||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
catch (ArgumentException ex)
|
||||||
{
|
{
|
||||||
return BadRequest(new { Error = ex.Message });
|
return BadRequest(new RequestError(ex.Message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get all resources that match the given filter.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
|
||||||
|
/// <param name="where">Filter the returned items.</param>
|
||||||
|
/// <param name="limit">How many items per page should be returned.</param>
|
||||||
|
/// <param name="afterID">Where the pagination should start.</param>
|
||||||
|
/// <returns>A list of resources that match every filters.</returns>
|
||||||
|
/// <response code="400">Invalid filters or sort information.</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[PartialPermission(Kind.Read)]
|
[PartialPermission(Kind.Read)]
|
||||||
public virtual async Task<ActionResult<Page<T>>> GetAll([FromQuery] string sortBy,
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[FromQuery] int afterID,
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
public async Task<ActionResult<Page<T>>> GetAll(
|
||||||
|
[FromQuery] string sortBy,
|
||||||
[FromQuery] Dictionary<string, string> where,
|
[FromQuery] Dictionary<string, string> where,
|
||||||
[FromQuery] int limit = 20)
|
[FromQuery] int limit = 20,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ICollection<T> resources = await _repository.GetAll(ApiHelper.ParseWhere<T>(where),
|
ICollection<T> resources = await Repository.GetAll(
|
||||||
|
ApiHelper.ParseWhere<T>(where),
|
||||||
new Sort<T>(sortBy),
|
new Sort<T>(sortBy),
|
||||||
new Pagination(limit, afterID));
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
return Page(resources, limit);
|
return Page(resources, limit);
|
||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
catch (ArgumentException ex)
|
||||||
{
|
{
|
||||||
return BadRequest(new { Error = ex.Message });
|
return BadRequest(new RequestError(ex.Message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
/// <summary>
|
||||||
where TResult : IResource
|
/// Create new
|
||||||
{
|
/// </summary>
|
||||||
return new Page<TResult>(resources,
|
/// <remarks>
|
||||||
new Uri(BaseURL, Request.Path),
|
/// Create a new item and store it. You may leave the ID unspecified, it will be filed by Kyoo.
|
||||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
/// </remarks>
|
||||||
limit);
|
/// <param name="resource">The resource to create.</param>
|
||||||
}
|
/// <returns>The created resource.</returns>
|
||||||
|
/// <response code="400">The resource in the request body is invalid.</response>
|
||||||
|
/// <response code="409">This item already exists (maybe a duplicated slug).</response>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[PartialPermission(Kind.Create)]
|
[PartialPermission(Kind.Create)]
|
||||||
public virtual async Task<ActionResult<T>> Create([FromBody] T resource)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(ActionResult<>))]
|
||||||
|
public async Task<ActionResult<T>> Create([FromBody] T resource)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _repository.Create(resource);
|
return await Repository.Create(resource);
|
||||||
}
|
}
|
||||||
catch (ArgumentException ex)
|
catch (ArgumentException ex)
|
||||||
{
|
{
|
||||||
return BadRequest(new { Error = ex.Message });
|
return BadRequest(new RequestError(ex.Message));
|
||||||
}
|
}
|
||||||
catch (DuplicatedItemException)
|
catch (DuplicatedItemException)
|
||||||
{
|
{
|
||||||
T existing = await _repository.GetOrDefault(resource.Slug);
|
T existing = await Repository.GetOrDefault(resource.Slug);
|
||||||
return Conflict(existing);
|
return Conflict(existing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Edit
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Edit an item. If the ID is specified it will be used to identify the resource.
|
||||||
|
/// If not, the slug will be used to identify it.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="resource">The resource to edit.</param>
|
||||||
|
/// <param name="resetOld">
|
||||||
|
/// Should old properties of the resource be discarded or should null values considered as not changed?
|
||||||
|
/// </param>
|
||||||
|
/// <returns>The created resource.</returns>
|
||||||
|
/// <response code="400">The resource in the request body is invalid.</response>
|
||||||
|
/// <response code="404">No item found with the specified ID (or slug).</response>
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
[PartialPermission(Kind.Write)]
|
[PartialPermission(Kind.Write)]
|
||||||
public virtual async Task<ActionResult<T>> Edit([FromQuery] bool resetOld, [FromBody] T resource)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<T>> Edit([FromBody] T resource, [FromQuery] bool resetOld = true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (resource.ID > 0)
|
if (resource.ID > 0)
|
||||||
return await _repository.Edit(resource, resetOld);
|
return await Repository.Edit(resource, resetOld);
|
||||||
|
|
||||||
T old = await _repository.Get(resource.Slug);
|
T old = await Repository.Get(resource.Slug);
|
||||||
resource.ID = old.ID;
|
resource.ID = old.ID;
|
||||||
return await _repository.Edit(resource, resetOld);
|
return await Repository.Edit(resource, resetOld);
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
@ -145,44 +209,27 @@ namespace Kyoo.Core.Api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id:int}")]
|
/// <summary>
|
||||||
[PartialPermission(Kind.Write)]
|
/// Delete an item
|
||||||
public virtual async Task<ActionResult<T>> Edit(int id, [FromQuery] bool resetOld, [FromBody] T resource)
|
/// </summary>
|
||||||
{
|
/// <remarks>
|
||||||
resource.ID = id;
|
/// Delete one item via it's ID or it's slug.
|
||||||
try
|
/// </remarks>
|
||||||
{
|
/// <param name="identifier">The ID or slug of the resource to delete.</param>
|
||||||
return await _repository.Edit(resource, resetOld);
|
/// <returns>The item has successfully been deleted.</returns>
|
||||||
}
|
/// <response code="404">No item could be found with the given id or slug.</response>
|
||||||
catch (ItemNotFoundException)
|
[HttpDelete("{identifier:id}")]
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("{slug}")]
|
|
||||||
[PartialPermission(Kind.Write)]
|
|
||||||
public virtual async Task<ActionResult<T>> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
T old = await _repository.Get(slug);
|
|
||||||
resource.ID = old.ID;
|
|
||||||
return await _repository.Edit(resource, resetOld);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{id:int}")]
|
|
||||||
[PartialPermission(Kind.Delete)]
|
[PartialPermission(Kind.Delete)]
|
||||||
public virtual async Task<IActionResult> Delete(int id)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Delete(Identifier identifier)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _repository.Delete(id);
|
await identifier.Match(
|
||||||
|
id => Repository.Delete(id),
|
||||||
|
slug => Repository.Delete(slug)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
@ -192,32 +239,28 @@ namespace Kyoo.Core.Api
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{slug}")]
|
/// <summary>
|
||||||
|
/// Delete all where
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Delete all items matching the given filters. If no filter is specified, delete all items.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="where">The list of filters.</param>
|
||||||
|
/// <returns>The item(s) has successfully been deleted.</returns>
|
||||||
|
/// <response code="400">One or multiple filters are invalid.</response>
|
||||||
|
[HttpDelete]
|
||||||
[PartialPermission(Kind.Delete)]
|
[PartialPermission(Kind.Delete)]
|
||||||
public virtual async Task<IActionResult> Delete(string slug)
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
public async Task<IActionResult> Delete([FromQuery] Dictionary<string, string> where)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _repository.Delete(slug);
|
await Repository.DeleteAll(ApiHelper.ParseWhere<T>(where));
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ArgumentException ex)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return BadRequest(new RequestError(ex.Message));
|
||||||
}
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
[PartialPermission(Kind.Delete)]
|
|
||||||
public virtual async Task<IActionResult> Delete(Dictionary<string, string> where)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _repository.DeleteAll(ApiHelper.ParseWhere<T>(where));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
|
157
src/Kyoo.Core/Views/Helper/CrudThumbsApi.cs
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A base class to handle CRUD operations and services thumbnails for
|
||||||
|
/// a specific resource type <typeparamref name="T"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of resource to make CRUD and thumbnails apis for.</typeparam>
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
public class CrudThumbsApi<T> : CrudApi<T>
|
||||||
|
where T : class, IResource, IThumbnails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The file manager used to send images.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IFileSystem _files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The thumbnail manager used to retrieve images paths.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IThumbnailsManager _thumbs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="CrudThumbsApi{T}"/> that handles crud requests and thumbnails.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repository">
|
||||||
|
/// The repository to use as a baking store for the type <typeparamref name="T"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images.</param>
|
||||||
|
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
public CrudThumbsApi(IRepository<T> repository,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbs)
|
||||||
|
: base(repository)
|
||||||
|
{
|
||||||
|
_files = files;
|
||||||
|
_thumbs = thumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get image
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get an image for the specified item.
|
||||||
|
/// List of commonly available images:<br/>
|
||||||
|
/// - Poster: Image 0, also available at /poster<br/>
|
||||||
|
/// - Thumbnail: Image 1, also available at /thumbnail<br/>
|
||||||
|
/// - Logo: Image 3, also available at /logo<br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Other images can be arbitrarily added by plugins so any image number can be specified from this endpoint.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||||
|
/// <param name="image">The number of the image to retrieve.</param>
|
||||||
|
/// <returns>The image asked.</returns>
|
||||||
|
/// <response code="404">No item exist with the specific identifier or the image does not exists on kyoo.</response>
|
||||||
|
[HttpGet("{identifier:id}/image-{image:int}")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetImage(Identifier identifier, int image)
|
||||||
|
{
|
||||||
|
T resource = await identifier.Match(
|
||||||
|
id => Repository.GetOrDefault(id),
|
||||||
|
slug => Repository.GetOrDefault(slug)
|
||||||
|
);
|
||||||
|
if (resource == null)
|
||||||
|
return NotFound();
|
||||||
|
string path = await _thumbs.GetImagePath(resource, image);
|
||||||
|
return _files.FileResult(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Poster
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the poster for the specified item.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||||
|
/// <returns>The image asked.</returns>
|
||||||
|
/// <response code="404">
|
||||||
|
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||||
|
/// </response>
|
||||||
|
[HttpGet("{identifier:id}/poster", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public Task<IActionResult> GetPoster(Identifier identifier)
|
||||||
|
{
|
||||||
|
return GetImage(identifier, Images.Poster);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Logo
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the logo for the specified item.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||||
|
/// <returns>The image asked.</returns>
|
||||||
|
/// <response code="404">
|
||||||
|
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||||
|
/// </response>
|
||||||
|
[HttpGet("{identifier:id}/logo", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public Task<IActionResult> GetLogo(Identifier identifier)
|
||||||
|
{
|
||||||
|
return GetImage(identifier, Images.Logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get Thumbnail
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the thumbnail for the specified item.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the resource to get the image for.</param>
|
||||||
|
/// <returns>The image asked.</returns>
|
||||||
|
/// <response code="404">
|
||||||
|
/// No item exist with the specific identifier or the image does not exists on kyoo.
|
||||||
|
/// </response>
|
||||||
|
[HttpGet("{identifier:id}/backdrop", Order = AlternativeRoute)]
|
||||||
|
[HttpGet("{identifier:id}/thumbnail", Order = AlternativeRoute)]
|
||||||
|
public Task<IActionResult> GetBackdrop(Identifier identifier)
|
||||||
|
{
|
||||||
|
return GetImage(identifier, Images.Thumbnail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ using System.Threading.Tasks;
|
|||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Utils;
|
using Kyoo.Utils;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
@ -32,8 +33,13 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An attribute to put on most controllers. It handle fields loading (only retuning fields requested and if they
|
||||||
|
/// are requested, load them) and help for the <c>where</c> query parameter.
|
||||||
|
/// </summary>
|
||||||
public class ResourceViewAttribute : ActionFilterAttribute
|
public class ResourceViewAttribute : ActionFilterAttribute
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public override void OnActionExecuting(ActionExecutingContext context)
|
public override void OnActionExecuting(ActionExecutingContext context)
|
||||||
{
|
{
|
||||||
if (context.ActionArguments.TryGetValue("where", out object dic) && dic is Dictionary<string, string> where)
|
if (context.ActionArguments.TryGetValue("where", out object dic) && dic is Dictionary<string, string> where)
|
||||||
@ -59,13 +65,13 @@ namespace Kyoo.Core.Api
|
|||||||
type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type;
|
type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type;
|
||||||
type = Utility.GetGenericDefinition(type, typeof(Page<>))?.GetGenericArguments()[0] ?? type;
|
type = Utility.GetGenericDefinition(type, typeof(Page<>))?.GetGenericArguments()[0] ?? type;
|
||||||
|
|
||||||
|
context.HttpContext.Items["ResourceType"] = type.Name;
|
||||||
|
|
||||||
PropertyInfo[] properties = type.GetProperties()
|
PropertyInfo[] properties = type.GetProperties()
|
||||||
.Where(x => x.GetCustomAttribute<LoadableRelationAttribute>() != null)
|
.Where(x => x.GetCustomAttribute<LoadableRelationAttribute>() != null)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
if (fields.Count == 1 && fields.Contains("all"))
|
if (fields.Count == 1 && fields.Contains("all"))
|
||||||
{
|
|
||||||
fields = properties.Select(x => x.Name).ToList();
|
fields = properties.Select(x => x.Name).ToList();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fields = fields
|
fields = fields
|
||||||
@ -77,10 +83,9 @@ namespace Kyoo.Core.Api
|
|||||||
?.Name;
|
?.Name;
|
||||||
if (property != null)
|
if (property != null)
|
||||||
return property;
|
return property;
|
||||||
context.Result = new BadRequestObjectResult(new
|
context.Result = new BadRequestObjectResult(
|
||||||
{
|
new RequestError($"{x} does not exist on {type.Name}.")
|
||||||
Error = $"{x} does not exist on {type.Name}."
|
);
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -92,6 +97,7 @@ namespace Kyoo.Core.Api
|
|||||||
base.OnActionExecuting(context);
|
base.OnActionExecuting(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
|
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
|
||||||
{
|
{
|
||||||
if (context.Result is ObjectResult result)
|
if (context.Result is ObjectResult result)
|
||||||
@ -104,7 +110,7 @@ namespace Kyoo.Core.Api
|
|||||||
if (result.DeclaredType == null)
|
if (result.DeclaredType == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ILibraryManager library = context.HttpContext.RequestServices.GetService<ILibraryManager>();
|
ILibraryManager library = context.HttpContext.RequestServices.GetRequiredService<ILibraryManager>();
|
||||||
ICollection<string> fields = (ICollection<string>)context.HttpContext.Items["fields"];
|
ICollection<string> fields = (ICollection<string>)context.HttpContext.Items["fields"];
|
||||||
Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>));
|
Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>));
|
||||||
|
|
||||||
@ -113,13 +119,13 @@ namespace Kyoo.Core.Api
|
|||||||
foreach (IResource resource in ((dynamic)result.Value).Items)
|
foreach (IResource resource in ((dynamic)result.Value).Items)
|
||||||
{
|
{
|
||||||
foreach (string field in fields!)
|
foreach (string field in fields!)
|
||||||
await library!.Load(resource, field);
|
await library.Load(resource, field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (result.DeclaredType.IsAssignableTo(typeof(IResource)))
|
else if (result.DeclaredType.IsAssignableTo(typeof(IResource)))
|
||||||
{
|
{
|
||||||
foreach (string field in fields!)
|
foreach (string field in fields!)
|
||||||
await library!.Load((IResource)result.Value, field);
|
await library.Load((IResource)result.Value, field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
src/Kyoo.Core/Views/Helper/Serializers/JsonOptions.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using Kyoo.Core.Models.Options;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The custom options of newtonsoft json. This simply add the <see cref="PeopleRoleConverter"/> and set
|
||||||
|
/// the <see cref="JsonSerializerContract"/>. It is on a separate class to use dependency injection.
|
||||||
|
/// </summary>
|
||||||
|
public class JsonOptions : IConfigureOptions<MvcNewtonsoftJsonOptions>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The http context accessor given to the <see cref="JsonSerializerContract"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The options containing the public URL of kyoo, given to <see cref="JsonSerializerContract"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IOptions<BasicOptions> _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="JsonOptions"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContextAccessor">
|
||||||
|
/// The http context accessor given to the <see cref="JsonSerializerContract"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// The options containing the public URL of kyoo, given to <see cref="JsonSerializerContract"/>.
|
||||||
|
/// </param>
|
||||||
|
public JsonOptions(IHttpContextAccessor httpContextAccessor, IOptions<BasicOptions> options)
|
||||||
|
{
|
||||||
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Configure(MvcNewtonsoftJsonOptions options)
|
||||||
|
{
|
||||||
|
options.SerializerSettings.ContractResolver = new JsonSerializerContract(_httpContextAccessor, _options);
|
||||||
|
options.SerializerSettings.Converters.Add(new PeopleRoleConverter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,89 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Reflection;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Attributes;
|
|
||||||
using Kyoo.Utils;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver
|
|
||||||
{
|
|
||||||
private int _depth = -1;
|
|
||||||
private string _host;
|
|
||||||
|
|
||||||
public JsonPropertyIgnorer(string host)
|
|
||||||
{
|
|
||||||
_host = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
|
||||||
{
|
|
||||||
JsonProperty property = base.CreateProperty(member, memberSerialization);
|
|
||||||
|
|
||||||
LoadableRelationAttribute relation = member.GetCustomAttribute<LoadableRelationAttribute>();
|
|
||||||
if (relation != null)
|
|
||||||
{
|
|
||||||
if (relation.RelationID == null)
|
|
||||||
property.ShouldSerialize = x => _depth == 0 && member.GetValue(x) != null;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
property.ShouldSerialize = x =>
|
|
||||||
{
|
|
||||||
if (_depth != 0)
|
|
||||||
return false;
|
|
||||||
if (member.GetValue(x) != null)
|
|
||||||
return true;
|
|
||||||
return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (member.GetCustomAttribute<SerializeIgnoreAttribute>() != null)
|
|
||||||
property.ShouldSerialize = _ => false;
|
|
||||||
if (member.GetCustomAttribute<DeserializeIgnoreAttribute>() != null)
|
|
||||||
property.ShouldDeserialize = _ => false;
|
|
||||||
|
|
||||||
// TODO use http context to disable serialize as.
|
|
||||||
// TODO check https://stackoverflow.com/questions/53288633/net-core-api-custom-json-resolver-based-on-request-values
|
|
||||||
SerializeAsAttribute serializeAs = member.GetCustomAttribute<SerializeAsAttribute>();
|
|
||||||
if (serializeAs != null)
|
|
||||||
property.ValueProvider = new SerializeAsProvider(serializeAs.Format, _host);
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override JsonContract CreateContract(Type objectType)
|
|
||||||
{
|
|
||||||
JsonContract contract = base.CreateContract(objectType);
|
|
||||||
if (Utility.GetGenericDefinition(objectType, typeof(Page<>)) == null
|
|
||||||
&& !objectType.IsAssignableTo(typeof(IEnumerable))
|
|
||||||
&& objectType.Name != "AnnotatedProblemDetails")
|
|
||||||
{
|
|
||||||
contract.OnSerializingCallbacks.Add((_, _) => _depth++);
|
|
||||||
contract.OnSerializedCallbacks.Add((_, _) => _depth--);
|
|
||||||
}
|
|
||||||
|
|
||||||
return contract;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
168
src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Reflection;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Core.Models.Options;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A custom json serializer that respects <see cref="SerializeIgnoreAttribute"/> and
|
||||||
|
/// <see cref="DeserializeIgnoreAttribute"/>. It also handle <see cref="LoadableRelationAttribute"/> via the
|
||||||
|
/// <c>fields</c> query parameter and <see cref="IThumbnails"/> items.
|
||||||
|
/// </summary>
|
||||||
|
public class JsonSerializerContract : CamelCasePropertyNamesContractResolver
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The http context accessor used to retrieve the <c>fields</c> query parameter as well as the type of
|
||||||
|
/// resource currently serializing.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The options containing the public URL of kyoo.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IOptions<BasicOptions> _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="JsonSerializerContract"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContextAccessor">The http context accessor to use.</param>
|
||||||
|
/// <param name="options">The options containing the public URL of kyoo.</param>
|
||||||
|
public JsonSerializerContract(IHttpContextAccessor httpContextAccessor, IOptions<BasicOptions> options)
|
||||||
|
{
|
||||||
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
|
||||||
|
{
|
||||||
|
JsonProperty property = base.CreateProperty(member, memberSerialization);
|
||||||
|
|
||||||
|
LoadableRelationAttribute relation = member.GetCustomAttribute<LoadableRelationAttribute>();
|
||||||
|
if (relation != null)
|
||||||
|
{
|
||||||
|
property.ShouldSerialize = _ =>
|
||||||
|
{
|
||||||
|
string resType = (string)_httpContextAccessor.HttpContext!.Items["ResourceType"];
|
||||||
|
if (member.DeclaringType!.Name != resType)
|
||||||
|
return false;
|
||||||
|
ICollection<string> fields = (ICollection<string>)_httpContextAccessor.HttpContext!.Items["fields"];
|
||||||
|
return fields!.Contains(member.Name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.GetCustomAttribute<SerializeIgnoreAttribute>() != null)
|
||||||
|
property.ShouldSerialize = _ => false;
|
||||||
|
if (member.GetCustomAttribute<DeserializeIgnoreAttribute>() != null)
|
||||||
|
property.ShouldDeserialize = _ => false;
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||||
|
{
|
||||||
|
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
|
||||||
|
if (!type.IsAssignableTo(typeof(IThumbnails)))
|
||||||
|
return properties;
|
||||||
|
foreach ((int id, string image) in Images.ImageName)
|
||||||
|
{
|
||||||
|
properties.Add(new JsonProperty
|
||||||
|
{
|
||||||
|
DeclaringType = type,
|
||||||
|
PropertyName = image.ToLower(),
|
||||||
|
UnderlyingName = image,
|
||||||
|
PropertyType = typeof(string),
|
||||||
|
Readable = true,
|
||||||
|
Writable = false,
|
||||||
|
ItemIsReference = false,
|
||||||
|
TypeNameHandling = TypeNameHandling.None,
|
||||||
|
ShouldSerialize = x =>
|
||||||
|
{
|
||||||
|
IThumbnails thumb = (IThumbnails)x;
|
||||||
|
return thumb?.Images?.ContainsKey(id) == true;
|
||||||
|
},
|
||||||
|
ValueProvider = new ThumbnailProvider(_options.Value.PublicUrl, id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A custom <see cref="IValueProvider"/> that uses the
|
||||||
|
/// <see cref="IThumbnails"/>.<see cref="IThumbnails.Images"/> as a value.
|
||||||
|
/// </summary>
|
||||||
|
private class ThumbnailProvider : IValueProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The public address of kyoo.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Uri _host;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index/ID of the image to retrieve/set.
|
||||||
|
/// </summary>
|
||||||
|
private readonly int _imageIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ThumbnailProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="host">The public address of kyoo.</param>
|
||||||
|
/// <param name="imageIndex">The index/ID of the image to retrieve/set.</param>
|
||||||
|
public ThumbnailProvider(Uri host, int imageIndex)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
_imageIndex = imageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SetValue(object target, object value)
|
||||||
|
{
|
||||||
|
if (target is not IThumbnails thumb)
|
||||||
|
throw new ArgumentException($"The given object is not an Thumbnail.");
|
||||||
|
thumb.Images[_imageIndex] = value as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object GetValue(object target)
|
||||||
|
{
|
||||||
|
string slug = (target as IResource)?.Slug ?? (target as ICustomTypeDescriptor)?.GetComponentName();
|
||||||
|
if (target is not IThumbnails thumb
|
||||||
|
|| slug == null
|
||||||
|
|| string.IsNullOrEmpty(thumb.Images?.GetValueOrDefault(_imageIndex)))
|
||||||
|
return null;
|
||||||
|
string type = target is ICustomTypeDescriptor descriptor
|
||||||
|
? descriptor.GetClassName()
|
||||||
|
: target.GetType().Name;
|
||||||
|
return new Uri(_host, $"/api/{type}/{slug}/{Images.ImageName[_imageIndex]}".ToLower())
|
||||||
|
.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,8 +24,13 @@ using Newtonsoft.Json.Linq;
|
|||||||
|
|
||||||
namespace Kyoo.Core.Api
|
namespace Kyoo.Core.Api
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A custom role's convertor to inline the person or the show depending on the value of
|
||||||
|
/// <see cref="PeopleRole.ForPeople"/>.
|
||||||
|
/// </summary>
|
||||||
public class PeopleRoleConverter : JsonConverter<PeopleRole>
|
public class PeopleRoleConverter : JsonConverter<PeopleRole>
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
ICollection<PeopleRole> oldPeople = value.Show?.People;
|
ICollection<PeopleRole> oldPeople = value.Show?.People;
|
||||||
@ -46,6 +51,7 @@ namespace Kyoo.Core.Api
|
|||||||
value.People.Roles = oldRoles;
|
value.People.Roles = oldRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public override PeopleRole ReadJson(JsonReader reader,
|
public override PeopleRole ReadJson(JsonReader reader,
|
||||||
Type objectType,
|
Type objectType,
|
||||||
PeopleRole existingValue,
|
PeopleRole existingValue,
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Newtonsoft.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
public class SerializeAsProvider : IValueProvider
|
|
||||||
{
|
|
||||||
private string _format;
|
|
||||||
private string _host;
|
|
||||||
|
|
||||||
public SerializeAsProvider(string format, string host)
|
|
||||||
{
|
|
||||||
_format = format;
|
|
||||||
_host = host.TrimEnd('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
public object GetValue(object target)
|
|
||||||
{
|
|
||||||
return Regex.Replace(_format, @"(?<!{){(\w+)(:(\w+))?}", x =>
|
|
||||||
{
|
|
||||||
string value = x.Groups[1].Value;
|
|
||||||
string modifier = x.Groups[3].Value;
|
|
||||||
|
|
||||||
if (value == "HOST")
|
|
||||||
return _host;
|
|
||||||
|
|
||||||
PropertyInfo properties = target.GetType()
|
|
||||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
|
||||||
.FirstOrDefault(y => y.Name == value);
|
|
||||||
if (properties == null)
|
|
||||||
return null;
|
|
||||||
object objValue = properties.GetValue(target);
|
|
||||||
if (objValue is not string ret)
|
|
||||||
ret = objValue?.ToString();
|
|
||||||
if (ret == null)
|
|
||||||
throw new ArgumentException($"Invalid serializer replacement {value}");
|
|
||||||
|
|
||||||
foreach (char modification in modifier)
|
|
||||||
{
|
|
||||||
ret = modification switch
|
|
||||||
{
|
|
||||||
'l' => ret.ToLowerInvariant(),
|
|
||||||
'u' => ret.ToUpperInvariant(),
|
|
||||||
_ => throw new ArgumentException($"Invalid serializer modificator {modification}.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValue(object target, object value)
|
|
||||||
{
|
|
||||||
// Values are ignored and should not be editable, except if the internal value is set.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/library")]
|
|
||||||
[Route("api/libraries")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(LibraryApi))]
|
|
||||||
public class LibraryApi : CrudApi<Library>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly ITaskManager _taskManager;
|
|
||||||
|
|
||||||
public LibraryApi(ILibraryManager libraryManager, ITaskManager taskManager, IOptions<BasicOptions> options)
|
|
||||||
: base(libraryManager.LibraryRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_taskManager = taskManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[PartialPermission(Kind.Create)]
|
|
||||||
public override async Task<ActionResult<Library>> Create(Library resource)
|
|
||||||
{
|
|
||||||
ActionResult<Library> result = await base.Create(resource);
|
|
||||||
if (result.Value != null)
|
|
||||||
{
|
|
||||||
_taskManager.StartTask("scan",
|
|
||||||
new Progress<float>(),
|
|
||||||
new Dictionary<string, object> { { "slug", result.Value.Slug } });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/show")]
|
|
||||||
[HttpGet("{id:int}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Libraries.Any(y => y.ID == id)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/show")]
|
|
||||||
[HttpGet("{slug}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Libraries.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/collection")]
|
|
||||||
[HttpGet("{id:int}/collections")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Collection>>> GetCollections(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Collection>(where, x => x.Libraries.Any(y => y.ID == id)),
|
|
||||||
new Sort<Collection>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/collection")]
|
|
||||||
[HttpGet("{slug}/collections")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Collection>>> GetCollections(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Collection>(where, x => x.Libraries.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Collection>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/item")]
|
|
||||||
[HttpGet("{id:int}/items")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<LibraryItem>>> GetItems(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<LibraryItem> resources = await _libraryManager.GetItemsFromLibrary(id,
|
|
||||||
ApiHelper.ParseWhere<LibraryItem>(where),
|
|
||||||
new Sort<LibraryItem>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/item")]
|
|
||||||
[HttpGet("{slug}/items")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<LibraryItem>>> GetItems(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<LibraryItem> resources = await _libraryManager.GetItemsFromLibrary(slug,
|
|
||||||
ApiHelper.ParseWhere<LibraryItem>(where),
|
|
||||||
new Sort<LibraryItem>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/item")]
|
|
||||||
[Route("api/items")]
|
|
||||||
[ApiController]
|
|
||||||
[ResourceView]
|
|
||||||
public class LibraryItemApi : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly ILibraryItemRepository _libraryItems;
|
|
||||||
private readonly Uri _baseURL;
|
|
||||||
|
|
||||||
public LibraryItemApi(ILibraryItemRepository libraryItems, IOptions<BasicOptions> options)
|
|
||||||
{
|
|
||||||
_libraryItems = libraryItems;
|
|
||||||
_baseURL = options.Value.PublicUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Permission(nameof(LibraryItemApi), Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<LibraryItem>>> GetAll([FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<LibraryItem> resources = await _libraryItems.GetAll(
|
|
||||||
ApiHelper.ParseWhere<LibraryItem>(where),
|
|
||||||
new Sort<LibraryItem>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
return new Page<LibraryItem>(resources,
|
|
||||||
new Uri(_baseURL, Request.Path),
|
|
||||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
|
||||||
limit);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
105
src/Kyoo.Core/Views/Metadata/GenreApi.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Genre"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/genres")]
|
||||||
|
[Route("api/genre", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[PartialPermission(nameof(Genre))]
|
||||||
|
[ApiDefinition("Genres", Group = MetadataGroup)]
|
||||||
|
public class GenreApi : CrudApi<Genre>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="GenreApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </param>
|
||||||
|
public GenreApi(ILibraryManager libraryManager)
|
||||||
|
: base(libraryManager.GenreRepository)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get shows with genre
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Lists the shows that have the selected genre.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Genre"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort shows by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of shows to return.</param>
|
||||||
|
/// <param name="afterID">An optional show's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of shows.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No genre with the given ID could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/shows")]
|
||||||
|
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 20,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)),
|
||||||
|
new Sort<Show>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Genre>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/Kyoo.Core/Views/Metadata/ProviderApi.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Provider"/>.
|
||||||
|
/// Providers are links to external websites or database.
|
||||||
|
/// They are mostly linked to plugins that provide metadata from those websites.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/providers")]
|
||||||
|
[Route("api/provider", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission(nameof(Provider))]
|
||||||
|
[ApiDefinition("Providers", Group = MetadataGroup)]
|
||||||
|
public class ProviderApi : CrudThumbsApi<Provider>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ProviderApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images and fonts.</param>
|
||||||
|
/// <param name="thumbnails">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
public ProviderApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbnails)
|
||||||
|
: base(libraryManager.ProviderRepository, files, thumbnails)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
116
src/Kyoo.Core/Views/Metadata/StaffApi.cs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple staff member.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/staff")]
|
||||||
|
[Route("api/people", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission(nameof(People))]
|
||||||
|
[ApiDefinition("Staff", Group = MetadataGroup)]
|
||||||
|
public class StaffApi : CrudThumbsApi<People>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="StaffApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images and fonts.</param>
|
||||||
|
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
public StaffApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbs)
|
||||||
|
: base(libraryManager.PeopleRepository, files, thumbs)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get roles
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the roles in witch this person has played, written or worked in a way.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the person.</param>
|
||||||
|
/// <param name="sortBy">A key to sort roles by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of roles to return.</param>
|
||||||
|
/// <param name="afterID">An optional role's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of roles.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No person with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/roles")]
|
||||||
|
[HttpGet("{identifier:id}/role", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<PeopleRole>>> GetRoles(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 20,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
|
||||||
|
Sort<PeopleRole> sort = new(sortBy);
|
||||||
|
Pagination pagination = new(limit, afterID);
|
||||||
|
|
||||||
|
ICollection<PeopleRole> resources = await identifier.Match(
|
||||||
|
id => _libraryManager.GetRolesFromPeople(id, whereQuery, sort, pagination),
|
||||||
|
slug => _libraryManager.GetRolesFromPeople(slug, whereQuery, sort, pagination)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
src/Kyoo.Core/Views/Metadata/StudioApi.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Studio"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/studios")]
|
||||||
|
[Route("api/studio", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[PartialPermission(nameof(Show))]
|
||||||
|
[ApiDefinition("Studios", Group = MetadataGroup)]
|
||||||
|
public class StudioApi : CrudApi<Studio>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="StudioApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </param>
|
||||||
|
public StudioApi(ILibraryManager libraryManager)
|
||||||
|
: base(libraryManager.StudioRepository)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get shows
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List shows that were made by this specific studio.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Studio"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort shows by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of shows to return.</param>
|
||||||
|
/// <param name="afterID">An optional show's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of shows.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No studio with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/shows")]
|
||||||
|
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 20,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioID, x => x.Studio.Slug)),
|
||||||
|
new Sort<Show>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Studio>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,126 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/people")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(PeopleApi))]
|
|
||||||
public class PeopleApi : CrudApi<People>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
private readonly IThumbnailsManager _thumbs;
|
|
||||||
|
|
||||||
public PeopleApi(ILibraryManager libraryManager,
|
|
||||||
IOptions<BasicOptions> options,
|
|
||||||
IFileSystem files,
|
|
||||||
IThumbnailsManager thumbs)
|
|
||||||
: base(libraryManager.PeopleRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_files = files;
|
|
||||||
_thumbs = thumbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/role")]
|
|
||||||
[HttpGet("{id:int}/roles")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<PeopleRole>>> GetRoles(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<PeopleRole> resources = await _libraryManager.GetRolesFromPeople(id,
|
|
||||||
ApiHelper.ParseWhere<PeopleRole>(where),
|
|
||||||
new Sort<PeopleRole>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/role")]
|
|
||||||
[HttpGet("{slug}/roles")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<PeopleRole>>> GetRoles(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<PeopleRole> resources = await _libraryManager.GetRolesFromPeople(slug,
|
|
||||||
ApiHelper.ParseWhere<PeopleRole>(where),
|
|
||||||
new Sort<PeopleRole>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/poster")]
|
|
||||||
public async Task<IActionResult> GetPeopleIcon(int id)
|
|
||||||
{
|
|
||||||
People people = await _libraryManager.GetOrDefault<People>(id);
|
|
||||||
if (people == null)
|
|
||||||
return NotFound();
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/poster")]
|
|
||||||
public async Task<IActionResult> GetPeopleIcon(string slug)
|
|
||||||
{
|
|
||||||
People people = await _libraryManager.GetOrDefault<People>(slug);
|
|
||||||
if (people == null)
|
|
||||||
return NotFound();
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/provider")]
|
|
||||||
[Route("api/providers")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(ProviderApi))]
|
|
||||||
public class ProviderApi : CrudApi<Provider>
|
|
||||||
{
|
|
||||||
private readonly IThumbnailsManager _thumbnails;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
|
|
||||||
public ProviderApi(ILibraryManager libraryManager,
|
|
||||||
IOptions<BasicOptions> options,
|
|
||||||
IFileSystem files,
|
|
||||||
IThumbnailsManager thumbnails)
|
|
||||||
: base(libraryManager.ProviderRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_files = files;
|
|
||||||
_thumbnails = thumbnails;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/logo")]
|
|
||||||
public async Task<IActionResult> GetLogo(int id)
|
|
||||||
{
|
|
||||||
Provider provider = await _libraryManager.GetOrDefault<Provider>(id);
|
|
||||||
if (provider == null)
|
|
||||||
return NotFound();
|
|
||||||
return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/logo")]
|
|
||||||
public async Task<IActionResult> GetLogo(string slug)
|
|
||||||
{
|
|
||||||
Provider provider = await _libraryManager.GetOrDefault<Provider>(slug);
|
|
||||||
if (provider == null)
|
|
||||||
return NotFound();
|
|
||||||
return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
153
src/Kyoo.Core/Views/Resources/CollectionApi.cs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Collection"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/collections")]
|
||||||
|
[Route("api/collection", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[PartialPermission(nameof(Collection))]
|
||||||
|
[ApiDefinition("Collections", Group = ResourcesGroup)]
|
||||||
|
public class CollectionApi : CrudThumbsApi<Collection>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="CollectionApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images.</param>
|
||||||
|
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
public CollectionApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbs)
|
||||||
|
: base(libraryManager.CollectionRepository, files, thumbs)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get shows in collection
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Lists the shows that are contained in the collection with the given id or slug.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort shows by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of shows to return.</param>
|
||||||
|
/// <param name="afterID">An optional show's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of shows.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No collection with the given ID could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/shows")]
|
||||||
|
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections)),
|
||||||
|
new Sort<Show>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Collection>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get libraries containing this collection
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Lists the libraries that contain the collection with the given id or slug.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort libraries by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of libraries to return.</param>
|
||||||
|
/// <param name="afterID">An optional library's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of libraries.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No collection with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/libraries")]
|
||||||
|
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Library> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Collection>(x => x.Collections)),
|
||||||
|
new Sort<Library>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Collection>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
src/Kyoo.Core/Views/Resources/EpisodeApi.cs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Episode"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/episodes")]
|
||||||
|
[Route("api/episode", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission(nameof(Episode))]
|
||||||
|
[ApiDefinition("Episodes", Group = ResourcesGroup)]
|
||||||
|
public class EpisodeApi : CrudThumbsApi<Episode>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="EpisodeApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images.</param>
|
||||||
|
/// <param name="thumbnails">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
public EpisodeApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbnails)
|
||||||
|
: base(libraryManager.EpisodeRepository, files, thumbnails)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get episode's show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the show that this episode is part of.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||||
|
/// <returns>The show that contains this episode.</returns>
|
||||||
|
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/show")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Show>> GetShow(Identifier identifier)
|
||||||
|
{
|
||||||
|
Show ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Show, Episode>(x => x.Episodes));
|
||||||
|
if (ret == null)
|
||||||
|
return NotFound();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get episode's season
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the season that this episode is part of.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||||
|
/// <returns>The season that contains this episode.</returns>
|
||||||
|
/// <response code="204">The episode is not part of a season.</response>
|
||||||
|
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/season")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Season>> GetSeason(Identifier identifier)
|
||||||
|
{
|
||||||
|
Season ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Season, Episode>(x => x.Episodes));
|
||||||
|
if (ret != null)
|
||||||
|
return ret;
|
||||||
|
Episode episode = await identifier.Match(
|
||||||
|
id => _libraryManager.GetOrDefault<Episode>(id),
|
||||||
|
slug => _libraryManager.GetOrDefault<Episode>(slug)
|
||||||
|
);
|
||||||
|
return episode == null
|
||||||
|
? NotFound()
|
||||||
|
: NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get tracks
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the tracks (video, audio and subtitles) available for this episode.
|
||||||
|
/// This endpoint provide the list of raw tracks, without transcode on it. To get a schema easier to watch
|
||||||
|
/// on a player, see the [/watch endpoint](#/watch).
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort tracks by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of tracks to return.</param>
|
||||||
|
/// <param name="afterID">An optional track's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of tracks.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||||
|
/// TODO fix the /watch endpoint link (when operations ID are specified).
|
||||||
|
[HttpGet("{identifier:id}/tracks")]
|
||||||
|
[HttpGet("{identifier:id}/track", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Track>>> GetEpisode(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Track> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.Matcher<Track>(x => x.EpisodeID, x => x.Episode.Slug)),
|
||||||
|
new Sort<Track>(sortBy),
|
||||||
|
new Pagination(limit, afterID));
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Episode>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
204
src/Kyoo.Core/Views/Resources/LibraryApi.cs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Library"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/libraries")]
|
||||||
|
[Route("api/library", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission(nameof(Library), Group = Group.Admin)]
|
||||||
|
[ApiDefinition("Library", Group = ResourcesGroup)]
|
||||||
|
public class LibraryApi : CrudApi<Library>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="EpisodeApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </param>
|
||||||
|
public LibraryApi(ILibraryManager libraryManager)
|
||||||
|
: base(libraryManager.LibraryRepository)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get shows
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the shows that are part of this library.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort shows by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of shows to return.</param>
|
||||||
|
/// <param name="afterID">An optional show's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of shows.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/shows")]
|
||||||
|
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 50,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Library>(x => x.Libraries)),
|
||||||
|
new Sort<Show>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get collections
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the collections that are part of this library.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort collections by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of collections to return.</param>
|
||||||
|
/// <param name="afterID">An optional collection's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of collections.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/collections")]
|
||||||
|
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 50,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Collection> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Library>(x => x.Libraries)),
|
||||||
|
new Sort<Collection>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get items
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List all items of this library.
|
||||||
|
/// An item can ether represent a collection or a show.
|
||||||
|
/// This endpoint allow one to retrieve all collections and shows that are not contained in a collection.
|
||||||
|
/// This is what is displayed on the /browse/library page of the webapp.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort items by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of items to return.</param>
|
||||||
|
/// <param name="afterID">An optional item's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of items.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/items")]
|
||||||
|
[HttpGet("{identifier:id}/item", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<LibraryItem>>> GetItems(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 50,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Expression<Func<LibraryItem, bool>> whereQuery = ApiHelper.ParseWhere<LibraryItem>(where);
|
||||||
|
Sort<LibraryItem> sort = new(sortBy);
|
||||||
|
Pagination pagination = new(limit, afterID);
|
||||||
|
|
||||||
|
ICollection<LibraryItem> resources = await identifier.Match(
|
||||||
|
id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination),
|
||||||
|
slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/Kyoo.Core/Views/Resources/LibraryItemApi.cs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Endpoint for items that are not part of a specific library.
|
||||||
|
/// An item can ether represent a collection or a show.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/items")]
|
||||||
|
[Route("api/item", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission(nameof(LibraryItem))]
|
||||||
|
[ApiDefinition("Items", Group = ResourcesGroup)]
|
||||||
|
public class LibraryItemApi : BaseApi
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library item repository used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryItemRepository _libraryItems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="LibraryItemApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryItems">
|
||||||
|
/// The library item repository used to modify or retrieve information in the data store.
|
||||||
|
/// </param>
|
||||||
|
public LibraryItemApi(ILibraryItemRepository libraryItems)
|
||||||
|
{
|
||||||
|
_libraryItems = libraryItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get items
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List all items of kyoo.
|
||||||
|
/// An item can ether represent a collection or a show.
|
||||||
|
/// This endpoint allow one to retrieve all collections and shows that are not contained in a collection.
|
||||||
|
/// This is what is displayed on the /browse page of the webapp.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="sortBy">A key to sort items by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of items to return.</param>
|
||||||
|
/// <param name="afterID">An optional item's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of items.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No library with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<LibraryItem>>> GetAll(
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 50,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<LibraryItem> resources = await _libraryItems.GetAll(
|
||||||
|
ApiHelper.ParseWhere<LibraryItem>(where),
|
||||||
|
new Sort<LibraryItem>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
src/Kyoo.Core/Views/Resources/SearchApi.cs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An endpoint to search for every resources of kyoo. Searching for only a specific type of resource
|
||||||
|
/// is available on the said endpoint.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/search/{query}")]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[ApiDefinition("Search", Group = ResourcesGroup)]
|
||||||
|
public class SearchApi : ControllerBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="SearchApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager used to interact with the data store.</param>
|
||||||
|
public SearchApi(ILibraryManager libraryManager)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Global search
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for collections, shows, episodes, staff, genre and studios at the same time
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of every resources found for the specified query.</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Permission(nameof(Collection), Kind.Read)]
|
||||||
|
[Permission(nameof(Show), Kind.Read)]
|
||||||
|
[Permission(nameof(Episode), Kind.Read)]
|
||||||
|
[Permission(nameof(People), Kind.Read)]
|
||||||
|
[Permission(nameof(Genre), Kind.Read)]
|
||||||
|
[Permission(nameof(Studio), Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult<SearchResult>> Search(string query)
|
||||||
|
{
|
||||||
|
return new SearchResult
|
||||||
|
{
|
||||||
|
Query = query,
|
||||||
|
Collections = await _libraryManager.Search<Collection>(query),
|
||||||
|
Shows = await _libraryManager.Search<Show>(query),
|
||||||
|
Episodes = await _libraryManager.Search<Episode>(query),
|
||||||
|
People = await _libraryManager.Search<People>(query),
|
||||||
|
Genres = await _libraryManager.Search<Genre>(query),
|
||||||
|
Studios = await _libraryManager.Search<Studio>(query)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search collections
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for collections
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of collections found for the specified query.</returns>
|
||||||
|
[HttpGet("collections")]
|
||||||
|
[HttpGet("collection", Order = AlternativeRoute)]
|
||||||
|
[Permission(nameof(Collection), Kind.Read)]
|
||||||
|
[ApiDefinition("Collections")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public Task<ICollection<Collection>> SearchCollections(string query)
|
||||||
|
{
|
||||||
|
return _libraryManager.Search<Collection>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search shows
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for shows
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of shows found for the specified query.</returns>
|
||||||
|
[HttpGet("shows")]
|
||||||
|
[HttpGet("show", Order = AlternativeRoute)]
|
||||||
|
[Permission(nameof(Show), Kind.Read)]
|
||||||
|
[ApiDefinition("Shows")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public Task<ICollection<Show>> SearchShows(string query)
|
||||||
|
{
|
||||||
|
return _libraryManager.Search<Show>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search episodes
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for episodes
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of episodes found for the specified query.</returns>
|
||||||
|
[HttpGet("episodes")]
|
||||||
|
[HttpGet("episode", Order = AlternativeRoute)]
|
||||||
|
[Permission(nameof(Episode), Kind.Read)]
|
||||||
|
[ApiDefinition("Episodes")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public Task<ICollection<Episode>> SearchEpisodes(string query)
|
||||||
|
{
|
||||||
|
return _libraryManager.Search<Episode>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search staff
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for staff
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of staff members found for the specified query.</returns>
|
||||||
|
[HttpGet("staff")]
|
||||||
|
[HttpGet("person", Order = AlternativeRoute)]
|
||||||
|
[HttpGet("people", Order = AlternativeRoute)]
|
||||||
|
[Permission(nameof(People), Kind.Read)]
|
||||||
|
[ApiDefinition("Staff")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public Task<ICollection<People>> SearchPeople(string query)
|
||||||
|
{
|
||||||
|
return _libraryManager.Search<People>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search genres
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for genres
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of genres found for the specified query.</returns>
|
||||||
|
[HttpGet("genres")]
|
||||||
|
[HttpGet("genre", Order = AlternativeRoute)]
|
||||||
|
[Permission(nameof(Genre), Kind.Read)]
|
||||||
|
[ApiDefinition("Genres")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public Task<ICollection<Genre>> SearchGenres(string query)
|
||||||
|
{
|
||||||
|
return _libraryManager.Search<Genre>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search studios
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Search for studios
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="query">The query to search for.</param>
|
||||||
|
/// <returns>A list of studios found for the specified query.</returns>
|
||||||
|
[HttpGet("studios")]
|
||||||
|
[HttpGet("studio", Order = AlternativeRoute)]
|
||||||
|
[Permission(nameof(Studio), Kind.Read)]
|
||||||
|
[ApiDefinition("Studios")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public Task<ICollection<Studio>> SearchStudios(string query)
|
||||||
|
{
|
||||||
|
return _libraryManager.Search<Studio>(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
src/Kyoo.Core/Views/Resources/SeasonApi.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Season"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/seasons")]
|
||||||
|
[Route("api/season", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[PartialPermission(nameof(Season))]
|
||||||
|
[ApiDefinition("Seasons", Group = ResourcesGroup)]
|
||||||
|
public class SeasonApi : CrudThumbsApi<Season>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="SeasonApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images.</param>
|
||||||
|
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
public SeasonApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbs)
|
||||||
|
: base(libraryManager.SeasonRepository, files, thumbs)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get episodes in the season
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the episodes that are part of the specified season.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Season"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort episodes by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of episodes to return.</param>
|
||||||
|
/// <param name="afterID">An optional episode's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of episodes.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No season with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/episodes")]
|
||||||
|
[HttpGet("{identifier:id}/episode", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Episode>>> GetEpisode(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Episode> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)),
|
||||||
|
new Sort<Episode>(sortBy),
|
||||||
|
new Pagination(limit, afterID));
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Season>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get season's show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the show that this season is part of.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Season"/>.</param>
|
||||||
|
/// <returns>The show that contains this season.</returns>
|
||||||
|
/// <response code="404">No season with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/show")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Show>> GetShow(Identifier identifier)
|
||||||
|
{
|
||||||
|
Show ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Show, Season>(x => x.Seasons));
|
||||||
|
if (ret == null)
|
||||||
|
return NotFound();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
439
src/Kyoo.Core/Views/Resources/ShowApi.cs
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Kyoo.Core.Models.Options;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Show"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/shows")]
|
||||||
|
[Route("api/show", Order = AlternativeRoute)]
|
||||||
|
[Route("api/movie", Order = AlternativeRoute)]
|
||||||
|
[Route("api/movies", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[PartialPermission(nameof(Show))]
|
||||||
|
[ApiDefinition("Shows", Group = ResourcesGroup)]
|
||||||
|
public class ShowApi : CrudThumbsApi<Show>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file manager used to send images and fonts.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IFileSystem _files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base URL of Kyoo. This will be used to create links for images and
|
||||||
|
/// <see cref="Abstractions.Models.Page{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Uri _baseURL;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ShowApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">The file manager used to send images and fonts.</param>
|
||||||
|
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||||
|
/// <param name="options">
|
||||||
|
/// Options used to retrieve the base URL of Kyoo.
|
||||||
|
/// </param>
|
||||||
|
public ShowApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbs,
|
||||||
|
IOptions<BasicOptions> options)
|
||||||
|
: base(libraryManager.ShowRepository, files, thumbs)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_files = files;
|
||||||
|
_baseURL = options.Value.PublicUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get seasons of this show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the seasons that are part of the specified show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort seasons by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of seasons to return.</param>
|
||||||
|
/// <param name="afterID">An optional season's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of seasons.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/seasons")]
|
||||||
|
[HttpGet("{identifier:id}/season", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Season>>> GetSeasons(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 20,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Season> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)),
|
||||||
|
new Sort<Season>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get episodes of this show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the episodes that are part of the specified show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort episodes by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of episodes to return.</param>
|
||||||
|
/// <param name="afterID">An optional episode's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of episodes.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/episodes")]
|
||||||
|
[HttpGet("{identifier:id}/episode", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Episode>>> GetEpisodes(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 50,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Episode> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowID, x => x.Show.Slug)),
|
||||||
|
new Sort<Episode>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get staff
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List staff members that made this show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort staff members by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of people to return.</param>
|
||||||
|
/// <param name="afterID">An optional person's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of people.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/staff")]
|
||||||
|
[HttpGet("{identifier:id}/people", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<PeopleRole>>> GetPeople(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
|
||||||
|
Sort<PeopleRole> sort = new(sortBy);
|
||||||
|
Pagination pagination = new(limit, afterID);
|
||||||
|
|
||||||
|
ICollection<PeopleRole> resources = await identifier.Match(
|
||||||
|
id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination),
|
||||||
|
slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination)
|
||||||
|
);
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get genres of this show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the genres that represent this show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort genres by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of genres to return.</param>
|
||||||
|
/// <param name="afterID">An optional genre's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of genres.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/genres")]
|
||||||
|
[HttpGet("{identifier:id}/genre", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Genre>>> GetGenres(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Genre> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Genre, Show>(x => x.Shows)),
|
||||||
|
new Sort<Genre>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get studio that made the show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the studio that made the show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <returns>The studio that made the show.</returns>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/studio")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Studio>> GetStudio(Identifier identifier)
|
||||||
|
{
|
||||||
|
Studio studio = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Studio, Show>(x => x.Shows));
|
||||||
|
if (studio == null)
|
||||||
|
return NotFound();
|
||||||
|
return studio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get libraries containing this show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the libraries that contain this show. If this show is contained in a collection that is contained in
|
||||||
|
/// a library, this library will be returned too.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort libraries by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of libraries to return.</param>
|
||||||
|
/// <param name="afterID">An optional library's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of libraries.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/libraries")]
|
||||||
|
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Library> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Show>(x => x.Shows)),
|
||||||
|
new Sort<Library>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get collections containing this show
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List the collections that contain this show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="sortBy">A key to sort collections by.</param>
|
||||||
|
/// <param name="where">An optional list of filters.</param>
|
||||||
|
/// <param name="limit">The number of collections to return.</param>
|
||||||
|
/// <param name="afterID">An optional collection's ID to start the query from this specific item.</param>
|
||||||
|
/// <returns>A page of collections.</returns>
|
||||||
|
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/collections")]
|
||||||
|
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
|
||||||
|
[FromQuery] string sortBy,
|
||||||
|
[FromQuery] Dictionary<string, string> where,
|
||||||
|
[FromQuery] int limit = 30,
|
||||||
|
[FromQuery] int? afterID = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<Collection> resources = await _libraryManager.GetAll(
|
||||||
|
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows)),
|
||||||
|
new Sort<Collection>(sortBy),
|
||||||
|
new Pagination(limit, afterID)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
|
||||||
|
return NotFound();
|
||||||
|
return Page(resources, limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestError(ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List fonts
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// List available fonts for this show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <returns>An object containing the name of the font followed by the url to retrieve it.</returns>
|
||||||
|
[HttpGet("{identifier:id}/fonts")]
|
||||||
|
[HttpGet("{identifier:id}/font", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Dictionary<string, string>>> GetFonts(Identifier identifier)
|
||||||
|
{
|
||||||
|
Show show = await identifier.Match(
|
||||||
|
id => _libraryManager.GetOrDefault<Show>(id),
|
||||||
|
slug => _libraryManager.GetOrDefault<Show>(slug)
|
||||||
|
);
|
||||||
|
if (show == null)
|
||||||
|
return NotFound();
|
||||||
|
string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments");
|
||||||
|
return (await _files.ListFiles(path))
|
||||||
|
.ToDictionary(
|
||||||
|
Path.GetFileNameWithoutExtension,
|
||||||
|
x => $"{_baseURL}api/shows/{identifier}/fonts/{Path.GetFileName(x)}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get font
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get a font file that is used in subtitles of this show.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
|
||||||
|
/// <param name="font">The name of the font to retrieve (with it's file extension).</param>
|
||||||
|
/// <returns>A page of collections.</returns>
|
||||||
|
/// <response code="400">The font name is invalid.</response>
|
||||||
|
/// <response code="404">No show with the given ID/slug could be found or the font does not exist.</response>
|
||||||
|
[HttpGet("{identifier:id}/fonts/{font}")]
|
||||||
|
[HttpGet("{identifier:id}/font/{font}", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetFont(Identifier identifier, string font)
|
||||||
|
{
|
||||||
|
if (font.Contains('/') || font.Contains('\\'))
|
||||||
|
return BadRequest(new RequestError("Invalid font name."));
|
||||||
|
Show show = await identifier.Match(
|
||||||
|
id => _libraryManager.GetOrDefault<Show>(id),
|
||||||
|
slug => _libraryManager.GetOrDefault<Show>(slug)
|
||||||
|
);
|
||||||
|
if (show == null)
|
||||||
|
return NotFound();
|
||||||
|
string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", font);
|
||||||
|
return _files.FileResult(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,107 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/search/{query}")]
|
|
||||||
[ApiController]
|
|
||||||
public class SearchApi : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
public SearchApi(ILibraryManager libraryManager)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Permission(nameof(Collection), Kind.Read)]
|
|
||||||
[Permission(nameof(Show), Kind.Read)]
|
|
||||||
[Permission(nameof(Episode), Kind.Read)]
|
|
||||||
[Permission(nameof(People), Kind.Read)]
|
|
||||||
[Permission(nameof(Genre), Kind.Read)]
|
|
||||||
[Permission(nameof(Studio), Kind.Read)]
|
|
||||||
public async Task<ActionResult<SearchResult>> Search(string query)
|
|
||||||
{
|
|
||||||
return new SearchResult
|
|
||||||
{
|
|
||||||
Query = query,
|
|
||||||
Collections = await _libraryManager.Search<Collection>(query),
|
|
||||||
Shows = await _libraryManager.Search<Show>(query),
|
|
||||||
Episodes = await _libraryManager.Search<Episode>(query),
|
|
||||||
People = await _libraryManager.Search<People>(query),
|
|
||||||
Genres = await _libraryManager.Search<Genre>(query),
|
|
||||||
Studios = await _libraryManager.Search<Studio>(query)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("collection")]
|
|
||||||
[HttpGet("collections")]
|
|
||||||
[Permission(nameof(Collection), Kind.Read)]
|
|
||||||
public Task<ICollection<Collection>> SearchCollections(string query)
|
|
||||||
{
|
|
||||||
return _libraryManager.Search<Collection>(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("show")]
|
|
||||||
[HttpGet("shows")]
|
|
||||||
[Permission(nameof(Show), Kind.Read)]
|
|
||||||
public Task<ICollection<Show>> SearchShows(string query)
|
|
||||||
{
|
|
||||||
return _libraryManager.Search<Show>(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("episode")]
|
|
||||||
[HttpGet("episodes")]
|
|
||||||
[Permission(nameof(Episode), Kind.Read)]
|
|
||||||
public Task<ICollection<Episode>> SearchEpisodes(string query)
|
|
||||||
{
|
|
||||||
return _libraryManager.Search<Episode>(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("people")]
|
|
||||||
[Permission(nameof(People), Kind.Read)]
|
|
||||||
public Task<ICollection<People>> SearchPeople(string query)
|
|
||||||
{
|
|
||||||
return _libraryManager.Search<People>(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("genre")]
|
|
||||||
[HttpGet("genres")]
|
|
||||||
[Permission(nameof(Genre), Kind.Read)]
|
|
||||||
public Task<ICollection<Genre>> SearchGenres(string query)
|
|
||||||
{
|
|
||||||
return _libraryManager.Search<Genre>(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("studio")]
|
|
||||||
[HttpGet("studios")]
|
|
||||||
[Permission(nameof(Studio), Kind.Read)]
|
|
||||||
public Task<ICollection<Studio>> SearchStudios(string query)
|
|
||||||
{
|
|
||||||
return _libraryManager.Search<Studio>(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/season")]
|
|
||||||
[Route("api/seasons")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(SeasonApi))]
|
|
||||||
public class SeasonApi : CrudApi<Season>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IThumbnailsManager _thumbs;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
|
|
||||||
public SeasonApi(ILibraryManager libraryManager,
|
|
||||||
IOptions<BasicOptions> options,
|
|
||||||
IThumbnailsManager thumbs,
|
|
||||||
IFileSystem files)
|
|
||||||
: base(libraryManager.SeasonRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_thumbs = thumbs;
|
|
||||||
_files = files;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{seasonID:int}/episode")]
|
|
||||||
[HttpGet("{seasonID:int}/episodes")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Episode>>> GetEpisode(int seasonID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Episode>(where, x => x.SeasonID == seasonID),
|
|
||||||
new Sort<Episode>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Season>(seasonID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showSlug}-s{seasonNumber:int}/episode")]
|
|
||||||
[HttpGet("{showSlug}-s{seasonNumber:int}/episodes")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Episode>>> GetEpisode(string showSlug,
|
|
||||||
int seasonNumber,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Episode>(where, x => x.Show.Slug == showSlug
|
|
||||||
&& x.SeasonNumber == seasonNumber),
|
|
||||||
new Sort<Episode>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault(showSlug, seasonNumber) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}-s{seasonNumber:int}/episode")]
|
|
||||||
[HttpGet("{showID:int}-s{seasonNumber:int}/episodes")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Episode>>> GetEpisode(int showID,
|
|
||||||
int seasonNumber,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Episode>(where, x => x.ShowID == showID && x.SeasonNumber == seasonNumber),
|
|
||||||
new Sort<Episode>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{seasonID:int}/show")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(int seasonID)
|
|
||||||
{
|
|
||||||
Show ret = await _libraryManager.GetOrDefault<Show>(x => x.Seasons.Any(y => y.ID == seasonID));
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showSlug}-s{seasonNumber:int}/show")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(string showSlug, int seasonNumber)
|
|
||||||
{
|
|
||||||
Show ret = await _libraryManager.GetOrDefault<Show>(showSlug);
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}-s{seasonNumber:int}/show")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber)
|
|
||||||
{
|
|
||||||
Show ret = await _libraryManager.GetOrDefault<Show>(showID);
|
|
||||||
if (ret == null)
|
|
||||||
return NotFound();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/poster")]
|
|
||||||
public async Task<IActionResult> GetPoster(int id)
|
|
||||||
{
|
|
||||||
Season season = await _libraryManager.GetOrDefault<Season>(id);
|
|
||||||
if (season == null)
|
|
||||||
return NotFound();
|
|
||||||
await _libraryManager.Load(season, x => x.Show);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/poster")]
|
|
||||||
public async Task<IActionResult> GetPoster(string slug)
|
|
||||||
{
|
|
||||||
Season season = await _libraryManager.GetOrDefault<Season>(slug);
|
|
||||||
if (season == null)
|
|
||||||
return NotFound();
|
|
||||||
await _libraryManager.Load(season, x => x.Show);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,474 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/show")]
|
|
||||||
[Route("api/shows")]
|
|
||||||
[Route("api/movie")]
|
|
||||||
[Route("api/movies")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(ShowApi))]
|
|
||||||
public class ShowApi : CrudApi<Show>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
private readonly IThumbnailsManager _thumbs;
|
|
||||||
|
|
||||||
public ShowApi(ILibraryManager libraryManager,
|
|
||||||
IFileSystem files,
|
|
||||||
IThumbnailsManager thumbs,
|
|
||||||
IOptions<BasicOptions> options)
|
|
||||||
: base(libraryManager.ShowRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_files = files;
|
|
||||||
_thumbs = thumbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/season")]
|
|
||||||
[HttpGet("{showID:int}/seasons")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Season>>> GetSeasons(int showID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Season> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Season>(where, x => x.ShowID == showID),
|
|
||||||
new Sort<Season>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/season")]
|
|
||||||
[HttpGet("{slug}/seasons")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Season>>> GetSeasons(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Season> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Season>(where, x => x.Show.Slug == slug),
|
|
||||||
new Sort<Season>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/episode")]
|
|
||||||
[HttpGet("{showID:int}/episodes")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Episode>>> GetEpisodes(int showID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Episode>(where, x => x.ShowID == showID),
|
|
||||||
new Sort<Episode>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/episode")]
|
|
||||||
[HttpGet("{slug}/episodes")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Episode>>> GetEpisodes(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 50)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Episode>(where, x => x.Show.Slug == slug),
|
|
||||||
new Sort<Episode>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/people")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<PeopleRole>>> GetPeople(int showID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<PeopleRole> resources = await _libraryManager.GetPeopleFromShow(showID,
|
|
||||||
ApiHelper.ParseWhere<PeopleRole>(where),
|
|
||||||
new Sort<PeopleRole>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/people")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<PeopleRole>>> GetPeople(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<PeopleRole> resources = await _libraryManager.GetPeopleFromShow(slug,
|
|
||||||
ApiHelper.ParseWhere<PeopleRole>(where),
|
|
||||||
new Sort<PeopleRole>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/genre")]
|
|
||||||
[HttpGet("{showID:int}/genres")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Genre>>> GetGenres(int showID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Genre> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Genre>(where, x => x.Shows.Any(y => y.ID == showID)),
|
|
||||||
new Sort<Genre>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/genre")]
|
|
||||||
[HttpGet("{slug}/genres")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Genre>>> GetGenre(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Genre> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Genre>(where, x => x.Shows.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Genre>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/studio")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Studio>> GetStudio(int showID)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _libraryManager.Get<Studio>(x => x.Shows.Any(y => y.ID == showID));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/studio")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Studio>> GetStudio(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _libraryManager.Get<Studio>(x => x.Shows.Any(y => y.Slug == slug));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/library")]
|
|
||||||
[HttpGet("{showID:int}/libraries")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Library>>> GetLibraries(int showID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Library> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Library>(where, x => x.Shows.Any(y => y.ID == showID)),
|
|
||||||
new Sort<Library>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/library")]
|
|
||||||
[HttpGet("{slug}/libraries")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Library>>> GetLibraries(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Library> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Library>(where, x => x.Shows.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Library>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showID:int}/collection")]
|
|
||||||
[HttpGet("{showID:int}/collections")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Collection>>> GetCollections(int showID,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Collection>(where, x => x.Shows.Any(y => y.ID == showID)),
|
|
||||||
new Sort<Collection>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/collection")]
|
|
||||||
[HttpGet("{slug}/collections")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Collection>>> GetCollections(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 30)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Collection>(where, x => x.Shows.Any(y => y.Slug == slug)),
|
|
||||||
new Sort<Collection>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/font")]
|
|
||||||
[HttpGet("{slug}/fonts")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Dictionary<string, string>>> GetFonts(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
|
||||||
string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments");
|
|
||||||
return (await _files.ListFiles(path))
|
|
||||||
.ToDictionary(Path.GetFileNameWithoutExtension,
|
|
||||||
x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}");
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{showSlug}/font/{slug}")]
|
|
||||||
[HttpGet("{showSlug}/fonts/{slug}")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<IActionResult> GetFont(string showSlug, string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Show show = await _libraryManager.Get<Show>(showSlug);
|
|
||||||
string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", slug);
|
|
||||||
return _files.FileResult(path);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/poster")]
|
|
||||||
public async Task<IActionResult> GetPoster(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(show, Images.Poster));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/logo")]
|
|
||||||
public async Task<IActionResult> GetLogo(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(show, Images.Logo));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/backdrop")]
|
|
||||||
[HttpGet("{slug}/thumbnail")]
|
|
||||||
public async Task<IActionResult> GetBackdrop(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
|
||||||
return _files.FileResult(await _thumbs.GetImagePath(show, Images.Thumbnail));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/studio")]
|
|
||||||
[Route("api/studios")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(ShowApi))]
|
|
||||||
public class StudioApi : CrudApi<Studio>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
public StudioApi(ILibraryManager libraryManager, IOptions<BasicOptions> options)
|
|
||||||
: base(libraryManager.StudioRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/show")]
|
|
||||||
[HttpGet("{id:int}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(int id,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.StudioID == id),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Studio>(id) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/show")]
|
|
||||||
[HttpGet("{slug}/shows")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows(string slug,
|
|
||||||
[FromQuery] string sortBy,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where,
|
|
||||||
[FromQuery] int limit = 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
|
||||||
ApiHelper.ParseWhere<Show>(where, x => x.Studio.Slug == slug),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
if (!resources.Any() && await _libraryManager.GetOrDefault<Studio>(slug) == null)
|
|
||||||
return NotFound();
|
|
||||||
return Page(resources, limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("subtitle")]
|
|
||||||
[ApiController]
|
|
||||||
public class SubtitleApi : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
|
|
||||||
public SubtitleApi(ILibraryManager libraryManager, IFileSystem files)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_files = files;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
|
||||||
[Permission(nameof(SubtitleApi), Kind.Read)]
|
|
||||||
public async Task<IActionResult> GetSubtitle(int id)
|
|
||||||
{
|
|
||||||
Track subtitle = await _libraryManager.GetOrDefault<Track>(id);
|
|
||||||
return subtitle != null
|
|
||||||
? _files.FileResult(subtitle.Path)
|
|
||||||
: NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}.{extension}")]
|
|
||||||
[Permission(nameof(SubtitleApi), Kind.Read)]
|
|
||||||
public async Task<IActionResult> GetSubtitle(int id, string extension)
|
|
||||||
{
|
|
||||||
Track subtitle = await _libraryManager.GetOrDefault<Track>(id);
|
|
||||||
if (subtitle == null)
|
|
||||||
return NotFound();
|
|
||||||
if (subtitle.Codec == "subrip" && extension == "vtt")
|
|
||||||
return new ConvertSubripToVtt(subtitle.Path, _files);
|
|
||||||
return _files.FileResult(subtitle.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}")]
|
|
||||||
[Permission(nameof(SubtitleApi), Kind.Read)]
|
|
||||||
public async Task<IActionResult> GetSubtitle(string slug)
|
|
||||||
{
|
|
||||||
string extension = null;
|
|
||||||
|
|
||||||
if (slug.Count(x => x == '.') == 3)
|
|
||||||
{
|
|
||||||
int idx = slug.LastIndexOf('.');
|
|
||||||
extension = slug[(idx + 1)..];
|
|
||||||
slug = slug[..idx];
|
|
||||||
}
|
|
||||||
|
|
||||||
Track subtitle = await _libraryManager.GetOrDefault<Track>(Track.BuildSlug(slug, StreamType.Subtitle));
|
|
||||||
if (subtitle == null)
|
|
||||||
return NotFound();
|
|
||||||
if (subtitle.Codec == "subrip" && extension == "vtt")
|
|
||||||
return new ConvertSubripToVtt(subtitle.Path, _files);
|
|
||||||
return _files.FileResult(subtitle.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ConvertSubripToVtt : IActionResult
|
|
||||||
{
|
|
||||||
private readonly string _path;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
|
|
||||||
public ConvertSubripToVtt(string subtitlePath, IFileSystem files)
|
|
||||||
{
|
|
||||||
_path = subtitlePath;
|
|
||||||
_files = files;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ExecuteResultAsync(ActionContext context)
|
|
||||||
{
|
|
||||||
List<string> lines = new();
|
|
||||||
|
|
||||||
context.HttpContext.Response.StatusCode = 200;
|
|
||||||
context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt");
|
|
||||||
|
|
||||||
await using (StreamWriter writer = new(context.HttpContext.Response.Body))
|
|
||||||
{
|
|
||||||
await writer.WriteLineAsync("WEBVTT");
|
|
||||||
await writer.WriteLineAsync(string.Empty);
|
|
||||||
await writer.WriteLineAsync(string.Empty);
|
|
||||||
|
|
||||||
using StreamReader reader = new(await _files.GetReader(_path));
|
|
||||||
string line;
|
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
|
||||||
{
|
|
||||||
if (line == string.Empty)
|
|
||||||
{
|
|
||||||
lines.Add(string.Empty);
|
|
||||||
IEnumerable<string> processedBlock = _ConvertBlock(lines);
|
|
||||||
foreach (string t in processedBlock)
|
|
||||||
await writer.WriteLineAsync(t);
|
|
||||||
lines.Clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
lines.Add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.HttpContext.Response.Body.FlushAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> _ConvertBlock(IList<string> lines)
|
|
||||||
{
|
|
||||||
if (lines.Count < 3)
|
|
||||||
return lines;
|
|
||||||
lines[1] = lines[1].Replace(',', '.');
|
|
||||||
if (lines[2].Length > 5)
|
|
||||||
{
|
|
||||||
lines[1] += lines[2].Substring(0, 6) switch
|
|
||||||
{
|
|
||||||
"{\\an1}" => " line:93% position:15%",
|
|
||||||
"{\\an2}" => " line:93%",
|
|
||||||
"{\\an3}" => " line:93% position:85%",
|
|
||||||
"{\\an4}" => " line:50% position:15%",
|
|
||||||
"{\\an5}" => " line:50%",
|
|
||||||
"{\\an6}" => " line:50% position:85%",
|
|
||||||
"{\\an7}" => " line:7% position:15%",
|
|
||||||
"{\\an8}" => " line:7%",
|
|
||||||
"{\\an9}" => " line:7% position:85%",
|
|
||||||
_ => " line:93%"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lines[2].StartsWith("{\\an"))
|
|
||||||
lines[2] = lines[2].Substring(6);
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/task")]
|
|
||||||
[Route("api/tasks")]
|
|
||||||
[ApiController]
|
|
||||||
public class TaskApi : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly ITaskManager _taskManager;
|
|
||||||
|
|
||||||
public TaskApi(ITaskManager taskManager)
|
|
||||||
{
|
|
||||||
_taskManager = taskManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Permission(nameof(TaskApi), Kind.Read)]
|
|
||||||
public ActionResult<ICollection<ITask>> GetTasks()
|
|
||||||
{
|
|
||||||
return Ok(_taskManager.GetAllTasks());
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{taskSlug}")]
|
|
||||||
[HttpPut("{taskSlug}")]
|
|
||||||
[Permission(nameof(TaskApi), Kind.Create)]
|
|
||||||
public IActionResult RunTask(string taskSlug, [FromQuery] Dictionary<string, object> args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_taskManager.StartTask(taskSlug, new Progress<float>(), args);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("api/track")]
|
|
||||||
[Route("api/tracks")]
|
|
||||||
[ApiController]
|
|
||||||
[PartialPermission(nameof(Track))]
|
|
||||||
public class TrackApi : CrudApi<Track>
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
public TrackApi(ILibraryManager libraryManager, IOptions<BasicOptions> options)
|
|
||||||
: base(libraryManager.TrackRepository, options.Value.PublicUrl)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id:int}/episode")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Episode>> GetEpisode(int id)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _libraryManager.Get<Episode>(x => x.Tracks.Any(y => y.ID == id));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}/episode")]
|
|
||||||
[PartialPermission(Kind.Read)]
|
|
||||||
public async Task<ActionResult<Episode>> GetEpisode(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _libraryManager.Get<Episode>(x => x.Tracks.Any(y => y.Slug == slug));
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
// Kyoo - A portable and vast media library solution.
|
|
||||||
// Copyright (c) Kyoo.
|
|
||||||
//
|
|
||||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
// Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// Kyoo is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Abstractions.Controllers;
|
|
||||||
using Kyoo.Abstractions.Models;
|
|
||||||
using Kyoo.Abstractions.Models.Exceptions;
|
|
||||||
using Kyoo.Abstractions.Models.Permissions;
|
|
||||||
using Kyoo.Core.Models.Options;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Api
|
|
||||||
{
|
|
||||||
[Route("video")]
|
|
||||||
[ApiController]
|
|
||||||
public class VideoApi : Controller
|
|
||||||
{
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly ITranscoder _transcoder;
|
|
||||||
private readonly IOptions<BasicOptions> _options;
|
|
||||||
private readonly IFileSystem _files;
|
|
||||||
|
|
||||||
public VideoApi(ILibraryManager libraryManager,
|
|
||||||
ITranscoder transcoder,
|
|
||||||
IOptions<BasicOptions> options,
|
|
||||||
IFileSystem files)
|
|
||||||
{
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_transcoder = transcoder;
|
|
||||||
_options = options;
|
|
||||||
_files = files;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnActionExecuted(ActionExecutedContext ctx)
|
|
||||||
{
|
|
||||||
base.OnActionExecuted(ctx);
|
|
||||||
// Disabling the cache prevent an issue on firefox that skip the last 30 seconds of HLS files.
|
|
||||||
ctx.HttpContext.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
||||||
ctx.HttpContext.Response.Headers.Add("Pragma", "no-cache");
|
|
||||||
ctx.HttpContext.Response.Headers.Add("Expires", "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)]
|
|
||||||
[HttpGet("{slug}")]
|
|
||||||
[HttpGet("direct/{slug}")]
|
|
||||||
public async Task<IActionResult> Direct(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Episode episode = await _libraryManager.Get<Episode>(slug);
|
|
||||||
return _files.FileResult(episode.Path, true);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("transmux/{slug}/master.m3u8")]
|
|
||||||
[Permission("video", Kind.Read)]
|
|
||||||
public async Task<IActionResult> Transmux(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Episode episode = await _libraryManager.Get<Episode>(slug);
|
|
||||||
string path = await _transcoder.Transmux(episode);
|
|
||||||
|
|
||||||
if (path == null)
|
|
||||||
return StatusCode(500);
|
|
||||||
return _files.FileResult(path, true);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("transcode/{slug}/master.m3u8")]
|
|
||||||
[Permission("video", Kind.Read)]
|
|
||||||
public async Task<IActionResult> Transcode(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Episode episode = await _libraryManager.Get<Episode>(slug);
|
|
||||||
string path = await _transcoder.Transcode(episode);
|
|
||||||
|
|
||||||
if (path == null)
|
|
||||||
return StatusCode(500);
|
|
||||||
return _files.FileResult(path, true);
|
|
||||||
}
|
|
||||||
catch (ItemNotFoundException)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("transmux/{episodeLink}/segments/{chunk}")]
|
|
||||||
[Permission("video", Kind.Read)]
|
|
||||||
public IActionResult GetTransmuxedChunk(string episodeLink, string chunk)
|
|
||||||
{
|
|
||||||
string path = Path.GetFullPath(Path.Combine(_options.Value.TransmuxPath, episodeLink));
|
|
||||||
path = Path.Combine(path, "segments", chunk);
|
|
||||||
return PhysicalFile(path, "video/MP2T");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("transcode/{episodeLink}/segments/{chunk}")]
|
|
||||||
[Permission("video", Kind.Read)]
|
|
||||||
public IActionResult GetTranscodedChunk(string episodeLink, string chunk)
|
|
||||||
{
|
|
||||||
string path = Path.GetFullPath(Path.Combine(_options.Value.TranscodePath, episodeLink));
|
|
||||||
path = Path.Combine(path, "segments", chunk);
|
|
||||||
return PhysicalFile(path, "video/MP2T");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
205
src/Kyoo.Core/Views/Watch/SubtitleApi.cs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An endpoint to retrieve subtitles for a specific episode.
|
||||||
|
/// </summary>
|
||||||
|
[Route("subtitles")]
|
||||||
|
[Route("subtitle", Order = AlternativeRoute)]
|
||||||
|
[PartialPermission(nameof(SubtitleApi))]
|
||||||
|
[ApiController]
|
||||||
|
[ApiDefinition("Subtitles", Group = WatchGroup)]
|
||||||
|
public class SubtitleApi : ControllerBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information about the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file manager used to send subtitles files.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IFileSystem _files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="SubtitleApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager used to interact with the data store.</param>
|
||||||
|
/// <param name="files">The file manager used to send subtitle files.</param>
|
||||||
|
public SubtitleApi(ILibraryManager libraryManager, IFileSystem files)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get subtitle
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the subtitle file with the given identifier.
|
||||||
|
/// The extension is optional and can be used to ask Kyoo to convert the subtitle file on the fly.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">
|
||||||
|
/// The ID or slug of the subtitle (the same as the corresponding <see cref="Track"/>).
|
||||||
|
/// </param>
|
||||||
|
/// <param name="extension">An optional extension for the subtitle file.</param>
|
||||||
|
/// <returns>The subtitle file</returns>
|
||||||
|
/// <response code="404">No subtitle exist with the given ID or slug.</response>
|
||||||
|
[HttpGet("{identifier:int}", Order = AlternativeRoute)]
|
||||||
|
[HttpGet("{identifier:id}.{extension}")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[SuppressMessage("ReSharper", "RouteTemplates.ParameterTypeAndConstraintsMismatch",
|
||||||
|
Justification = "An indentifier can be constructed with an int.")]
|
||||||
|
public async Task<IActionResult> GetSubtitle(Identifier identifier, string extension)
|
||||||
|
{
|
||||||
|
Track subtitle = await identifier.Match(
|
||||||
|
id => _libraryManager.GetOrDefault<Track>(id),
|
||||||
|
slug =>
|
||||||
|
{
|
||||||
|
if (slug.Count(x => x == '.') == 3)
|
||||||
|
{
|
||||||
|
int idx = slug.LastIndexOf('.');
|
||||||
|
extension = slug[(idx + 1)..];
|
||||||
|
slug = slug[..idx];
|
||||||
|
}
|
||||||
|
return _libraryManager.GetOrDefault<Track>(Track.BuildSlug(slug, StreamType.Subtitle));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subtitle == null)
|
||||||
|
return NotFound();
|
||||||
|
if (subtitle.Codec == "subrip" && extension == "vtt")
|
||||||
|
return new ConvertSubripToVtt(subtitle.Path, _files);
|
||||||
|
return _files.FileResult(subtitle.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An action result that convert a subrip subtitle to vtt.
|
||||||
|
/// </summary>
|
||||||
|
private class ConvertSubripToVtt : IActionResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The path of the file to convert. It can be any path supported by a <see cref="IFileSystem"/>.
|
||||||
|
/// </summary>
|
||||||
|
private readonly string _path;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file system used to manipulate the given file.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IFileSystem _files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ConvertSubripToVtt"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subtitlePath">
|
||||||
|
/// The path of the subtitle file. It can be any path supported by the given <paramref name="files"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="files">
|
||||||
|
/// The file system used to interact with the file at the given <paramref name="subtitlePath"/>.
|
||||||
|
/// </param>
|
||||||
|
public ConvertSubripToVtt(string subtitlePath, IFileSystem files)
|
||||||
|
{
|
||||||
|
_path = subtitlePath;
|
||||||
|
_files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task ExecuteResultAsync(ActionContext context)
|
||||||
|
{
|
||||||
|
List<string> lines = new();
|
||||||
|
|
||||||
|
context.HttpContext.Response.StatusCode = 200;
|
||||||
|
context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt");
|
||||||
|
|
||||||
|
await using (StreamWriter writer = new(context.HttpContext.Response.Body))
|
||||||
|
{
|
||||||
|
await writer.WriteLineAsync("WEBVTT");
|
||||||
|
await writer.WriteLineAsync(string.Empty);
|
||||||
|
await writer.WriteLineAsync(string.Empty);
|
||||||
|
|
||||||
|
using StreamReader reader = new(await _files.GetReader(_path));
|
||||||
|
string line;
|
||||||
|
while ((line = await reader.ReadLineAsync()) != null)
|
||||||
|
{
|
||||||
|
if (line == string.Empty)
|
||||||
|
{
|
||||||
|
lines.Add(string.Empty);
|
||||||
|
IEnumerable<string> processedBlock = _ConvertBlock(lines);
|
||||||
|
foreach (string t in processedBlock)
|
||||||
|
await writer.WriteLineAsync(t);
|
||||||
|
lines.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
lines.Add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.HttpContext.Response.Body.FlushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a block from subrip to vtt.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lines">All the lines in the block.</param>
|
||||||
|
/// <returns>The given block, converted to vtt.</returns>
|
||||||
|
private static IList<string> _ConvertBlock(IList<string> lines)
|
||||||
|
{
|
||||||
|
if (lines.Count < 3)
|
||||||
|
return lines;
|
||||||
|
lines[1] = lines[1].Replace(',', '.');
|
||||||
|
if (lines[2].Length > 5)
|
||||||
|
{
|
||||||
|
lines[1] += lines[2].Substring(0, 6) switch
|
||||||
|
{
|
||||||
|
"{\\an1}" => " line:93% position:15%",
|
||||||
|
"{\\an2}" => " line:93%",
|
||||||
|
"{\\an3}" => " line:93% position:85%",
|
||||||
|
"{\\an4}" => " line:50% position:15%",
|
||||||
|
"{\\an5}" => " line:50%",
|
||||||
|
"{\\an6}" => " line:50% position:85%",
|
||||||
|
"{\\an7}" => " line:7% position:15%",
|
||||||
|
"{\\an8}" => " line:7%",
|
||||||
|
"{\\an9}" => " line:7% position:85%",
|
||||||
|
_ => " line:93%"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines[2].StartsWith("{\\an"))
|
||||||
|
lines[2] = lines[2][6..];
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
src/Kyoo.Core/Views/Watch/TrackApi.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information about one or multiple <see cref="Track"/>.
|
||||||
|
/// A track contain metadata about a video, an audio or a subtitles.
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/tracks")]
|
||||||
|
[Route("api/track", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ResourceView]
|
||||||
|
[PartialPermission(nameof(Track))]
|
||||||
|
[ApiDefinition("Tracks", Group = WatchGroup)]
|
||||||
|
public class TrackApi : CrudApi<Track>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TrackApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </param>
|
||||||
|
public TrackApi(ILibraryManager libraryManager)
|
||||||
|
: base(libraryManager.TrackRepository)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get track's episode
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Get the episode that uses this track.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The ID or slug of the <see cref="Track"/>.</param>
|
||||||
|
/// <returns>The episode that uses this track.</returns>
|
||||||
|
/// <response code="404">No track with the given ID or slug could be found.</response>
|
||||||
|
[HttpGet("{identifier:id}/episode")]
|
||||||
|
[PartialPermission(Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<ActionResult<Episode>> GetEpisode(Identifier identifier)
|
||||||
|
{
|
||||||
|
Episode ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Episode, Track>(x => x.Tracks));
|
||||||
|
if (ret == null)
|
||||||
|
return NotFound();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
src/Kyoo.Core/Views/Watch/VideoApi.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
// Kyoo - A portable and vast media library solution.
|
||||||
|
// Copyright (c) Kyoo.
|
||||||
|
//
|
||||||
|
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
// Kyoo is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// Kyoo is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Abstractions.Controllers;
|
||||||
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
|
using Kyoo.Abstractions.Models.Permissions;
|
||||||
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
|
using Kyoo.Core.Models.Options;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||||
|
|
||||||
|
namespace Kyoo.Core.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get the video in a raw format or transcoded in the codec you want.
|
||||||
|
/// </summary>
|
||||||
|
[Route("videos")]
|
||||||
|
[Route("video", Order = AlternativeRoute)]
|
||||||
|
[ApiController]
|
||||||
|
[ApiDefinition("Videos", Group = WatchGroup)]
|
||||||
|
public class VideoApi : Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to modify or retrieve information in the data store.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The file system used to send video files.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IFileSystem _files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="VideoApi"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager used to retrieve episodes.</param>
|
||||||
|
/// <param name="files">The file manager used to send video files.</param>
|
||||||
|
public VideoApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_files = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// Disabling the cache prevent an issue on firefox that skip the last 30 seconds of HLS files
|
||||||
|
/// </remarks>
|
||||||
|
public override void OnActionExecuted(ActionExecutedContext ctx)
|
||||||
|
{
|
||||||
|
base.OnActionExecuted(ctx);
|
||||||
|
ctx.HttpContext.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
ctx.HttpContext.Response.Headers.Add("Pragma", "no-cache");
|
||||||
|
ctx.HttpContext.Response.Headers.Add("Expires", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Direct video
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or
|
||||||
|
/// transmuxing is done.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The identifier of the episode to retrieve.</param>
|
||||||
|
/// <returns>The raw video stream</returns>
|
||||||
|
/// <response code="404">No episode exists for the given identifier.</response>
|
||||||
|
// TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)]
|
||||||
|
[HttpGet("direct/{identifier:id}")]
|
||||||
|
[HttpGet("{identifier:id}", Order = AlternativeRoute)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Direct(Identifier identifier)
|
||||||
|
{
|
||||||
|
Episode episode = await identifier.Match(
|
||||||
|
id => _libraryManager.GetOrDefault<Episode>(id),
|
||||||
|
slug => _libraryManager.GetOrDefault<Episode>(slug)
|
||||||
|
);
|
||||||
|
return _files.FileResult(episode?.Path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transmux video
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Change the container of the video to hls but don't re-encode the video or audio. This doesn't require mutch
|
||||||
|
/// resources from the server.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="identifier">The identifier of the episode to retrieve.</param>
|
||||||
|
/// <returns>The transmuxed video stream</returns>
|
||||||
|
/// <response code="404">No episode exists for the given identifier.</response>
|
||||||
|
[HttpGet("transmux/{identifier:id}/master.m3u8")]
|
||||||
|
[Permission("video", Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Transmux(Identifier identifier)
|
||||||
|
{
|
||||||
|
Episode episode = await identifier.Match(
|
||||||
|
id => _libraryManager.GetOrDefault<Episode>(id),
|
||||||
|
slug => _libraryManager.GetOrDefault<Episode>(slug)
|
||||||
|
);
|
||||||
|
return _files.Transmux(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transmuxed chunk
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Retrieve a chunk of a transmuxed video.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="episodeLink">The identifier of the episode.</param>
|
||||||
|
/// <param name="chunk">The identifier of the chunk to retrieve.</param>
|
||||||
|
/// <param name="options">The options used to retrieve the path of the segments.</param>
|
||||||
|
/// <returns>A transmuxed video chunk.</returns>
|
||||||
|
[HttpGet("transmux/{episodeLink}/segments/{chunk}", Order = AlternativeRoute)]
|
||||||
|
[Permission("video", Kind.Read)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public IActionResult GetTransmuxedChunk(string episodeLink, string chunk,
|
||||||
|
[FromServices] IOptions<BasicOptions> options)
|
||||||
|
{
|
||||||
|
string path = Path.GetFullPath(Path.Combine(options.Value.TransmuxPath, episodeLink));
|
||||||
|
path = Path.Combine(path, "segments", chunk);
|
||||||
|
return PhysicalFile(path, "video/MP2T");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|