Merge remote-tracking branch 'upstream/master' into healthy-base-url

pull/5803/head
crobibero 3 years ago
commit 01e8ff8ddf

@ -1,59 +0,0 @@
parameters:
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: GeneratorVersion
type: string
default: "5.0.1"
jobs:
- job: GenerateApiClients
displayName: 'Generate Api Clients'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
dependsOn: Test
pool:
vmImage: "${{ parameters.LinuxImage }}"
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec Artifact'
inputs:
source: 'current'
artifact: "OpenAPI Spec"
path: "$(System.ArtifactsDirectory)/openapispec"
runVersion: "latest"
- task: CmdLine@2
displayName: 'Download OpenApi Generator'
inputs:
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
## Authenticate with npm registry
- task: npmAuthenticate@0
inputs:
workingFile: ./.npmrc
customEndpoint: 'jellyfin-bot for NPM'
## Generate npm api client
- task: CmdLine@2
displayName: 'Build stable typescript axios client'
inputs:
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
## Run npm install
- task: Npm@1
displayName: 'Install npm dependencies'
inputs:
command: install
workingDir: ./apiclient/generated/typescript/axios
## Publish npm packages
- task: Npm@1
displayName: 'Publish stable typescript axios client'
inputs:
command: custom
customCommand: publish --access public
publishRegistry: useExternalRegistry
publishEndpoint: 'jellyfin-bot for NPM'
workingDir: ./apiclient/generated/typescript/axios

@ -61,6 +61,3 @@ jobs:
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-api-client.yml

@ -33,7 +33,13 @@ assignees: ''
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
**Server Logs**
<!-- Please paste any log errors. -->
**FFmpeg Logs**
<!-- Please paste any log errors. -->
**Browser Console Logs**
<!-- Please paste any log errors. -->
**Screenshots**

@ -6,4 +6,10 @@ updates:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10

@ -0,0 +1,43 @@
comment:
header: Hello @{{ issue.user.login }}
footer: "\
---\n\n
> This is an automated comment created by the [peaceiris/actions-label-commenter]. \
Responding to the bot or mentioning it won't have any effect.\n\n
[peaceiris/actions-label-commenter]: https://github.com/peaceiris/actions-label-commenter
"
labels:
- name: stable backport
labeled:
pr:
body: |
This pull request has been tagged as a stable backport. It will be cherry-picked into the next stable point release.
Please observe the following:
* Any dependent PRs that this PR requires **must** be tagged for stable backporting as well.
* Any issue(s) this PR fixes or closes **should** target the current stable release or a previous stable release to which a fix has not yet entered the current stable release.
* This PR **must** be test cherry-picked against the current release branch (`release-X.Y.z` where X and Y are numbers). It must apply cleanly, or a diff of the expected change must be provided.
To do this, run the following commands from your local copy of the Jellyfin repository:
1. `git checkout master`
1. `git merge --no-ff <myPullRequestBranch>`
1. `git log` -> `commit xxxxxxxxx`, grab hash
1. `git checkout release-X.Y.z` replacing X and Y with the *current* stable version (e.g. `release-10.7.z`)
1. `git cherry-pick -sx -m1 <hash>`
Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff.
Test your changes with a build to ensure they are successful. If not, adjust the diff accordingly.
**Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state.
Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable.

@ -0,0 +1,64 @@
name: Automation
on:
pull_request:
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Does PR has the stable backport label?
uses: Dreamcodeio/does-pr-has-label@v1.2
id: checkLabel
with:
label: stable backport
- name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
continue-on-error: true
with:
project: Current Release
action: delete
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true
with:
project: Release Next
column: In progress
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel
continue-on-error: true
with:
project: Current Release
column: In progress
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Check number of comments from the team member
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
id: member_comments
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage
uses: alex-page/github-project-automation-plus@v0.7.1
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true
with:
project: Issue Triage for Main Repo
column: Needs triage
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project
uses: alex-page/github-project-automation-plus@v0.7.1
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true
with:
project: Issue Triage for Main Repo
column: Pending response
repo-token: ${{ secrets.JF_BOT_TOKEN }}

@ -0,0 +1,22 @@
name: Label Commenter
on:
issues:
types:
- labeled
- unlabeled
pull_request_target:
types:
- labeled
- unlabeled
jobs:
comment:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
ref: master
- name: Label Commenter
uses: peaceiris/actions-label-commenter@v1

@ -0,0 +1,17 @@
name: 'Merge Conflicts'
on:
push:
branches:
- master
pull_request_target:
types:
- synchronize
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: eps1lon/actions-label-merge-conflict@v2.0.1
with:
dirtyLabel: 'merge conflict'
repoToken: ${{ secrets.JF_BOT_TOKEN }}

@ -0,0 +1,27 @@
name: Automatic Rebase
on:
issue_comment:
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
runs-on: ubuntu-latest
steps:
- name: Notify as seen
uses: peter-evans/create-or-update-comment@v1.4.5
with:
token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }}
reactions: '+1'
- name: Checkout the latest code
uses: actions/checkout@v2
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.4
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}

@ -146,6 +146,7 @@
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
- [skyfrk](https://github.com/skyfrk)
- [ianjazz246](https://github.com/ianjazz246)
- [peterspenler](https://github.com/peterspenler)
# Emby Contributors

@ -1,11 +1,11 @@
ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder
FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& npm ci --no-audit \
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder

@ -5,12 +5,12 @@
ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder
FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& npm ci --no-audit \
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist

@ -5,12 +5,12 @@
ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder
FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& npm ci --no-audit \
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
namespace Emby.Dlna.Configuration

@ -1,4 +1,3 @@
#nullable enable
#pragma warning disable CS1591
using Emby.Dlna.Configuration;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,5 +1,6 @@
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@ -7,7 +8,6 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
using Emby.Dlna.Configuration;
using Emby.Dlna.Didl;
using Emby.Dlna.Service;
using Jellyfin.Data.Entities;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.IO;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -208,7 +210,8 @@ namespace Emby.Dlna.Didl
var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(
var contentFeatureList = ContentFeatureBuilder.BuildVideoHeader(
_profile,
streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
@ -599,7 +602,8 @@ namespace Emby.Dlna.Didl
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(
var contentFeatures = ContentFeatureBuilder.BuildAudioHeader(
_profile,
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetAudioBitrate,
@ -974,15 +978,28 @@ namespace Emby.Dlna.Didl
return;
}
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
// TODO: Remove these default values
var albumArtUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxAlbumArtWidth ?? 10000,
_profile.MaxAlbumArtHeight ?? 10000,
"jpg");
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.url);
if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
{
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
}
writer.WriteString(albumArtUrlInfo.url);
writer.WriteFullEndElement();
// TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
// TODO: Remove these default values
var iconUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxIconWidth ?? 48,
_profile.MaxIconHeight ?? 48,
"jpg");
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl)
@ -1033,8 +1050,7 @@ namespace Emby.Dlna.Didl
var width = albumartUrlInfo.width ?? maxWidth;
var height = albumartUrlInfo.height ?? maxHeight;
var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
writer.WriteAttributeString(
"protocolInfo",
@ -1206,8 +1222,7 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue)
{
var newSize = DrawingUtils.Resize(
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
width = newSize.Width;
height = newSize.Height;

@ -9,7 +9,7 @@ namespace Emby.Dlna.Didl
{
public class StringWriterWithEncoding : StringWriter
{
private readonly Encoding _encoding;
private readonly Encoding? _encoding;
public StringWriterWithEncoding()
{

@ -1,4 +1,3 @@
#nullable enable
#pragma warning disable CS1591
using System.Collections.Generic;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -111,7 +113,7 @@ namespace Emby.Dlna
if (profile != null)
{
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
else
{
@ -138,80 +140,45 @@ namespace Emby.Dlna
_logger.LogInformation(builder.ToString());
}
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
/// <summary>
/// Attempts to match a device with a profile.
/// Rules:
/// - If the profile field has no value, the field matches irregardless of its contents.
/// - the profile field can be an exact match, or a reg exp.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
/// <returns><b>True</b> if they match.</returns>
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{
if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
return false;
}
}
return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
&& IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
&& IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
&& IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
&& IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
&& IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
&& IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
private bool IsRegexOrSubstringMatch(string input, string pattern)
{
if (string.IsNullOrEmpty(pattern))
{
if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
return false;
}
// In profile identification: An empty pattern matches anything.
return true;
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
if (string.IsNullOrEmpty(input))
{
if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
return false;
}
// The profile contains a value, and the device doesn't.
return false;
}
return true;
}
private bool IsRegexOrSubstringMatch(string input, string pattern)
{
try
{
return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
catch (ArgumentException ex)
{

@ -21,6 +21,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Code Analyzers-->

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -5,7 +7,6 @@ using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
using Emby.Dlna.Ssdp;

@ -1,7 +1,6 @@
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.MediaReceiverRegistrar
{

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -368,6 +370,42 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true);
}
/*
* SetNextAvTransport is used to specify to the DLNA device what is the next track to play.
* Without that information, the next track command on the device does not work.
*/
public async Task SetNextAvTransport(string url, string header, string metaData, CancellationToken cancellationToken = default)
{
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
url = url.Replace("&", "&amp;", StringComparison.Ordinal);
_logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header);
var command = avCommands.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase));
if (command == null)
{
return;
}
var dictionary = new Dictionary<string, string>
{
{ "NextURI", url },
{ "NextURIMetaData", CreateDidlMeta(metaData) }
};
var service = GetAvTransportService();
if (service == null)
{
throw new InvalidOperationException("Unable to find service");
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken)
.ConfigureAwait(false);
}
private static string CreateDidlMeta(string value)
{
if (string.IsNullOrEmpty(value))

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -102,6 +104,22 @@ namespace Emby.Dlna.PlayTo
_deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
}
/*
* Send a message to the DLNA device to notify what is the next track in the playlist.
*/
private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken)
{
if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1)
{
// The current playing item is indeed in the play list and we are not yet at the end of the playlist.
var nextItemIndex = currentPlayListItemIndex + 1;
var nextItem = _playlist[nextItemIndex];
// Send the SetNextAvTransport message.
await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false);
}
}
private void OnDeviceUnavailable()
{
try
@ -156,6 +174,15 @@ namespace Emby.Dlna.PlayTo
var newItemProgress = GetProgressInfo(streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
// Send a message to the DLNA device to notify what is the next track in the playlist.
var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId);
if (currentItemIndex >= 0)
{
_currentPlaylistIndex = currentItemIndex;
}
await SendNextTrackMessage(currentItemIndex, CancellationToken.None);
}
catch (Exception ex)
{
@ -425,6 +452,11 @@ namespace Emby.Dlna.PlayTo
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
// Send a message to the DLNA device to notify what is the next track in the play list.
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
return;
}
@ -499,8 +531,8 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.MediaType == DlnaProfileType.Audio)
{
return new ContentFeatureBuilder(profile)
.BuildAudioHeader(
return ContentFeatureBuilder.BuildAudioHeader(
profile,
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetAudioBitrate,
@ -514,8 +546,8 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.MediaType == DlnaProfileType.Video)
{
var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(
var list = ContentFeatureBuilder.BuildVideoHeader(
profile,
streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
@ -623,6 +655,9 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
// Send a message to the DLNA device to notify what is the next track in the play list.
await SendNextTrackMessage(index, cancellationToken);
var streamInfo = currentitem.StreamInfo;
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
{
@ -736,6 +771,10 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
// Send a message to the DLNA device to notify what is the next track in the play list.
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
if (EnableClientSideSeek(newItem.StreamInfo))
{
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
@ -761,6 +800,10 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
// Send a message to the DLNA device to notify what is the next track in the play list.
var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
await SendNextTrackMessage(newItemIndex, CancellationToken.None);
if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0)
{
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -188,7 +190,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress);
string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress);
controller = new PlayToController(
sessionInfo,

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.IO;

@ -1,8 +1,9 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Net.Mime;
using System.Text;

@ -46,7 +46,7 @@ namespace Emby.Dlna.PlayTo
{
var serviceAction = new ServiceAction
{
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
};
var argumentList = serviceAction.ArgumentList;
@ -68,9 +68,9 @@ namespace Emby.Dlna.PlayTo
return new Argument
{
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
Direction = container.GetValue(UPnpNamespaces.Svc + "direction"),
RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable")
Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty,
RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty
};
}
@ -89,8 +89,8 @@ namespace Emby.Dlna.PlayTo
return new StateVariable
{
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"),
Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty,
AllowedValues = allowedValues
};
}
@ -166,7 +166,7 @@ namespace Emby.Dlna.PlayTo
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
}
private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "")
{
var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase));

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -250,7 +250,8 @@ namespace Emby.Dlna.Server
url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/');
return SecurityElement.Escape(url);
// TODO: @bond remove null-coalescing operator when https://github.com/dotnet/runtime/pull/52442 is merged/released
return SecurityElement.Escape(url) ?? string.Empty;
}
private IEnumerable<DeviceIcon> GetIcons()

@ -47,7 +47,7 @@ namespace Emby.Dlna.Service
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
{
ControlRequestInfo requestInfo = null;
ControlRequestInfo? requestInfo = null;
using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8))
{
@ -151,7 +151,7 @@ namespace Emby.Dlna.Service
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
{
string namespaceURI = null, localName = null;
string? namespaceURI = null, localName = null;
await reader.MoveToContentAsync().ConfigureAwait(false);
await reader.ReadAsync().ConfigureAwait(false);

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -104,7 +106,7 @@ namespace Emby.Dlna.Ssdp
{
Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers,
LocalIpAddress = e.LocalIpAddress
RemoteIpAddress = e.RemoteIpAddress
});
DeviceDiscoveredInternal?.Invoke(this, args);

@ -7,21 +7,21 @@ namespace Emby.Dlna.Ssdp
{
public static class SsdpExtensions
{
public static string GetValue(this XElement container, XName name)
public static string? GetValue(this XElement container, XName name)
{
var node = container.Element(name);
return node?.Value;
}
public static string GetAttributeValue(this XElement container, XName name)
public static string? GetAttributeValue(this XElement container, XName name)
{
var node = container.Attribute(name);
return node?.Value;
}
public static string GetDescendantValue(this XElement container, XName name)
public static string? GetDescendantValue(this XElement container, XName name)
=> container.Descendants(name).FirstOrDefault()?.Value;
}
}

@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
namespace Emby.Naming.Audio
{
@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
/// <returns>True if file at path is audio file.</returns>
public static bool IsAudioFile(string path, NamingOptions options)
{
var extension = Path.GetExtension(path);
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
var extension = Path.GetExtension(path.AsSpan());
return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
}
}

@ -23,11 +23,12 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
<Compile Include="../SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="../MediaBrowser.Common/MediaBrowser.Common.csproj" />
<ProjectReference Include="../MediaBrowser.Model/MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup>

@ -68,6 +68,11 @@ namespace Emby.Naming.TV
var parsingResult = new EpisodePathParser(_options)
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
if (!parsingResult.Success && !isStub)
{
return null;
}
return new EpisodeInfo(path)
{
Container = container,

@ -29,70 +29,73 @@ namespace Emby.Naming.Video
/// <param name="path">Path to file.</param>
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path)
{
return _options.VideoExtraRules
.Select(i => GetExtraInfo(path, i))
.FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
}
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
{
var result = new ExtraResult();
if (rule.MediaType == MediaType.Audio)
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
{
if (!AudioFileParser.IsAudioFile(path, _options))
var rule = _options.VideoExtraRules[i];
if (rule.MediaType == MediaType.Audio)
{
return result;
if (!AudioFileParser.IsAudioFile(path, _options))
{
continue;
}
}
}
else if (rule.MediaType == MediaType.Video)
{
if (!new VideoResolver(_options).IsVideoFile(path))
else if (rule.MediaType == MediaType.Video)
{
return result;
if (!new VideoResolver(_options).IsVideoFile(path))
{
continue;
}
}
}
if (rule.RuleType == ExtraRuleType.Filename)
{
var filename = Path.GetFileNameWithoutExtension(path);
if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase))
var pathSpan = path.AsSpan();
if (rule.RuleType == ExtraRuleType.Filename)
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.Suffix)
{
var filename = Path.GetFileNameWithoutExtension(path);
var filename = Path.GetFileNameWithoutExtension(pathSpan);
if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0)
if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.Suffix)
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
var filename = Path.GetFileNameWithoutExtension(pathSpan);
if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
}
else if (rule.RuleType == ExtraRuleType.Regex)
{
var filename = Path.GetFileName(path);
else if (rule.RuleType == ExtraRuleType.Regex)
{
var filename = Path.GetFileName(path);
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
if (regex.IsMatch(filename))
if (regex.IsMatch(filename))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.DirectoryName)
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
}
else if (rule.RuleType == ExtraRuleType.DirectoryName)
{
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
if (result.ExtraType != null)
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
return result;
}
}

@ -1,8 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
namespace Emby.Naming.Video
{
@ -59,15 +59,15 @@ namespace Emby.Naming.Video
}
bool isStub = false;
string? container = null;
ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty;
string? stubType = null;
if (!isDirectory)
{
var extension = Path.GetExtension(path);
var extension = Path.GetExtension(path.AsSpan());
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
// It's not supported. Check stub extensions
if (!StubResolver.TryResolveFile(path, _options, out stubType))
@ -86,9 +86,7 @@ namespace Emby.Naming.Video
var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
var name = isDirectory
? Path.GetFileName(path)
: Path.GetFileNameWithoutExtension(path);
var name = Path.GetFileNameWithoutExtension(path);
int? year = null;
@ -107,7 +105,7 @@ namespace Emby.Naming.Video
return new VideoFileInfo(
path: path,
container: container,
container: container.IsEmpty ? null : container.ToString(),
isStub: isStub,
name: name,
year: year,
@ -126,8 +124,8 @@ namespace Emby.Naming.Video
/// <returns>True if is video file.</returns>
public bool IsVideoFile(string path)
{
var extension = Path.GetExtension(path);
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
var extension = Path.GetExtension(path.AsSpan());
return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
@ -137,8 +135,8 @@ namespace Emby.Naming.Video
/// <returns>True if is video file stub.</returns>
public bool IsStubFile(string path)
{
var extension = Path.GetExtension(path);
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
var extension = Path.GetExtension(path.AsSpan());
return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
}
/// <summary>

@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase
CachePath = cacheDirectoryPath;
WebPath = webDirectoryPath;
DataPath = Path.Combine(ProgramDataPath, "data");
_dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
}
/// <summary>
@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the folder path to the data directory.
/// </summary>
/// <value>The data directory.</value>
public string DataPath
{
get => _dataPath;
private set => _dataPath = Directory.CreateDirectory(value).FullName;
}
public string DataPath => _dataPath;
/// <inheritdoc />
public string VirtualDataPath => "%AppDataPath%";

@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

@ -1,9 +1,6 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -335,10 +337,7 @@ namespace Emby.Server.Implementations
{
get
{
if (_deviceId == null)
{
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
}
_deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
return _deviceId.Value;
}
@ -370,10 +369,7 @@ namespace Emby.Server.Implementations
/// <returns>System.Object.</returns>
protected object CreateInstanceSafe(Type type)
{
if (_creatingInstances == null)
{
_creatingInstances = new List<Type>();
}
_creatingInstances ??= new List<Type>();
if (_creatingInstances.IndexOf(type) != -1)
{
@ -607,12 +603,8 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
ServiceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
ServiceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
ServiceCollection.AddSingleton<EncodingHelper>();
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
@ -677,8 +669,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
ServiceCollection.AddSingleton<EncodingHelper>();
ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
ServiceCollection.AddSingleton<TranscodingJobHelper>();

@ -1,9 +1,10 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.GroupBy(x => x!.Id) // We removed the null values
.Select(x => x.First())
.ToList();
.ToList()!; // Again... the list doesn't contain any null values
}
/// <inheritdoc />

@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@ -8,11 +9,9 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@ -124,7 +123,7 @@ namespace Emby.Server.Implementations.Collections
private IEnumerable<BoxSet> GetCollections(User user)
{
var folder = GetCollectionsFolder(false).Result;
var folder = GetCollectionsFolder(false).GetAwaiter().GetResult();
return folder == null
? Enumerable.Empty<BoxSet>()
@ -167,7 +166,7 @@ namespace Emby.Server.Implementations.Collections
parentFolder.AddChild(collection, CancellationToken.None);
if (options.ItemIdList.Length > 0)
if (options.ItemIdList.Count > 0)
{
await AddToCollectionAsync(
collection.Id,
@ -251,11 +250,7 @@ namespace Emby.Server.Implementations.Collections
if (fireEvent)
{
ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs
{
Collection = collection,
ItemsChanged = itemList
});
ItemsAddedToCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
}
}
}
@ -307,11 +302,7 @@ namespace Emby.Server.Implementations.Collections
},
RefreshPriority.High);
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
{
Collection = collection,
ItemsChanged = itemList
});
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs(collection, itemList));
}
/// <inheritdoc />
@ -319,11 +310,11 @@ namespace Emby.Server.Implementations.Collections
{
var results = new Dictionary<Guid, BaseItem>();
var allBoxsets = GetCollections(user).ToList();
var allBoxSets = GetCollections(user).ToList();
foreach (var item in items)
{
if (!(item is ISupportsBoxSetGrouping))
if (item is not ISupportsBoxSetGrouping)
{
results[item.Id] = item;
}
@ -331,34 +322,45 @@ namespace Emby.Server.Implementations.Collections
{
var itemId = item.Id;
var currentBoxSets = allBoxsets
.Where(i => i.ContainsLinkedChildByItemId(itemId))
.ToList();
if (currentBoxSets.Count > 0)
var itemIsInBoxSet = false;
foreach (var boxSet in allBoxSets)
{
foreach (var boxset in currentBoxSets)
if (!boxSet.ContainsLinkedChildByItemId(itemId))
{
results[boxset.Id] = boxset;
continue;
}
itemIsInBoxSet = true;
results.TryAdd(boxSet.Id, boxSet);
}
else
// skip any item that is in a box set
if (itemIsInBoxSet)
{
var alreadyInResults = false;
foreach (var child in item.GetMediaSources(true))
continue;
}
var alreadyInResults = false;
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video)
{
foreach (var childId in video.GetLocalAlternateVersionIds())
{
if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
if (!results.ContainsKey(childId))
{
alreadyInResults = true;
break;
continue;
}
}
if (!alreadyInResults)
{
results[item.Id] = item;
alreadyInResults = true;
break;
}
}
if (!alreadyInResults)
{
results[itemId] = item;
}
}
}

@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Globalization;
using System.IO;

@ -1,5 +1,4 @@
using System.Collections.Generic;
using Emby.Server.Implementations.HttpServer;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations

@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Security.Cryptography;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -181,11 +183,9 @@ namespace Emby.Server.Implementations.Data
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
{
if (row[1].SQLiteType != SQLiteType.Null)
if (row.TryGetString(1, out var columnName))
{
var name = row[1].ToString();
columnNames.Add(name);
columnNames.Add(columnName);
}
}

@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data
{
public class ManagedConnection : IDisposable
{
private SQLiteDatabaseConnection _db;
private SQLiteDatabaseConnection? _db;
private readonly SemaphoreSlim _writeLock;
private bool _disposed = false;
@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data
return _db.RunInTransaction(action, mode);
}
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql)
public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql)
{
return _db.Query(sql);
}
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql, params object[] values)
public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql, params object[] values)
{
return _db.Query(sql, values);
}

@ -1,3 +1,4 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Data
});
}
public static Guid ReadGuidFromBlob(this IResultSetValue result)
public static Guid ReadGuidFromBlob(this ResultSetValue result)
{
return new Guid(result.ToBlob());
}
@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data
private static string GetDateTimeKindFormat(DateTimeKind kind)
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
public static DateTime ReadDateTime(this IResultSetValue result)
public static DateTime ReadDateTime(this ResultSetValue result)
{
var dateText = result.ToString();
@ -96,49 +97,139 @@ namespace Emby.Server.Implementations.Data
DateTimeStyles.None).ToUniversalTime();
}
public static DateTime? TryReadDateTime(this IResultSetValue result)
public static bool TryReadDateTime(this IReadOnlyList<ResultSetValue> reader, int index, out DateTime result)
{
var dateText = result.ToString();
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
var dateText = item.ToString();
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
{
return dateTimeResult.ToUniversalTime();
result = dateTimeResult.ToUniversalTime();
return true;
}
result = default;
return false;
}
public static bool TryGetGuid(this IReadOnlyList<ResultSetValue> reader, int index, out Guid result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
return null;
result = item.ReadGuidFromBlob();
return true;
}
public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index)
public static bool IsDbNull(this ResultSetValue result)
{
return result[index].SQLiteType == SQLiteType.Null;
return result.SQLiteType == SQLiteType.Null;
}
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
public static string GetString(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ToString();
}
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
public static bool TryGetString(this IReadOnlyList<ResultSetValue> reader, int index, out string result)
{
result = null;
var item = reader[index];
if (item.IsDbNull())
{
return false;
}
result = item.ToString();
return true;
}
public static bool GetBoolean(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ToBool();
}
public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index)
public static bool TryGetBoolean(this IReadOnlyList<ResultSetValue> reader, int index, out bool result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToBool();
return true;
}
public static bool TryGetInt32(this IReadOnlyList<ResultSetValue> reader, int index, out int result)
{
return result[index].ToInt();
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToInt();
return true;
}
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
public static long GetInt64(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ToInt64();
}
public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index)
public static bool TryGetInt64(this IReadOnlyList<ResultSetValue> reader, int index, out long result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToInt64();
return true;
}
public static bool TryGetSingle(this IReadOnlyList<ResultSetValue> reader, int index, out float result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToFloat();
return true;
}
public static bool TryGetDouble(this IReadOnlyList<ResultSetValue> reader, int index, out double result)
{
return result[index].ToFloat();
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToDouble();
return true;
}
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)
public static Guid GetGuid(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ReadGuidFromBlob();
}
@ -350,7 +441,7 @@ namespace Emby.Server.Implementations.Data
}
}
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
public static IEnumerable<IReadOnlyList<ResultSetValue>> ExecuteQuery(this IStatement statement)
{
while (statement.MoveNext())
{

File diff suppressed because it is too large Load Diff

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -348,16 +350,16 @@ namespace Emby.Server.Implementations.Data
/// Read a row from the specified reader into the provided userData object.
/// </summary>
/// <param name="reader"></param>
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
{
var userData = new UserItemData();
userData.Key = reader[0].ToString();
// userData.UserId = reader[1].ReadGuidFromBlob();
if (reader[2].SQLiteType != SQLiteType.Null)
if (reader.TryGetDouble(2, out var rating))
{
userData.Rating = reader[2].ToDouble();
userData.Rating = rating;
}
userData.Played = reader[3].ToBool();
@ -365,19 +367,19 @@ namespace Emby.Server.Implementations.Data
userData.IsFavorite = reader[5].ToBool();
userData.PlaybackPositionTicks = reader[6].ToInt64();
if (reader[7].SQLiteType != SQLiteType.Null)
if (reader.TryReadDateTime(7, out var lastPlayedDate))
{
userData.LastPlayedDate = reader[7].TryReadDateTime();
userData.LastPlayedDate = lastPlayedDate;
}
if (reader[8].SQLiteType != SQLiteType.Null)
if (reader.TryGetInt32(8, out var audioStreamIndex))
{
userData.AudioStreamIndex = reader[8].ToInt();
userData.AudioStreamIndex = audioStreamIndex;
}
if (reader[9].SQLiteType != SQLiteType.Null)
if (reader.TryGetInt32(9, out var subtitleStreamIndex))
{
userData.SubtitleStreamIndex = reader[9].ToInt();
userData.SubtitleStreamIndex = subtitleStreamIndex;
}
return userData;

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data
/// This holds all the types in the running assemblies
/// so that we can de-serialize properly when we don't have strong types.
/// </summary>
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>();
private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
/// <summary>
/// Gets the type.
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data
/// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns>
/// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
public Type GetType(string typeName)
public Type? GetType(string typeName)
{
if (string.IsNullOrEmpty(typeName))
{
@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data
/// </summary>
/// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns>
private Type LookupType(string typeName)
private Type? LookupType(string typeName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetType(typeName))

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -665,10 +667,7 @@ namespace Emby.Server.Implementations.Dto
var tag = GetImageCacheTag(item, image);
if (!string.IsNullOrEmpty(image.BlurHash))
{
if (dto.ImageBlurHashes == null)
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
if (!dto.ImageBlurHashes.ContainsKey(image.Type))
{
@ -702,10 +701,7 @@ namespace Emby.Server.Implementations.Dto
if (hashes.Count > 0)
{
if (dto.ImageBlurHashes == null)
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
dto.ImageBlurHashes[imageType] = hashes;
}
@ -898,10 +894,7 @@ namespace Emby.Server.Implementations.Dto
dto.Taglines = new string[] { item.Tagline };
}
if (dto.Taglines == null)
{
dto.Taglines = Array.Empty<string>();
}
dto.Taglines ??= Array.Empty<string>();
}
dto.Type = item.GetBaseItemKind();

@ -9,6 +9,7 @@
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@ -27,11 +28,11 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
<PackageReference Include="sharpcompress" Version="0.28.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
<PackageReference Include="sharpcompress" Version="0.28.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.0.1" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup>
@ -44,6 +45,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
<NoWarn>AD0001</NoWarn>
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Net.Sockets;
using System.Threading;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,6 +1,5 @@
#pragma warning disable CS1591
using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;

@ -2,8 +2,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
{
return (AuthorizationInfo)cached;
return (AuthorizationInfo)cached!; // Cache should never contain null
}
return GetAuthorization(requestContext);
@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security
}
private AuthorizationInfo GetAuthorizationInfoFromDictionary(
in Dictionary<string, string> auth,
in Dictionary<string, string>? auth,
in IHeaderDictionary headers,
in IQueryCollection queryString)
{
string deviceId = null;
string device = null;
string client = null;
string version = null;
string token = null;
string? deviceId = null;
string? device = null;
string? client = null;
string? version = null;
string? token = null;
if (auth != null)
{
@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq)
private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq)
{
var auth = httpReq.Request.Headers["X-Emby-Authorization"];
@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Request.Headers[HeaderNames.Authorization];
}
return GetAuthorization(auth);
return GetAuthorization(auth.Count > 0 ? auth[0] : null);
}
/// <summary>
@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
{
var auth = httpReq.Headers["X-Emby-Authorization"];
@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Headers[HeaderNames.Authorization];
}
return GetAuthorization(auth);
return GetAuthorization(auth.Count > 0 ? auth[0] : null);
}
/// <summary>
@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="authorizationHeader">The authorization header.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorization(string authorizationHeader)
private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
{
if (authorizationHeader == null)
{
return null;
}
var parts = authorizationHeader.Split(' ', 2);
var firstSpace = authorizationHeader.IndexOf(' ');
// There should be at least to parts
if (parts.Length != 2)
// There should be at least two parts
if (firstSpace == -1)
{
return null;
}
var acceptedNames = new[] { "MediaBrowser", "Emby" };
var name = authorizationHeader[..firstSpace];
// It has to be a digest request
if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase))
if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
&& !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Remove uptil the first space
authorizationHeader = parts[1];
parts = authorizationHeader.Split(',');
authorizationHeader = authorizationHeader[(firstSpace + 1)..];
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in parts)
foreach (var item in authorizationHeader.Split(','))
{
var param = item.Trim().Split('=', 2);
var trimmedItem = item.Trim();
var firstEqualsSign = trimmedItem.IndexOf('=');
if (param.Length == 2)
if (firstEqualsSign > 0)
{
var value = NormalizeValue(param[1].Trim('"'));
result[param[0]] = value;
var key = trimmedItem[..firstEqualsSign].ToString();
var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
result[key] = value;
}
}

@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
return GetSession((HttpContext)requestContext);
}
public User GetUser(HttpContext requestContext)
public User? GetUser(HttpContext requestContext)
{
var session = GetSession(requestContext);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
public User GetUser(object requestContext)
public User? GetUser(object requestContext)
{
return GetUser(((HttpRequest)requestContext).HttpContext);
}

@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Buffers;
using System.IO.Pipelines;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -2,11 +2,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private readonly string _tempPath;
private readonly bool _isEnvironmentCaseInsensitive;
private static readonly bool _isEnvironmentCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public ManagedFileSystem(
ILogger<ManagedFileSystem> logger,
@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO
{
Logger = logger;
_tempPath = applicationPaths.TempDirectory;
_isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows;
}
public virtual void AddShortcutHandler(IShortcutHandler handler)
@ -55,7 +52,7 @@ namespace Emby.Server.Implementations.IO
}
var extension = Path.GetExtension(filename);
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
@ -64,7 +61,7 @@ namespace Emby.Server.Implementations.IO
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">filename</exception>
public virtual string ResolveShortcut(string filename)
public virtual string? ResolveShortcut(string filename)
{
if (string.IsNullOrEmpty(filename))
{
@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO
}
var extension = Path.GetExtension(filename);
var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
return handler?.Resolve(filename);
}
@ -263,8 +260,6 @@ namespace Emby.Server.Implementations.IO
result.Exists = false;
}
}
result.DirectoryName = fileInfo.DirectoryName;
}
result.CreationTimeUtc = GetCreationTimeUtc(info);
@ -303,16 +298,37 @@ namespace Emby.Server.Implementations.IO
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">The filename is null.</exception>
public virtual string GetValidFilename(string filename)
public string GetValidFilename(string filename)
{
var builder = new StringBuilder(filename);
foreach (var c in Path.GetInvalidFileNameChars())
var invalid = Path.GetInvalidFileNameChars();
var first = filename.IndexOfAny(invalid);
if (first == -1)
{
builder = builder.Replace(c, ' ');
// Fast path for clean strings
return filename;
}
return builder.ToString();
return string.Create(
filename.Length,
(filename, invalid, first),
(chars, state) =>
{
state.filename.AsSpan().CopyTo(chars);
chars[state.first++] = ' ';
var len = chars.Length;
foreach (var c in state.invalid)
{
for (int i = state.first; i < len; i++)
{
if (chars[i] == c)
{
chars[i] = ' ';
}
}
}
});
}
/// <summary>
@ -585,7 +601,7 @@ namespace Emby.Server.Implementations.IO
return GetFiles(path, null, false, recursive);
}
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var enumerationOptions = GetEnumerationOptions(recursive);
@ -639,7 +655,7 @@ namespace Emby.Server.Implementations.IO
return GetFilePaths(path, null, false, recursive);
}
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var enumerationOptions = GetEnumerationOptions(recursive);
@ -684,20 +700,5 @@ namespace Emby.Server.Implementations.IO
AttributesToSkip = 0
};
}
private static void RunProcess(string path, string args, string workingDirectory)
{
using (var process = Process.Start(new ProcessStartInfo
{
Arguments = args,
FileName = path,
CreateNoWindow = true,
WorkingDirectory = workingDirectory,
WindowStyle = ProcessWindowStyle.Normal
}))
{
process.WaitForExit();
}
}
}
}

@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO
public string Extension => ".mblink";
public string Resolve(string shortcutPath)
public string? Resolve(string shortcutPath)
{
if (string.IsNullOrEmpty(shortcutPath))
{

@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO
{
public class StreamHelper : IStreamHelper
{
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try

@ -1,6 +1,4 @@
#pragma warning disable CS1591
#nullable enable
using System;
namespace Emby.Server.Implementations
{

@ -2,20 +2,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Images
{

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -191,7 +193,7 @@ namespace Emby.Server.Implementations.Images
InputPaths = GetStripCollageImagePaths(primaryItem, items).ToArray()
};
if (options.InputPaths.Length == 0)
if (options.InputPaths.Count == 0)
{
return null;
}

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,10 +1,11 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
@ -29,9 +31,7 @@ namespace Emby.Server.Implementations.Images
{
var subItem = i.Item2;
var episode = subItem as Episode;
if (episode != null)
if (subItem is Episode episode)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Linq;
using DotNet.Globbing;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -48,6 +50,7 @@ using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person;
using VideoResolver = Emby.Naming.Video.VideoResolver;
@ -175,10 +178,7 @@ namespace Emby.Server.Implementations.Library
{
lock (_rootFolderSyncLock)
{
if (_rootFolder == null)
{
_rootFolder = CreateRootFolder();
}
_rootFolder ??= CreateRootFolder();
}
}
@ -558,7 +558,6 @@ namespace Emby.Server.Implementations.Library
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{
Parent = parent,
Path = fullPath,
FileInfo = fileInfo,
CollectionType = collectionType,
LibraryOptions = libraryOptions
@ -684,7 +683,7 @@ namespace Emby.Server.Implementations.Library
foreach (var item in items)
{
ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService);
ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
}
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
@ -1163,7 +1162,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100);
}
_itemRepository.UpdateInheritedValues(cancellationToken);
_itemRepository.UpdateInheritedValues();
progress.Report(100);
}
@ -2517,7 +2516,7 @@ namespace Emby.Server.Implementations.Library
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{
var series = episode.Series;
bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
if (!isAbsoluteNaming.Value)
{
// In other words, no filter applied
@ -2529,9 +2528,23 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
// TODO nullable - what are we trying to do there with empty episodeInfo?
var episodeInfo = episode.IsFileProtocol
? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path)
: new Naming.TV.EpisodeInfo(episode.Path);
EpisodeInfo episodeInfo = null;
if (episode.IsFileProtocol)
{
episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
// Resolve from parent folder if it's not the Season folder
if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder))
{
episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming);
if (episodeInfo != null)
{
// add the container
episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.');
}
}
}
episodeInfo ??= new EpisodeInfo(episode.Path);
try
{
@ -2880,6 +2893,12 @@ namespace Emby.Server.Implementations.Library
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
{
UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc />
public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
{
if (!item.SupportsPeople)
{
@ -2887,6 +2906,8 @@ namespace Emby.Server.Implementations.Library
}
_itemRepository.UpdatePeople(item.Id, people);
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
}
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@ -2990,6 +3011,58 @@ namespace Emby.Server.Implementations.Library
}
}
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
var personEntity = GetPerson(person.Name);
// if PresentationUniqueKey is empty it's likely a new item.
if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
{
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
saveEntity = true;
}
foreach (var id in person.ProviderIds)
{
if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
{
personEntity.SetProviderId(id.Key, id.Value);
saveEntity = true;
}
}
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
personEntity.SetImage(
new ItemImageInfo
{
Path = person.ImageUrl,
Type = ImageType.Primary
},
0);
saveEntity = true;
itemUpdateType = ItemUpdateType.ImageUpdate;
}
if (saveEntity)
{
personsToSave.Add(personEntity);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
CreateItems(personsToSave, null, CancellationToken.None);
}
private void StartScanInBackground()
{
Task.Run(() =>

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@ -199,10 +201,15 @@ namespace Emby.Server.Implementations.Library
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
}
}
}
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList();
return SortMediaSources(list);
}
public MediaProtocol GetPathProtocol(string path)
@ -436,7 +443,7 @@ namespace Emby.Server.Implementations.Library
}
}
private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
{
@ -451,8 +458,9 @@ namespace Emby.Server.Implementations.Library
{
var stream = i.VideoStream;
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
return stream?.Width ?? 0;
})
.Where(i => i.Type != MediaSourceType.Placeholder)
.ToList();
}
@ -584,18 +592,9 @@ namespace Emby.Server.Implementations.Library
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
{
var info = _openStreams.Values.FirstOrDefault(i =>
{
var liveStream = i as ILiveStream;
if (liveStream != null)
{
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
}
return false;
});
var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
return Task.FromResult(info as IDirectStreamProvider);
return Task.FromResult(info.Value as IDirectStreamProvider);
}
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)

@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save