Update plugin to latest artwork spec

This commit is contained in:
crobibero 2021-03-12 21:45:56 -07:00
parent 002231e388
commit 3b381750e4
23 changed files with 397 additions and 140 deletions

23
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,23 @@
version: 2
updates:
# Fetch and update latest `nuget` pkgs
- package-ecosystem: nuget
directory: /
schedule:
interval: daily
time: '00:00'
open-pull-requests-limit: 10
commit-message:
prefix: chore
include: scope
# Fetch and update latest `github-actions` pkgs
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
time: '00:00'
open-pull-requests-limit: 10
commit-message:
prefix: chore
include: scope

9
.github/release-drafter.yml vendored Normal file
View File

@ -0,0 +1,9 @@
_extends: jellyfin-meta-plugins
template: |
<!-- Optional: add a release summary here -->
[Plugin build can be downloaded here](https://repo.jellyfin.org/releases/plugin/artwork/artwork_$NEXT_MAJOR_VERSION.0.0.0.zip).
## :sparkles: What's New
$CHANGES

31
.github/workflows/build-dotnet.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Test Build Plugin
on:
push:
branches: [ master ]
paths-ignore:
- '**/*.md'
pull_request:
branches: [ master ]
paths-ignore:
- '**/*.md'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal

44
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Run CodeQL
on:
push:
branches: [ master ]
paths-ignore:
- '**/*.md'
pull_request:
branches: [ master ]
paths-ignore:
- '**/*.md'
schedule:
- cron: '24 2 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -0,0 +1,19 @@
name: Publish Drafted GitHub Release
on:
push:
tags: [ 'v*' ]
jobs:
publish_release_draft:
runs-on: ubuntu-latest
steps:
- name: Get version from GITHUB_REF
id: get-version
run: echo "::set-output name=version::${GITHUB_REF/refs\/tags\//}"
- name: Publish release on GitHub
uses: test-room-7/action-publish-release-drafts@v0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
tag-name: ${{ steps.get-version.outputs.version }}

View File

@ -0,0 +1,16 @@
# Automates creation of Release Drafts using Release Drafter
name: Update Release Draft
on:
push:
branches:
- master
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/release-drafter@v5.14.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -47,7 +47,13 @@ namespace Jellyfin.Plugin.Artwork
yield return new PluginPageInfo
{
Name = Name,
EmbeddedResourcePath = prefix + ".Configuration.config.html"
EmbeddedResourcePath = prefix + ".Configuration.Web.config.html",
};
yield return new PluginPageInfo
{
Name = $"{Name}.js",
EmbeddedResourcePath = prefix + ".Configuration.Web.config.js"
};
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Jellyfin.Plugin.Artwork.Models;
using MediaBrowser.Model.Plugins;
@ -12,6 +13,7 @@ namespace Jellyfin.Plugin.Artwork.Configuration
/// <summary>
/// Gets or sets the list of artwork repos.
/// </summary>
[SuppressMessage(category: "Performance", checkId: "CA1819", Target = "ArtworkRepos", Justification = "Xml Serializer doesn't support IReadOnlyList")]
public ArtworkRepo[] ArtworkRepos { get; set; } = Array.Empty<ArtworkRepo>();
}
}

View File

@ -0,0 +1,39 @@
<div data-role="page" id="artworkConfigurationPage" class="page type-interior pluginConfigurationPage fullWidthContent"
data-controller="__plugin/Artwork.js">
<div class="content-primary">
<div class="verticalSection">
<div class="sectionTitleContainer">
<h2 class="sectionTitle">Artwork</h2>
<a is="emby-linkbutton" class="raised raised-mini" style="margin-left: 2em;" target="_blank"
href="https://github.com/jellyfin/jellyfin-plugin-artwork">
<i class="md-icon button-icon button-icon-left secondaryText"></i>
<span>Help</span>
</a>
</div>
</div>
<form class="artworkConfigurationForm">
<div id="configurationWrapper"></div>
<button id="btnAddRepo" is="emby-button" type="button" class="raised button block">
<span>Add new Repository</span>
</button>
<br/>
<button id="saveConfig" is="emby-button" type="submit" class="raised button-submit block">
<span>Save</span>
</button>
</form>
</div>
<template id="template-repository">
<div data-id="repo-config">
<div class="inputContainer">
<input is="emby-input" type="text" data-id="txtRepositoryName" required="required" label="Repository Name:"/>
<span>The artwork repository name.</span>
</div>
<div class="inputContainer">
<input is="emby-input" type="text" data-id="txtRepositoryUrl" required="required" label="Repository Url:"/>
<span>The artwork repository url.</span>
</div>
</div>
</template>
</div>

View File

@ -0,0 +1,69 @@
export default function (view) {
const Artwork = {
pluginId: "7871D3B1-F1B9-4318-9C27-F35998FFBBCC",
configurationWrapper: document.querySelector("#configurationWrapper"),
template: document.querySelector("#template-repository"),
btnAddRepo: document.querySelector("#btnAddRepo"),
btnSave: document.querySelector("#saveConfig"),
addRepo: function (config) {
const template = Artwork.template.cloneNode(true).content;
template.querySelector("[data-id=txtRepositoryName]").value = config.Name || "";
template.querySelector("[data-id=txtRepositoryUrl]").value = config.Url || "";
Artwork.configurationWrapper.appendChild(template);
},
saveConfig: function (e) {
e.preventDefault();
Dashboard.showLoadingMsg();
const config = {
ArtworkRepos: []
};
const configs = document.querySelectorAll("[data-id=repo-config]");
for (let i = 0; i < configs.length; i++) {
const repo = {
Name: configs[i].querySelector("[data-id=txtRepositoryName]").value,
Url: configs[i].querySelector("[data-id=txtRepositoryUrl]").value
};
config.ArtworkRepos.push(repo);
}
window.ApiClient.updatePluginConfiguration(Artwork.pluginId, config)
.then(Dashboard.processPluginConfigurationUpdateResult)
.catch(function (error) {
console.error(error);
})
.finally(function () {
Dashboard.hideLoadingMsg();
});
},
loadConfig: function () {
Dashboard.showLoadingMsg();
window.ApiClient.getPluginConfiguration(Artwork.pluginId)
.then(function (config) {
for (let i = 0; i < config.ArtworkRepos.length; i++) {
Artwork.addRepo(config.ArtworkRepos[i]);
}
})
.catch(function (error) {
console.error(error);
})
.finally(function () {
Dashboard.hideLoadingMsg();
});
},
init: function () {
Artwork.btnAddRepo.addEventListener("click", Artwork.addRepo);
Artwork.btnSave.addEventListener("click", Artwork.saveConfig);
Artwork.loadConfig();
}
}
view.addEventListener("viewshow", function (e) {
Artwork.init();
});
}

View File

@ -14,9 +14,10 @@ namespace Jellyfin.Plugin.Artwork
/// <summary>
/// Get the remote image info for item.
/// </summary>
/// <param name="imageTypeKey">The item repo key.</param>
/// <param name="itemType">The item type.</param>
/// <param name="providerIds">The provider ids.</param>
/// <returns>The list of remote image info.</returns>
Task<IEnumerable<RemoteImageInfo>> GetImageInfos(Type itemType, IHasProviderIds providerIds);
Task<IEnumerable<RemoteImageInfo>> GetImageInfos(string imageTypeKey, Type itemType, IHasProviderIds providerIds);
}
}

View File

@ -7,6 +7,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@ -14,23 +16,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.*" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.*" />
</ItemGroup>
<ItemGroup>
<None Remove="Configuration\Web\config.html" />
<None Remove="Configuration\Web\config.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Configuration\config.html" />
<EmbeddedResource Include="Configuration\Web\config.html" />
<EmbeddedResource Include="Configuration\Web\config.js" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>

View File

@ -10,17 +10,25 @@ namespace Jellyfin.Plugin.Artwork.Models
/// <summary>
/// Gets or sets the artwork name.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the machine name.
/// </summary>
[JsonPropertyName("machine-name")]
public string MachineName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the artwork.
/// </summary>
[JsonPropertyName("artwork")]
public ArtworkImages? ArtworkImages { get; set; }
public ArtworkImageDto? ArtworkImages { get; set; }
/// <summary>
/// Gets or sets the providers.
/// </summary>
public Providers? Providers { get; set; }
[JsonPropertyName("providers")]
public ArtworkProviderDto? Providers { get; set; }
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Artwork.Models
{
/// <summary>
/// The artwork image dto.
/// </summary>
public class ArtworkImageDto
{
/// <summary>
/// Gets or sets the backdrop image url.
/// </summary>
[JsonPropertyName("backdrop")]
public IReadOnlyList<string> Backdrop { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the primary image url.
/// </summary>
[JsonPropertyName("primary")]
public IReadOnlyList<string> Primary { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the thumb image url.
/// </summary>
[JsonPropertyName("thumb")]
public IReadOnlyList<string> Thumb { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the logo image url.
/// </summary>
[JsonPropertyName("logo")]
public IReadOnlyList<string> Logo { get; set; } = Array.Empty<string>();
}
}

View File

@ -1,28 +0,0 @@
namespace Jellyfin.Plugin.Artwork.Models
{
/// <summary>
/// The artwork images.
/// </summary>
public class ArtworkImages
{
/// <summary>
/// Gets or sets the backdrop image url.
/// </summary>
public string? Backdrop { get; set; }
/// <summary>
/// Gets or sets the primary image url.
/// </summary>
public string? Primary { get; set; }
/// <summary>
/// Gets or sets the thumb image url.
/// </summary>
public string? Thumb { get; set; }
/// <summary>
/// Gets or sets the logo image url.
/// </summary>
public string? Logo { get; set; }
}
}

View File

@ -1,9 +1,9 @@
namespace Jellyfin.Plugin.Artwork.Models
{
/// <summary>
/// The providers.
/// The artwork provider dto.
/// </summary>
public class Providers
public class ArtworkProviderDto
{
/// <summary>
/// Gets or sets the anilist identifier.

View File

@ -1,4 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Jellyfin.Plugin.Artwork.Models
{
@ -10,16 +10,13 @@ namespace Jellyfin.Plugin.Artwork.Models
/// <summary>
/// Gets or sets the repo name.
/// </summary>
public string? Name { get; set; }
[Required]
public string Name { get; set; } = null!;
/// <summary>
/// Gets or sets the repo url.
/// </summary>
public string? Url { get; set; }
/// <summary>
/// Gets or sets the item types.
/// </summary>
public string[] ItemType { get; set; } = Array.Empty<string>();
[Required]
public string Url { get; set; } = null!;
}
}

View File

@ -1,13 +0,0 @@
namespace Jellyfin.Plugin.Artwork.Models
{
/// <summary>
/// The artwork type.
/// </summary>
public enum ArtworkType
{
/// <summary>
/// Studio artwork.
/// </summary>
Studio = 1
}
}

View File

@ -17,10 +17,9 @@ namespace Jellyfin.Plugin.Artwork.Providers
};
/// <summary>
/// Get supported image types.
/// Gets the supported image types.
/// </summary>
/// <returns>The supported image types.</returns>
public static IEnumerable<ImageType> GetSupportedImages()
=> SupportedImages;
public static IEnumerable<ImageType> GetSupportedImages => SupportedImages;
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -41,16 +42,16 @@ namespace Jellyfin.Plugin.Artwork.Providers
/// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
=> ArtworkProviderHelper.GetSupportedImages();
=> ArtworkProviderHelper.GetSupportedImages;
/// <inheritdoc />
public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
=> _repositoryCache.GetImageInfos(item.GetType(), item);
=> _repositoryCache.GetImageInfos("studios", typeof(Studio), item);
/// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
=> _httpClientFactory
.CreateClient(NamedClient.Default)
.GetAsync(url, cancellationToken);
.GetAsync(new Uri(url), cancellationToken);
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
@ -41,32 +42,21 @@ namespace Jellyfin.Plugin.Artwork
}
/// <inheritdoc />
public async Task<IEnumerable<RemoteImageInfo>> GetImageInfos(Type itemType, IHasProviderIds providerIds)
public async Task<IEnumerable<RemoteImageInfo>> GetImageInfos(string imageTypeKey, Type itemType, IHasProviderIds providerIds)
{
var artworkRepo = ArtworkPlugin.Instance!.Configuration.ArtworkRepos;
var remoteImageInfos = new List<RemoteImageInfo>();
var itemTypeString = itemType.ToString();
foreach (var repo in artworkRepo)
{
var arrayIndex = Array.FindIndex(repo.ItemType, t => string.Equals(t, itemTypeString, StringComparison.OrdinalIgnoreCase));
if (arrayIndex == -1)
{
// Repo not configured for item type.
continue;
}
if (string.IsNullOrEmpty(repo.Url))
{
continue;
}
var artworkDtos = await GetFromRepo(repo.Url);
var fullUrl = repo.Url.TrimEnd('/') + $"/{imageTypeKey}.json";
var artworkDtos = await GetFromRepo(fullUrl).ConfigureAwait(false);
var artworkDto = GetMatch(itemType, providerIds, artworkDtos);
if (artworkDto?.ArtworkImages != null)
{
AddImageInfos(ref remoteImageInfos, artworkDto.ArtworkImages);
}
AddImageInfos(
repo,
imageTypeKey,
ref remoteImageInfos,
artworkDto);
}
return remoteImageInfos;
@ -83,79 +73,61 @@ namespace Jellyfin.Plugin.Artwork
continue;
}
if (string.Equals(
providerIds.ProviderIds["AniList"],
artworkDto.Providers.Anilist,
StringComparison.OrdinalIgnoreCase))
if (providerIds.TryGetProviderId("AniList", out var providerId)
&& string.Equals(providerId, artworkDto.Providers.Anilist, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if (string.Equals(
providerIds.ProviderIds[MetadataProvider.Imdb.ToString()],
artworkDto.Providers.Imdb,
StringComparison.OrdinalIgnoreCase))
if (providerIds.TryGetProviderId(MetadataProvider.Imdb, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Imdb, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if (string.Equals(
providerIds.ProviderIds[MetadataProvider.Tmdb.ToString()],
artworkDto.Providers.Tmdb,
StringComparison.OrdinalIgnoreCase))
if (providerIds.TryGetProviderId(MetadataProvider.Tmdb, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Tmdb, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if (string.Equals(
providerIds.ProviderIds[MetadataProvider.Tvdb.ToString()],
artworkDto.Providers.Tvdb,
StringComparison.OrdinalIgnoreCase))
if (providerIds.TryGetProviderId(MetadataProvider.Tvdb, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Tvdb, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if ((itemType == typeof(Audio) || itemType == typeof(MusicAlbum))
&& string.Equals(
providerIds.ProviderIds[MetadataProvider.MusicBrainzReleaseGroup.ToString()],
artworkDto.Providers.Musicbrainz,
StringComparison.OrdinalIgnoreCase))
&& providerIds.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Musicbrainz, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if (itemType == typeof(Audio)
&& string.Equals(
providerIds.ProviderIds[MetadataProvider.MusicBrainzAlbumArtist.ToString()],
artworkDto.Providers.Musicbrainz,
StringComparison.OrdinalIgnoreCase))
&& providerIds.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Musicbrainz, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if ((itemType == typeof(MusicAlbum) || itemType == typeof(Audio))
&& string.Equals(
providerIds.ProviderIds[MetadataProvider.MusicBrainzAlbum.ToString()],
artworkDto.Providers.Musicbrainz,
StringComparison.OrdinalIgnoreCase))
&& providerIds.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Musicbrainz, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if (itemType == typeof(MusicArtist)
&& string.Equals(
providerIds.ProviderIds[MetadataProvider.MusicBrainzArtist.ToString()],
artworkDto.Providers.Musicbrainz,
StringComparison.OrdinalIgnoreCase))
&& providerIds.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Musicbrainz, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
if (itemType == typeof(Audio)
&& string.Equals(
providerIds.ProviderIds[MetadataProvider.MusicBrainzTrack.ToString()],
artworkDto.Providers.Musicbrainz,
StringComparison.OrdinalIgnoreCase))
&& providerIds.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out providerId)
&& string.Equals(providerId, artworkDto.Providers.Musicbrainz, StringComparison.OrdinalIgnoreCase))
{
return artworkDto;
}
@ -164,41 +136,58 @@ namespace Jellyfin.Plugin.Artwork
return null;
}
private static void AddImageInfos(ref List<RemoteImageInfo> imageInfos, ArtworkImages artworkImages)
private static void AddImageInfos(
ArtworkRepo repo,
string itemKey,
ref List<RemoteImageInfo> imageInfos,
ArtworkDto? artworkDto)
{
if (!string.IsNullOrEmpty(artworkImages.Backdrop))
if (artworkDto?.ArtworkImages == null)
{
// Repo or images not found.
return;
}
/*
* 0: machine name
* 1: image type
* 2: image extension
*/
var imageTemplate = repo.Url.TrimEnd('/') + $"/{itemKey}/{0}/{1}.{2}";
foreach (var image in artworkDto.ArtworkImages.Backdrop)
{
imageInfos.Add(new RemoteImageInfo
{
Type = ImageType.Backdrop,
Url = artworkImages.Backdrop
Url = string.Format(CultureInfo.InvariantCulture, imageTemplate, artworkDto.MachineName, "backdrop", image)
});
}
if (!string.IsNullOrEmpty(artworkImages.Primary))
foreach (var image in artworkDto.ArtworkImages.Primary)
{
imageInfos.Add(new RemoteImageInfo
{
Type = ImageType.Primary,
Url = artworkImages.Primary
Url = string.Format(CultureInfo.InvariantCulture, imageTemplate, artworkDto.MachineName, "primary", image)
});
}
if (!string.IsNullOrEmpty(artworkImages.Thumb))
foreach (var image in artworkDto.ArtworkImages.Thumb)
{
imageInfos.Add(new RemoteImageInfo
{
Type = ImageType.Thumb,
Url = artworkImages.Thumb
Url = string.Format(CultureInfo.InvariantCulture, imageTemplate, artworkDto.MachineName, "thumb", image)
});
}
if (!string.IsNullOrEmpty(artworkImages.Logo))
foreach (var image in artworkDto.ArtworkImages.Logo)
{
imageInfos.Add(new RemoteImageInfo
{
Type = ImageType.Logo,
Url = artworkImages.Logo
Url = string.Format(CultureInfo.InvariantCulture, imageTemplate, artworkDto.MachineName, "logo", image)
});
}
}
@ -214,7 +203,8 @@ namespace Jellyfin.Plugin.Artwork
{
var artworkDto = await _httpClientFactory
.CreateClient(NamedClient.Default)
.GetFromJsonAsync<IReadOnlyList<ArtworkDto>>(repositoryUrl);
.GetFromJsonAsync<IReadOnlyList<ArtworkDto>>(repositoryUrl)
.ConfigureAwait(false);
if (artworkDto != null)
{
_memoryCache.Set(repositoryUrl, artworkDto, _cacheExpire);

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for Jellyfin.Server" Description="Code analysis rules for Jellyfin.Server.csproj" ToolsVersion="14.0">
<RuleSet Name="Rules for Jellyfin" Description="Code analysis rules for Jellyfin" ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<!-- disable warning SA1202: 'public' members must come before 'private' members -->
<Rule Id="SA1202" Action="Info" />
@ -10,6 +10,8 @@
<!-- disable warning SA1009: Closing parenthesis should be followed by a space. -->
<Rule Id="SA1009" Action="None" />
<!-- disable warning SA1011: Closing square bracket should be followed by a space. -->
<Rule Id="SA1011" Action="None" />
<!-- disable warning SA1101: Prefix local calls with 'this.' -->
<Rule Id="SA1101" Action="None" />
<!-- disable warning SA1108: Block statements should not contain embedded comments -->
@ -30,11 +32,17 @@
<Rule Id="SA1515" Action="None" />
<!-- disable warning SA1600: Elements should be documented -->
<Rule Id="SA1600" Action="None" />
<!-- disable warning SA1602: Enumeration items should be documented -->
<Rule Id="SA1602" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
<Rules AnalyzerId="Microsoft.CodeAnalysis.NetAnalyzers" RuleNamespace="Microsoft.Design">
<!-- error on CA2016: Forward the CancellationToken parameter to methods that take one
or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token -->
<Rule Id="CA2016" Action="Error" />
<!-- disable warning CA1031: Do not catch general exception types -->
<Rule Id="CA1031" Action="Info" />
<!-- disable warning CA1032: Implement standard exception constructors -->
@ -45,6 +53,8 @@
<Rule Id="CA1716" Action="Info" />
<!-- disable warning CA1720: Identifiers should not contain type names -->
<Rule Id="CA1720" Action="Info" />
<!-- disable warning CA1805: Do not initialize unnecessarily -->
<Rule Id="CA1805" Action="Info" />
<!-- disable warning CA1812: internal class that is apparently never instantiated.
If so, remove the code from the assembly.
If this class is intended to contain only static members, make it static -->
@ -53,7 +63,11 @@
<Rule Id="CA1822" Action="Info" />
<!-- disable warning CA2000: Dispose objects before losing scope -->
<Rule Id="CA2000" Action="Info" />
<!-- disable warning CA5394: Do not use insecure randomness -->
<Rule Id="CA5394" Action="Info" />
<!-- disable warning CA1014: Mark assemblies with CLSCompliantAttribute -->
<Rule Id="CA1014" Action="Info" />
<!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
<Rule Id="CA1054" Action="None" />
<!-- disable warning CA1055: URI return values should not be strings -->