Merge branch 'master' into readonlyspan

This commit is contained in:
Bond-009 2020-07-24 09:40:44 +02:00 committed by GitHub
commit 3ff110984a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 791 additions and 629 deletions

View File

@ -11,6 +11,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace Emby.Dlna.Api namespace Emby.Dlna.Api
{ {
@ -108,7 +109,7 @@ namespace Emby.Dlna.Api
public string Filename { get; set; } public string Filename { get; set; }
} }
public class DlnaServerService : IService, IRequiresRequest public class DlnaServerService : IService
{ {
private const string XMLContentType = "text/xml; charset=UTF-8"; private const string XMLContentType = "text/xml; charset=UTF-8";
@ -127,11 +128,13 @@ namespace Emby.Dlna.Api
public DlnaServerService( public DlnaServerService(
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
IHttpResultFactory httpResultFactory, IHttpResultFactory httpResultFactory,
IServerConfigurationManager configurationManager) IServerConfigurationManager configurationManager,
IHttpContextAccessor httpContextAccessor)
{ {
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_resultFactory = httpResultFactory; _resultFactory = httpResultFactory;
_configurationManager = configurationManager; _configurationManager = configurationManager;
Request = httpContextAccessor?.HttpContext.GetServiceStackRequest() ?? throw new ArgumentNullException(nameof(httpContextAccessor));
} }
private string GetHeader(string name) private string GetHeader(string name)

View File

@ -152,11 +152,15 @@ namespace Emby.Dlna.Eventing
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">"); builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
foreach (var key in stateVariables.Keys) foreach (var key in stateVariables.Keys)
{ {
builder.Append("<e:property>"); builder.Append("<e:property>")
builder.Append("<" + key + ">"); .Append('<')
builder.Append(stateVariables[key]); .Append(key)
builder.Append("</" + key + ">"); .Append('>')
builder.Append("</e:property>"); .Append(stateVariables[key])
.Append("</")
.Append(key)
.Append('>')
.Append("</e:property>");
} }
builder.Append("</e:propertyset>"); builder.Append("</e:propertyset>");

View File

@ -4,12 +4,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Server;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -334,7 +334,7 @@ namespace Emby.Dlna.PlayTo
return string.Empty; return string.Empty;
} }
return DescriptionXmlBuilder.Escape(value); return SecurityElement.Escape(value);
} }
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken) private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security;
using System.Text; using System.Text;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -64,10 +65,10 @@ namespace Emby.Dlna.Server
foreach (var att in attributes) foreach (var att in attributes)
{ {
builder.AppendFormat(" {0}=\"{1}\"", att.Name, att.Value); builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", att.Name, att.Value);
} }
builder.Append(">"); builder.Append('>');
builder.Append("<specVersion>"); builder.Append("<specVersion>");
builder.Append("<major>1</major>"); builder.Append("<major>1</major>");
@ -76,7 +77,9 @@ namespace Emby.Dlna.Server
if (!EnableAbsoluteUrls) if (!EnableAbsoluteUrls)
{ {
builder.Append("<URLBase>" + Escape(_serverAddress) + "</URLBase>"); builder.Append("<URLBase>")
.Append(SecurityElement.Escape(_serverAddress))
.Append("</URLBase>");
} }
AppendDeviceInfo(builder); AppendDeviceInfo(builder);
@ -93,91 +96,14 @@ namespace Emby.Dlna.Server
AppendIconList(builder); AppendIconList(builder);
builder.Append("<presentationURL>" + Escape(_serverAddress) + "/web/index.html</presentationURL>"); builder.Append("<presentationURL>")
.Append(SecurityElement.Escape(_serverAddress))
.Append("/web/index.html</presentationURL>");
AppendServiceList(builder); AppendServiceList(builder);
builder.Append("</device>"); builder.Append("</device>");
} }
private static readonly char[] s_escapeChars = new char[]
{
'<',
'>',
'"',
'\'',
'&'
};
private static readonly string[] s_escapeStringPairs = new[]
{
"<",
"&lt;",
">",
"&gt;",
"\"",
"&quot;",
"'",
"&apos;",
"&",
"&amp;"
};
private static string GetEscapeSequence(char c)
{
int num = s_escapeStringPairs.Length;
for (int i = 0; i < num; i += 2)
{
string text = s_escapeStringPairs[i];
string result = s_escapeStringPairs[i + 1];
if (text[0] == c)
{
return result;
}
}
return c.ToString(CultureInfo.InvariantCulture);
}
/// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary>
/// <returns>The input string with invalid characters replaced.</returns>
/// <param name="str">The string within which to escape invalid characters. </param>
public static string Escape(string str)
{
if (str == null)
{
return null;
}
StringBuilder stringBuilder = null;
int length = str.Length;
int num = 0;
while (true)
{
int num2 = str.IndexOfAny(s_escapeChars, num);
if (num2 == -1)
{
break;
}
if (stringBuilder == null)
{
stringBuilder = new StringBuilder();
}
stringBuilder.Append(str, num, num2 - num);
stringBuilder.Append(GetEscapeSequence(str[num2]));
num = num2 + 1;
}
if (stringBuilder == null)
{
return str;
}
stringBuilder.Append(str, num, length - num);
return stringBuilder.ToString();
}
private void AppendDeviceProperties(StringBuilder builder) private void AppendDeviceProperties(StringBuilder builder)
{ {
builder.Append("<dlna:X_DLNACAP/>"); builder.Append("<dlna:X_DLNACAP/>");
@ -187,32 +113,54 @@ namespace Emby.Dlna.Server
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"); builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
builder.Append("<friendlyName>" + Escape(GetFriendlyName()) + "</friendlyName>"); builder.Append("<friendlyName>")
builder.Append("<manufacturer>" + Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>"); .Append(SecurityElement.Escape(GetFriendlyName()))
builder.Append("<manufacturerURL>" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "</manufacturerURL>"); .Append("</friendlyName>");
builder.Append("<manufacturer>")
.Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty))
.Append("</manufacturer>");
builder.Append("<manufacturerURL>")
.Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty))
.Append("</manufacturerURL>");
builder.Append("<modelDescription>" + Escape(_profile.ModelDescription ?? string.Empty) + "</modelDescription>"); builder.Append("<modelDescription>")
builder.Append("<modelName>" + Escape(_profile.ModelName ?? string.Empty) + "</modelName>"); .Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty))
.Append("</modelDescription>");
builder.Append("<modelName>")
.Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty))
.Append("</modelName>");
builder.Append("<modelNumber>" + Escape(_profile.ModelNumber ?? string.Empty) + "</modelNumber>"); builder.Append("<modelNumber>")
builder.Append("<modelURL>" + Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>"); .Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty))
.Append("</modelNumber>");
builder.Append("<modelURL>")
.Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty))
.Append("</modelURL>");
if (string.IsNullOrEmpty(_profile.SerialNumber)) if (string.IsNullOrEmpty(_profile.SerialNumber))
{ {
builder.Append("<serialNumber>" + Escape(_serverId) + "</serialNumber>"); builder.Append("<serialNumber>")
.Append(SecurityElement.Escape(_serverId))
.Append("</serialNumber>");
} }
else else
{ {
builder.Append("<serialNumber>" + Escape(_profile.SerialNumber) + "</serialNumber>"); builder.Append("<serialNumber>")
.Append(SecurityElement.Escape(_profile.SerialNumber))
.Append("</serialNumber>");
} }
builder.Append("<UPC/>"); builder.Append("<UPC/>");
builder.Append("<UDN>uuid:" + Escape(_serverUdn) + "</UDN>"); builder.Append("<UDN>uuid:")
.Append(SecurityElement.Escape(_serverUdn))
.Append("</UDN>");
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags)) if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
{ {
builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>"); builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">")
.Append(SecurityElement.Escape(_profile.SonyAggregationFlags))
.Append("</av:aggregationFlags>");
} }
} }
@ -250,11 +198,21 @@ namespace Emby.Dlna.Server
{ {
builder.Append("<icon>"); builder.Append("<icon>");
builder.Append("<mimetype>" + Escape(icon.MimeType ?? string.Empty) + "</mimetype>"); builder.Append("<mimetype>")
builder.Append("<width>" + Escape(icon.Width.ToString(_usCulture)) + "</width>"); .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
builder.Append("<height>" + Escape(icon.Height.ToString(_usCulture)) + "</height>"); .Append("</mimetype>");
builder.Append("<depth>" + Escape(icon.Depth ?? string.Empty) + "</depth>"); builder.Append("<width>")
builder.Append("<url>" + BuildUrl(icon.Url) + "</url>"); .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture)))
.Append("</width>");
builder.Append("<height>")
.Append(SecurityElement.Escape(icon.Height.ToString(_usCulture)))
.Append("</height>");
builder.Append("<depth>")
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
.Append("</depth>");
builder.Append("<url>")
.Append(BuildUrl(icon.Url))
.Append("</url>");
builder.Append("</icon>"); builder.Append("</icon>");
} }
@ -270,11 +228,21 @@ namespace Emby.Dlna.Server
{ {
builder.Append("<service>"); builder.Append("<service>");
builder.Append("<serviceType>" + Escape(service.ServiceType ?? string.Empty) + "</serviceType>"); builder.Append("<serviceType>")
builder.Append("<serviceId>" + Escape(service.ServiceId ?? string.Empty) + "</serviceId>"); .Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
builder.Append("<SCPDURL>" + BuildUrl(service.ScpdUrl) + "</SCPDURL>"); .Append("</serviceType>");
builder.Append("<controlURL>" + BuildUrl(service.ControlUrl) + "</controlURL>"); builder.Append("<serviceId>")
builder.Append("<eventSubURL>" + BuildUrl(service.EventSubUrl) + "</eventSubURL>"); .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
.Append("</serviceId>");
builder.Append("<SCPDURL>")
.Append(BuildUrl(service.ScpdUrl))
.Append("</SCPDURL>");
builder.Append("<controlURL>")
.Append(BuildUrl(service.ControlUrl))
.Append("</controlURL>");
builder.Append("<eventSubURL>")
.Append(BuildUrl(service.EventSubUrl))
.Append("</eventSubURL>");
builder.Append("</service>"); builder.Append("</service>");
} }
@ -298,7 +266,7 @@ namespace Emby.Dlna.Server
url = _serverAddress.TrimEnd('/') + url; url = _serverAddress.TrimEnd('/') + url;
} }
return Escape(url); return SecurityElement.Escape(url);
} }
private IEnumerable<DeviceIcon> GetIcons() private IEnumerable<DeviceIcon> GetIcons()

View File

@ -1,9 +1,9 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.Security;
using System.Text; using System.Text;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Server;
namespace Emby.Dlna.Service namespace Emby.Dlna.Service
{ {
@ -37,7 +37,9 @@ namespace Emby.Dlna.Service
{ {
builder.Append("<action>"); builder.Append("<action>");
builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>"); builder.Append("<name>")
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
.Append("</name>");
builder.Append("<argumentList>"); builder.Append("<argumentList>");
@ -45,9 +47,15 @@ namespace Emby.Dlna.Service
{ {
builder.Append("<argument>"); builder.Append("<argument>");
builder.Append("<name>" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "</name>"); builder.Append("<name>")
builder.Append("<direction>" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "</direction>"); .Append(SecurityElement.Escape(argument.Name ?? string.Empty))
builder.Append("<relatedStateVariable>" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>"); .Append("</name>");
builder.Append("<direction>")
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
.Append("</direction>");
builder.Append("<relatedStateVariable>")
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
.Append("</relatedStateVariable>");
builder.Append("</argument>"); builder.Append("</argument>");
} }
@ -68,17 +76,25 @@ namespace Emby.Dlna.Service
{ {
var sendEvents = item.SendsEvents ? "yes" : "no"; var sendEvents = item.SendsEvents ? "yes" : "no";
builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">"); builder.Append("<stateVariable sendEvents=\"")
.Append(sendEvents)
.Append("\">");
builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>"); builder.Append("<name>")
builder.Append("<dataType>" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "</dataType>"); .Append(SecurityElement.Escape(item.Name ?? string.Empty))
.Append("</name>");
builder.Append("<dataType>")
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
.Append("</dataType>");
if (item.AllowedValues.Length > 0) if (item.AllowedValues.Length > 0)
{ {
builder.Append("<allowedValueList>"); builder.Append("<allowedValueList>");
foreach (var allowedValue in item.AllowedValues) foreach (var allowedValue in item.AllowedValues)
{ {
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>"); builder.Append("<allowedValue>")
.Append(SecurityElement.Escape(allowedValue))
.Append("</allowedValue>");
} }
builder.Append("</allowedValueList>"); builder.Append("</allowedValueList>");

View File

@ -136,8 +136,8 @@ namespace Emby.Naming.Common
CleanDateTimes = new[] CleanDateTimes = new[]
{ {
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*", @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*" @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
}; };
CleanStrings = new[] CleanStrings = new[]
@ -277,7 +277,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives, // This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first. // so we make sure this one gets tested first.
// "Foo Bar 889" // "Foo Bar 889"
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/x]*$") new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
{ {
IsNamed = true IsNamed = true
}, },
@ -300,32 +300,32 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming // *** End Kodi Standard Naming
// [bar] Foo - 1 [baz] // [bar] Foo - 1 [baz]
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$") new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>[0-9]+).*$")
{ {
IsNamed = true IsNamed = true
}, },
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$") new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$")
{ {
IsNamed = true IsNamed = true
}, },
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$") new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>[0-9]+)[x,X]?[eE](?<epnumber>[0-9]+)[^\\\/]*$")
{ {
IsNamed = true IsNamed = true
}, },
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$") new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]+))[^\\\/]*$")
{ {
IsNamed = true IsNamed = true
}, },
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$") new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]+)[^\\\/]*$")
{ {
IsNamed = true IsNamed = true
}, },
// "01.avi" // "01.avi"
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$") new EpisodeExpression(@".*[\\\/](?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))*\.\w+$")
{ {
IsOptimistic = true, IsOptimistic = true,
IsNamed = true IsNamed = true
@ -335,34 +335,34 @@ namespace Emby.Naming.Common
new EpisodeExpression(@"([0-9]+)-([0-9]+)"), new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
// "01 - blah.avi", "01-blah.avi" // "01 - blah.avi", "01-blah.avi"
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$") new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
{ {
IsOptimistic = true, IsOptimistic = true,
IsNamed = true IsNamed = true
}, },
// "01.blah.avi" // "01.blah.avi"
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.[^\\\/]+$") new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\.[^\\\/]+$")
{ {
IsOptimistic = true, IsOptimistic = true,
IsNamed = true IsNamed = true
}, },
// "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah" // "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$") new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$")
{ {
IsOptimistic = true, IsOptimistic = true,
IsNamed = true IsNamed = true
}, },
// "01 episode title.avi" // "01 episode title.avi"
new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>\d{1,3})([^\\\/]*)$") new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>[0-9]{1,3})([^\\\/]*)$")
{ {
IsOptimistic = true, IsOptimistic = true,
IsNamed = true IsNamed = true
}, },
// "Episode 16", "Episode 16 - Title" // "Episode 16", "Episode 16 - Title"
new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$") new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$")
{ {
IsOptimistic = true, IsOptimistic = true,
IsNamed = true IsNamed = true
@ -625,17 +625,17 @@ namespace Emby.Naming.Common
AudioBookPartsExpressions = new[] AudioBookPartsExpressions = new[]
{ {
// Detect specified chapters, like CH 01 // Detect specified chapters, like CH 01
@"ch(?:apter)?[\s_-]?(?<chapter>\d+)", @"ch(?:apter)?[\s_-]?(?<chapter>[0-9]+)",
// Detect specified parts, like Part 02 // Detect specified parts, like Part 02
@"p(?:ar)?t[\s_-]?(?<part>\d+)", @"p(?:ar)?t[\s_-]?(?<part>[0-9]+)",
// Chapter is often beginning of filename // Chapter is often beginning of filename
@"^(?<chapter>\d+)", "^(?<chapter>[0-9]+)",
// Part if often ending of filename // Part if often ending of filename
@"(?<part>\d+)$", "(?<part>[0-9]+)$",
// Sometimes named as 0001_005 (chapter_part) // Sometimes named as 0001_005 (chapter_part)
@"(?<chapter>\d+)_(?<part>\d+)", "(?<chapter>[0-9]+)_(?<part>[0-9]+)",
// Some audiobooks are ripped from cd's, and will be named by disk number. // Some audiobooks are ripped from cd's, and will be named by disk number.
@"dis(?:c|k)[\s_-]?(?<chapter>\d+)" @"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)"
}; };
var extensions = VideoFileExtensions.ToList(); var extensions = VideoFileExtensions.ToList();
@ -675,16 +675,16 @@ namespace Emby.Naming.Common
MultipleEpisodeExpressions = new string[] MultipleEpisodeExpressions = new string[]
{ {
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})(-[xE]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$" @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$"
}.Select(i => new EpisodeExpression(i) }.Select(i => new EpisodeExpression(i)
{ {
IsNamed = true IsNamed = true

View File

@ -192,7 +192,7 @@ namespace Emby.Server.Implementations
/// Gets or sets the application paths. /// Gets or sets the application paths.
/// </summary> /// </summary>
/// <value>The application paths.</value> /// <value>The application paths.</value>
protected ServerApplicationPaths ApplicationPaths { get; set; } protected IServerApplicationPaths ApplicationPaths { get; set; }
/// <summary> /// <summary>
/// Gets or sets all concrete types. /// Gets or sets all concrete types.
@ -236,7 +236,7 @@ namespace Emby.Server.Implementations
/// Initializes a new instance of the <see cref="ApplicationHost" /> class. /// Initializes a new instance of the <see cref="ApplicationHost" /> class.
/// </summary> /// </summary>
public ApplicationHost( public ApplicationHost(
ServerApplicationPaths applicationPaths, IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IStartupOptions options, IStartupOptions options,
IFileSystem fileSystem, IFileSystem fileSystem,
@ -795,7 +795,6 @@ namespace Emby.Server.Implementations
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>()); Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>()); Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
} }

View File

@ -1110,7 +1110,8 @@ namespace Emby.Server.Implementations.Data
continue; continue;
} }
str.Append(ToValueString(i) + "|"); str.Append(ToValueString(i))
.Append('|');
} }
str.Length -= 1; // Remove last | str.Length -= 1; // Remove last |
@ -2471,7 +2472,7 @@ namespace Emby.Server.Implementations.Data
var item = query.SimilarTo; var item = query.SimilarTo;
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append("("); builder.Append('(');
if (string.IsNullOrEmpty(item.OfficialRating)) if (string.IsNullOrEmpty(item.OfficialRating))
{ {
@ -2509,7 +2510,7 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrEmpty(query.SearchTerm)) if (!string.IsNullOrEmpty(query.SearchTerm))
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append("("); builder.Append('(');
builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)"); builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)");
@ -5238,7 +5239,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{ {
if (i > 0) if (i > 0)
{ {
insertText.Append(","); insertText.Append(',');
} }
insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture)); insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture));
@ -6331,7 +6332,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
foreach (var column in _mediaAttachmentSaveColumns.Skip(1)) foreach (var column in _mediaAttachmentSaveColumns.Skip(1))
{ {
insertText.Append("@" + column + index + ","); insertText.Append('@')
.Append(column)
.Append(index)
.Append(',');
} }
insertText.Length -= 1; insertText.Length -= 1;

View File

@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IStreamHelper _streamHelper; private readonly IStreamHelper _streamHelper;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
/// <summary> /// <summary>
/// The _options. /// The _options.
@ -49,7 +48,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
_streamHelper = streamHelper; _streamHelper = streamHelper;
_fileSystem = fileSystem;
Path = path; Path = path;
_logger = logger; _logger = logger;

View File

@ -13,26 +13,22 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.HttpServer.Security namespace Emby.Server.Implementations.HttpServer.Security
{ {
public class AuthService : IAuthService public class AuthService : IAuthService
{ {
private readonly ILogger<AuthService> _logger;
private readonly IAuthorizationContext _authorizationContext; private readonly IAuthorizationContext _authorizationContext;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
public AuthService( public AuthService(
ILogger<AuthService> logger,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
IServerConfigurationManager config, IServerConfigurationManager config,
ISessionManager sessionManager, ISessionManager sessionManager,
INetworkManager networkManager) INetworkManager networkManager)
{ {
_logger = logger;
_authorizationContext = authorizationContext; _authorizationContext = authorizationContext;
_config = config; _config = config;
_sessionManager = sessionManager; _sessionManager = sessionManager;

View File

@ -11,7 +11,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -25,14 +24,9 @@ namespace Emby.Server.Implementations.Images
/// </summary> /// </summary>
public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist> public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist>
{ {
/// <summary> public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor)
/// The library manager. : base(fileSystem, providerManager, applicationPaths, imageProcessor)
/// </summary>
private readonly ILibraryManager _libraryManager;
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{ {
_libraryManager = libraryManager;
} }
/// <summary> /// <summary>

View File

@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library
if (parent != null) if (parent != null)
{ {
// Don't resolve these into audio files // Don't resolve these into audio files
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename, StringComparison.Ordinal)
&& _libraryManager.IsAudioFile(filename)) && _libraryManager.IsAudioFile(filename))
{ {
return true; return true;

View File

@ -11,6 +11,17 @@ namespace Emby.Server.Implementations.Library
{ {
public class ExclusiveLiveStream : ILiveStream public class ExclusiveLiveStream : ILiveStream
{ {
private readonly Func<Task> _closeFn;
public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
{
MediaSource = mediaSource;
EnableStreamSharing = false;
_closeFn = closeFn;
ConsumerCount = 1;
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
}
public int ConsumerCount { get; set; } public int ConsumerCount { get; set; }
public string OriginalStreamId { get; set; } public string OriginalStreamId { get; set; }
@ -21,18 +32,7 @@ namespace Emby.Server.Implementations.Library
public MediaSourceInfo MediaSource { get; set; } public MediaSourceInfo MediaSource { get; set; }
public string UniqueId { get; private set; } public string UniqueId { get; }
private Func<Task> _closeFn;
public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
{
MediaSource = mediaSource;
EnableStreamSharing = false;
_closeFn = closeFn;
ConsumerCount = 1;
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
}
public Task Close() public Task Close()
{ {

View File

@ -60,6 +60,8 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
public class LibraryManager : ILibraryManager public class LibraryManager : ILibraryManager
{ {
private const string ShortcutFileExtension = ".mblink";
private readonly ILogger<LibraryManager> _logger; private readonly ILogger<LibraryManager> _logger;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
@ -75,63 +77,24 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache; private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
/// <summary>
/// The _root folder sync lock.
/// </summary>
private readonly object _rootFolderSyncLock = new object();
private readonly object _userRootFolderSyncLock = new object();
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
private NamingOptions _namingOptions; private NamingOptions _namingOptions;
private string[] _videoFileExtensions; private string[] _videoFileExtensions;
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
private IProviderManager ProviderManager => _providerManagerFactory.Value;
private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
/// <summary> /// <summary>
/// Gets or sets the postscan tasks. /// The _root folder.
/// </summary> /// </summary>
/// <value>The postscan tasks.</value> private volatile AggregateFolder _rootFolder;
private ILibraryPostScanTask[] PostscanTasks { get; set; } private volatile UserRootFolder _userRootFolder;
/// <summary> private bool _wizardCompleted;
/// Gets or sets the intro providers.
/// </summary>
/// <value>The intro providers.</value>
private IIntroProvider[] IntroProviders { get; set; }
/// <summary>
/// Gets or sets the list of entity resolution ignore rules.
/// </summary>
/// <value>The entity resolution ignore rules.</value>
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
/// <summary>
/// Gets or sets the list of currently registered entity resolvers.
/// </summary>
/// <value>The entity resolvers enumerable.</value>
private IItemResolver[] EntityResolvers { get; set; }
private IMultiItemResolver[] MultiItemResolvers { get; set; }
/// <summary>
/// Gets or sets the comparers.
/// </summary>
/// <value>The comparers.</value>
private IBaseItemComparer[] Comparers { get; set; }
/// <summary>
/// Occurs when [item added].
/// </summary>
public event EventHandler<ItemChangeEventArgs> ItemAdded;
/// <summary>
/// Occurs when [item updated].
/// </summary>
public event EventHandler<ItemChangeEventArgs> ItemUpdated;
/// <summary>
/// Occurs when [item removed].
/// </summary>
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
public bool IsScanRunning { get; private set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LibraryManager" /> class. /// Initializes a new instance of the <see cref="LibraryManager" /> class.
@ -186,37 +149,19 @@ namespace Emby.Server.Implementations.Library
} }
/// <summary> /// <summary>
/// Adds the parts. /// Occurs when [item added].
/// </summary> /// </summary>
/// <param name="rules">The rules.</param> public event EventHandler<ItemChangeEventArgs> ItemAdded;
/// <param name="resolvers">The resolvers.</param>
/// <param name="introProviders">The intro providers.</param>
/// <param name="itemComparers">The item comparers.</param>
/// <param name="postscanTasks">The post scan tasks.</param>
public void AddParts(
IEnumerable<IResolverIgnoreRule> rules,
IEnumerable<IItemResolver> resolvers,
IEnumerable<IIntroProvider> introProviders,
IEnumerable<IBaseItemComparer> itemComparers,
IEnumerable<ILibraryPostScanTask> postscanTasks)
{
EntityResolutionIgnoreRules = rules.ToArray();
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
IntroProviders = introProviders.ToArray();
Comparers = itemComparers.ToArray();
PostscanTasks = postscanTasks.ToArray();
}
/// <summary> /// <summary>
/// The _root folder. /// Occurs when [item updated].
/// </summary> /// </summary>
private volatile AggregateFolder _rootFolder; public event EventHandler<ItemChangeEventArgs> ItemUpdated;
/// <summary> /// <summary>
/// The _root folder sync lock. /// Occurs when [item removed].
/// </summary> /// </summary>
private readonly object _rootFolderSyncLock = new object(); public event EventHandler<ItemChangeEventArgs> ItemRemoved;
/// <summary> /// <summary>
/// Gets the root folder. /// Gets the root folder.
@ -241,7 +186,68 @@ namespace Emby.Server.Implementations.Library
} }
} }
private bool _wizardCompleted; private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
private IProviderManager ProviderManager => _providerManagerFactory.Value;
private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
/// <summary>
/// Gets or sets the postscan tasks.
/// </summary>
/// <value>The postscan tasks.</value>
private ILibraryPostScanTask[] PostscanTasks { get; set; }
/// <summary>
/// Gets or sets the intro providers.
/// </summary>
/// <value>The intro providers.</value>
private IIntroProvider[] IntroProviders { get; set; }
/// <summary>
/// Gets or sets the list of entity resolution ignore rules.
/// </summary>
/// <value>The entity resolution ignore rules.</value>
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
/// <summary>
/// Gets or sets the list of currently registered entity resolvers.
/// </summary>
/// <value>The entity resolvers enumerable.</value>
private IItemResolver[] EntityResolvers { get; set; }
private IMultiItemResolver[] MultiItemResolvers { get; set; }
/// <summary>
/// Gets or sets the comparers.
/// </summary>
/// <value>The comparers.</value>
private IBaseItemComparer[] Comparers { get; set; }
public bool IsScanRunning { get; private set; }
/// <summary>
/// Adds the parts.
/// </summary>
/// <param name="rules">The rules.</param>
/// <param name="resolvers">The resolvers.</param>
/// <param name="introProviders">The intro providers.</param>
/// <param name="itemComparers">The item comparers.</param>
/// <param name="postscanTasks">The post scan tasks.</param>
public void AddParts(
IEnumerable<IResolverIgnoreRule> rules,
IEnumerable<IItemResolver> resolvers,
IEnumerable<IIntroProvider> introProviders,
IEnumerable<IBaseItemComparer> itemComparers,
IEnumerable<ILibraryPostScanTask> postscanTasks)
{
EntityResolutionIgnoreRules = rules.ToArray();
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
IntroProviders = introProviders.ToArray();
Comparers = itemComparers.ToArray();
PostscanTasks = postscanTasks.ToArray();
}
/// <summary> /// <summary>
/// Records the configuration values. /// Records the configuration values.
@ -775,14 +781,11 @@ namespace Emby.Server.Implementations.Library
return rootFolder; return rootFolder;
} }
private volatile UserRootFolder _userRootFolder;
private readonly object _syncLock = new object();
public Folder GetUserRootFolder() public Folder GetUserRootFolder()
{ {
if (_userRootFolder == null) if (_userRootFolder == null)
{ {
lock (_syncLock) lock (_userRootFolderSyncLock)
{ {
if (_userRootFolder == null) if (_userRootFolder == null)
{ {
@ -1332,7 +1335,7 @@ namespace Emby.Server.Implementations.Library
return new QueryResult<BaseItem> return new QueryResult<BaseItem>
{ {
Items = _itemRepository.GetItemList(query).ToArray() Items = _itemRepository.GetItemList(query)
}; };
} }
@ -1463,11 +1466,9 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItems(query); return _itemRepository.GetItems(query);
} }
var list = _itemRepository.GetItemList(query);
return new QueryResult<BaseItem> return new QueryResult<BaseItem>
{ {
Items = list Items = _itemRepository.GetItemList(query)
}; };
} }
@ -1876,7 +1877,8 @@ namespace Emby.Server.Implementations.Library
} }
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
if (outdated.Length == 0) // Skip image processing if current or live tv source
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{ {
RegisterItem(item); RegisterItem(item);
return; return;
@ -1945,12 +1947,9 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Updates the item. /// Updates the item.
/// </summary> /// </summary>
public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{ {
// Don't iterate multiple times foreach (var item in items)
var itemsList = items.ToList();
foreach (var item in itemsList)
{ {
if (item.IsFileProtocol) if (item.IsFileProtocol)
{ {
@ -1962,11 +1961,11 @@ namespace Emby.Server.Implementations.Library
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate); UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
} }
_itemRepository.SaveItems(itemsList, cancellationToken); _itemRepository.SaveItems(items, cancellationToken);
if (ItemUpdated != null) if (ItemUpdated != null)
{ {
foreach (var item in itemsList) foreach (var item in items)
{ {
// With the live tv guide this just creates too much noise // With the live tv guide this just creates too much noise
if (item.SourceType != SourceType.Library) if (item.SourceType != SourceType.Library)
@ -2189,8 +2188,6 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(i => !string.IsNullOrEmpty(i)); .FirstOrDefault(i => !string.IsNullOrEmpty(i));
} }
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
public UserView GetNamedView( public UserView GetNamedView(
User user, User user,
string name, string name,
@ -2488,14 +2485,9 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
var episodeInfo = episode.IsFileProtocol ? var episodeInfo = episode.IsFileProtocol
resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) : ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo()
new Naming.TV.EpisodeInfo(); : new Naming.TV.EpisodeInfo();
if (episodeInfo == null)
{
episodeInfo = new Naming.TV.EpisodeInfo();
}
try try
{ {
@ -2503,11 +2495,13 @@ namespace Emby.Server.Implementations.Library
if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase)) if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
{ {
// Read from metadata // Read from metadata
var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest var mediaInfo = _mediaEncoder.GetMediaInfo(
new MediaInfoRequest
{ {
MediaSource = episode.GetMediaSources(false)[0], MediaSource = episode.GetMediaSources(false)[0],
MediaType = DlnaProfileType.Video MediaType = DlnaProfileType.Video
}, CancellationToken.None).GetAwaiter().GetResult(); },
CancellationToken.None).GetAwaiter().GetResult();
if (mediaInfo.ParentIndexNumber > 0) if (mediaInfo.ParentIndexNumber > 0)
{ {
episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber; episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
@ -2665,7 +2659,7 @@ namespace Emby.Server.Implementations.Library
var videos = videoListResolver.Resolve(fileSystemChildren); var videos = videoListResolver.Resolve(fileSystemChildren);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase)); var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
if (currentVideo != null) if (currentVideo != null)
{ {
@ -2682,9 +2676,7 @@ namespace Emby.Server.Implementations.Library
.Select(video => .Select(video =>
{ {
// Try to retrieve it from the db. If we don't find it, use the resolved version // Try to retrieve it from the db. If we don't find it, use the resolved version
var dbItem = GetItemById(video.Id) as Trailer; if (GetItemById(video.Id) is Trailer dbItem)
if (dbItem != null)
{ {
video = dbItem; video = dbItem;
} }
@ -3011,8 +3003,6 @@ namespace Emby.Server.Implementations.Library
}); });
} }
private const string ShortcutFileExtension = ".mblink";
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
{ {
AddMediaPathInternal(virtualFolderName, pathInfo, true); AddMediaPathInternal(virtualFolderName, pathInfo, true);
@ -3206,7 +3196,8 @@ namespace Emby.Server.Implementations.Library
if (!Directory.Exists(virtualFolderPath)) if (!Directory.Exists(virtualFolderPath))
{ {
throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName)); throw new FileNotFoundException(
string.Format(CultureInfo.InvariantCulture, "The media collection {0} does not exist", virtualFolderName));
} }
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true) var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)

View File

@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.Library
{ {
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IJsonSerializer _json;
private IJsonSerializer _json; private readonly IApplicationPaths _appPaths;
private IApplicationPaths _appPaths;
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths) public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
{ {
@ -72,13 +71,14 @@ namespace Emby.Server.Implementations.Library
mediaSource.AnalyzeDurationMs = 3000; mediaSource.AnalyzeDurationMs = 3000;
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest mediaInfo = await _mediaEncoder.GetMediaInfo(
new MediaInfoRequest
{ {
MediaSource = mediaSource, MediaSource = mediaSource,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
ExtractChapters = false ExtractChapters = false
},
}, cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
if (cacheFilePath != null) if (cacheFilePath != null)
{ {
@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.RunTimeTicks = null; mediaSource.RunTimeTicks = null;
} }
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
if (audioStream == null || audioStream.Index == -1) if (audioStream == null || audioStream.Index == -1)
{ {
@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.DefaultAudioStreamIndex = audioStream.Index; mediaSource.DefaultAudioStreamIndex = audioStream.Index;
} }
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null) if (videoStream != null)
{ {
if (!videoStream.BitRate.HasValue) if (!videoStream.BitRate.HasValue)

View File

@ -29,6 +29,9 @@ namespace Emby.Server.Implementations.Library
{ {
public class MediaSourceManager : IMediaSourceManager, IDisposable public class MediaSourceManager : IMediaSourceManager, IDisposable
{ {
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char LiveStreamIdDelimeter = '_';
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -40,6 +43,11 @@ namespace Emby.Server.Implementations.Library
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
private readonly object _disposeLock = new object();
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
public MediaSourceManager( public MediaSourceManager(
@ -368,7 +376,6 @@ namespace Emby.Server.Implementations.Library
} }
} }
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference) var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference); ? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
@ -451,9 +458,6 @@ namespace Emby.Server.Implementations.Library
.ToList(); .ToList();
} }
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
{ {
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
@ -855,9 +859,6 @@ namespace Emby.Server.Implementations.Library
} }
} }
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char LiveStreamIdDelimeter = '_';
private Tuple<IMediaSourceProvider, string> GetProvider(string key) private Tuple<IMediaSourceProvider, string> GetProvider(string key)
{ {
if (string.IsNullOrEmpty(key)) if (string.IsNullOrEmpty(key))
@ -884,7 +885,6 @@ namespace Emby.Server.Implementations.Library
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private readonly object _disposeLock = new object();
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>

View File

@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
} }
// load forced subs if we have found no suitable full subtitles // load forced subs if we have found no suitable full subtitles
stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)); stream ??= streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
if (stream != null) if (stream != null)
{ {

View File

@ -1,6 +1,5 @@
using System.Globalization; using System.Globalization;
using Emby.Naming.TV; using Emby.Naming.TV;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
@ -13,7 +12,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary> /// </summary>
public class SeasonResolver : FolderResolver<Season> public class SeasonResolver : FolderResolver<Season>
{ {
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ILogger<SeasonResolver> _logger; private readonly ILogger<SeasonResolver> _logger;
@ -21,17 +19,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class. /// Initializes a new instance of the <see cref="SeasonResolver"/> class.
/// </summary> /// </summary>
/// <param name="config">The config.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <param name="localization">The localization.</param> /// <param name="localization">The localization.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public SeasonResolver( public SeasonResolver(
IServerConfigurationManager config,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILocalizationManager localization, ILocalizationManager localization,
ILogger<SeasonResolver> logger) ILogger<SeasonResolver> logger)
{ {
_config = config;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_localization = localization; _localization = localization;
_logger = logger; _logger = logger;

View File

@ -20,13 +20,11 @@ namespace Emby.Server.Implementations.Library
{ {
public class SearchEngine : ISearchEngine public class SearchEngine : ISearchEngine
{ {
private readonly ILogger<SearchEngine> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager) public SearchEngine(ILibraryManager libraryManager, IUserManager userManager)
{ {
_logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
} }
@ -34,11 +32,7 @@ namespace Emby.Server.Implementations.Library
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query) public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
{ {
User user = null; User user = null;
if (query.UserId != Guid.Empty)
if (query.UserId.Equals(Guid.Empty))
{
}
else
{ {
user = _userManager.GetUserById(query.UserId); user = _userManager.GetUserById(query.UserId);
} }
@ -48,19 +42,19 @@ namespace Emby.Server.Implementations.Library
if (query.StartIndex.HasValue) if (query.StartIndex.HasValue)
{ {
results = results.Skip(query.StartIndex.Value).ToList(); results = results.GetRange(query.StartIndex.Value, totalRecordCount - query.StartIndex.Value);
} }
if (query.Limit.HasValue) if (query.Limit.HasValue)
{ {
results = results.Take(query.Limit.Value).ToList(); results = results.GetRange(0, query.Limit.Value);
} }
return new QueryResult<SearchHintInfo> return new QueryResult<SearchHintInfo>
{ {
TotalRecordCount = totalRecordCount, TotalRecordCount = totalRecordCount,
Items = results.ToArray() Items = results
}; };
} }
@ -85,7 +79,7 @@ namespace Emby.Server.Implementations.Library
if (string.IsNullOrEmpty(searchTerm)) if (string.IsNullOrEmpty(searchTerm))
{ {
throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm)); throw new ArgumentException("SearchTerm can't be empty.", nameof(query));
} }
searchTerm = searchTerm.Trim().RemoveDiacritics(); searchTerm = searchTerm.Trim().RemoveDiacritics();

View File

@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book; using Book = MediaBrowser.Controller.Entities.Book;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
@ -28,18 +27,15 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary<string, UserItemData> _userData = private readonly ConcurrentDictionary<string, UserItemData> _userData =
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger<UserDataManager> _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IUserDataRepository _repository; private readonly IUserDataRepository _repository;
public UserDataManager( public UserDataManager(
ILogger<UserDataManager> logger,
IServerConfigurationManager config, IServerConfigurationManager config,
IUserManager userManager, IUserManager userManager,
IUserDataRepository repository) IUserDataRepository repository)
{ {
_logger = logger;
_config = config; _config = config;
_userManager = userManager; _userManager = userManager;
_repository = repository; _repository = repository;

View File

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
@ -28,7 +29,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Episode = MediaBrowser.Controller.Entities.TV.Episode;
@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.LiveTv
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IChannelManager _channelManager; private readonly IChannelManager _channelManager;
private readonly LiveTvDtoService _tvDtoService; private readonly LiveTvDtoService _tvDtoService;
@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv
ILibraryManager libraryManager, ILibraryManager libraryManager,
ITaskManager taskManager, ITaskManager taskManager,
ILocalizationManager localization, ILocalizationManager localization,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem, IFileSystem fileSystem,
IChannelManager channelManager, IChannelManager channelManager,
LiveTvDtoService liveTvDtoService) LiveTvDtoService liveTvDtoService)
@ -85,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv
_libraryManager = libraryManager; _libraryManager = libraryManager;
_taskManager = taskManager; _taskManager = taskManager;
_localization = localization; _localization = localization;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_dtoService = dtoService; _dtoService = dtoService;
_userDataManager = userDataManager; _userDataManager = userDataManager;
@ -2234,7 +2231,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{ {
info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info)); info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info));
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@ -2278,7 +2275,7 @@ namespace Emby.Server.Implementations.LiveTv
{ {
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider // Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider // ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
info = _jsonSerializer.DeserializeFromString<ListingsProviderInfo>(_jsonSerializer.SerializeToString(info)); info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));

View File

@ -7,7 +7,6 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -37,7 +36,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IApplicationPaths _applicationPaths; private readonly IApplicationPaths _applicationPaths;
private readonly ILogger<TaskManager> _logger; private readonly ILogger<TaskManager> _logger;
private readonly IFileSystem _fileSystem;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TaskManager" /> class. /// Initializes a new instance of the <see cref="TaskManager" /> class.
@ -45,17 +43,14 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="applicationPaths">The application paths.</param> /// <param name="applicationPaths">The application paths.</param>
/// <param name="jsonSerializer">The json serializer.</param> /// <param name="jsonSerializer">The json serializer.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="fileSystem">The filesystem manager.</param>
public TaskManager( public TaskManager(
IApplicationPaths applicationPaths, IApplicationPaths applicationPaths,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
ILogger<TaskManager> logger, ILogger<TaskManager> logger)
IFileSystem fileSystem)
{ {
_applicationPaths = applicationPaths; _applicationPaths = applicationPaths;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_logger = logger; _logger = logger;
_fileSystem = fileSystem;
ScheduledTasks = Array.Empty<IScheduledTaskWorker>(); ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
} }

View File

@ -14,7 +14,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks namespace Emby.Server.Implementations.ScheduledTasks
@ -24,11 +23,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary> /// </summary>
public class ChapterImagesTask : IScheduledTask public class ChapterImagesTask : IScheduledTask
{ {
/// <summary>
/// The _logger.
/// </summary>
private readonly ILogger<ChapterImagesTask> _logger;
/// <summary> /// <summary>
/// The _library manager. /// The _library manager.
/// </summary> /// </summary>
@ -46,7 +40,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
/// </summary> /// </summary>
public ChapterImagesTask( public ChapterImagesTask(
ILoggerFactory loggerFactory,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IItemRepository itemRepo, IItemRepository itemRepo,
IApplicationPaths appPaths, IApplicationPaths appPaths,
@ -54,7 +47,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
IFileSystem fileSystem, IFileSystem fileSystem,
ILocalizationManager localization) ILocalizationManager localization)
{ {
_logger = loggerFactory.CreateLogger<ChapterImagesTask>();
_libraryManager = libraryManager; _libraryManager = libraryManager;
_itemRepo = itemRepo; _itemRepo = itemRepo;
_appPaths = appPaths; _appPaths = appPaths;

View File

@ -189,5 +189,4 @@ namespace Emby.Server.Implementations.Services
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()); return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
} }
} }
} }

View File

@ -7,6 +7,7 @@ using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -83,6 +84,7 @@ namespace Emby.Server.Implementations.Services
httpHost.ApplyRequestFilters(httpReq, httpRes, request); httpHost.ApplyRequestFilters(httpReq, httpRes, request);
httpRes.HttpContext.SetServiceStackRequest(httpReq);
var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false); var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
// Apply response filters // Apply response filters

View File

@ -488,7 +488,8 @@ namespace Emby.Server.Implementations.Services
sb.Append(value); sb.Append(value);
for (var j = pathIx + 1; j < requestComponents.Length; j++) for (var j = pathIx + 1; j < requestComponents.Length; j++)
{ {
sb.Append(PathSeperatorChar + requestComponents[j]); sb.Append(PathSeperatorChar)
.Append(requestComponents[j]);
} }
value = sb.ToString(); value = sb.ToString();
@ -505,7 +506,8 @@ namespace Emby.Server.Implementations.Services
pathIx++; pathIx++;
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase)) while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{ {
sb.Append(PathSeperatorChar + requestComponents[pathIx++]); sb.Append(PathSeperatorChar)
.Append(requestComponents[pathIx++]);
} }
value = sb.ToString(); value = sb.ToString();

View File

@ -46,14 +46,12 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Configuration")] [HttpGet("Configuration")]
public StartupConfigurationDto GetStartupConfiguration() public StartupConfigurationDto GetStartupConfiguration()
{ {
var result = new StartupConfigurationDto return new StartupConfigurationDto
{ {
UICulture = _config.Configuration.UICulture, UICulture = _config.Configuration.UICulture,
MetadataCountryCode = _config.Configuration.MetadataCountryCode, MetadataCountryCode = _config.Configuration.MetadataCountryCode,
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
}; };
return result;
} }
/// <summary> /// <summary>
@ -92,10 +90,10 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <returns>The first user.</returns> /// <returns>The first user.</returns>
[HttpGet("User")] [HttpGet("User")]
public StartupUserDto GetFirstUser() public async Task<StartupUserDto> GetFirstUser()
{ {
// TODO: Remove this method when startup wizard no longer requires an existing user. // TODO: Remove this method when startup wizard no longer requires an existing user.
_userManager.Initialize(); await _userManager.InitializeAsync().ConfigureAwait(false);
var user = _userManager.Users.First(); var user = _userManager.Users.First();
return new StartupUserDto return new StartupUserDto
{ {

View File

@ -1,5 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using System.Linq; using System.Linq;
using Jellyfin.Data; using Jellyfin.Data;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
@ -133,6 +134,18 @@ namespace Jellyfin.Server.Implementations
return base.SaveChanges(); return base.SaveChanges();
} }
/// <inheritdoc/>
public override void Dispose()
{
foreach (var entry in ChangeTracker.Entries())
{
entry.State = EntityState.Detached;
}
GC.SuppressFinalize(this);
base.Dispose();
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -23,7 +24,7 @@ namespace Jellyfin.Server.Implementations.Users
private const string BaseResetFileName = "passwordreset"; private const string BaseResetFileName = "passwordreset";
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IUserManager _userManager; private readonly IApplicationHost _appHost;
private readonly string _passwordResetFileBase; private readonly string _passwordResetFileBase;
private readonly string _passwordResetFileBaseDir; private readonly string _passwordResetFileBaseDir;
@ -33,16 +34,17 @@ namespace Jellyfin.Server.Implementations.Users
/// </summary> /// </summary>
/// <param name="configurationManager">The configuration manager.</param> /// <param name="configurationManager">The configuration manager.</param>
/// <param name="jsonSerializer">The JSON serializer.</param> /// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="userManager">The user manager.</param> /// <param name="appHost">The application host.</param>
public DefaultPasswordResetProvider( public DefaultPasswordResetProvider(
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IUserManager userManager) IApplicationHost appHost)
{ {
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_userManager = userManager; _appHost = appHost;
// TODO: Remove the circular dependency on UserManager
} }
/// <inheritdoc /> /// <inheritdoc />
@ -54,6 +56,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc /> /// <inheritdoc />
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin) public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{ {
var userManager = _appHost.Resolve<IUserManager>();
var usersReset = new List<string>(); var usersReset = new List<string>();
foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
{ {
@ -72,10 +75,10 @@ namespace Jellyfin.Server.Implementations.Users
pin.Replace("-", string.Empty, StringComparison.Ordinal), pin.Replace("-", string.Empty, StringComparison.Ordinal),
StringComparison.InvariantCultureIgnoreCase)) StringComparison.InvariantCultureIgnoreCase))
{ {
var resetUser = _userManager.GetUserByName(spr.UserName) var resetUser = userManager.GetUserByName(spr.UserName)
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); await userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
usersReset.Add(resetUser.Username); usersReset.Add(resetUser.Username);
File.Delete(resetFile); File.Delete(resetFile);
} }
@ -121,7 +124,6 @@ namespace Jellyfin.Server.Implementations.Users
} }
user.EasyPassword = pin; user.EasyPassword = pin;
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
return new ForgotPasswordResult return new ForgotPasswordResult
{ {

View File

@ -39,12 +39,11 @@ namespace Jellyfin.Server.Implementations.Users
private readonly IApplicationHost _appHost; private readonly IApplicationHost _appHost;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly ILogger<UserManager> _logger; private readonly ILogger<UserManager> _logger;
private readonly IReadOnlyCollection<IPasswordResetProvider> _passwordResetProviders;
private IAuthenticationProvider[] _authenticationProviders = null!; private readonly IReadOnlyCollection<IAuthenticationProvider> _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider = null!; private readonly InvalidAuthProvider _invalidAuthProvider;
private InvalidAuthProvider _invalidAuthProvider = null!; private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
private IPasswordResetProvider[] _passwordResetProviders = null!; private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
private DefaultPasswordResetProvider _defaultPasswordResetProvider = null!;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserManager"/> class. /// Initializes a new instance of the <see cref="UserManager"/> class.
@ -69,6 +68,13 @@ namespace Jellyfin.Server.Implementations.Users
_appHost = appHost; _appHost = appHost;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_logger = logger; _logger = logger;
_passwordResetProviders = appHost.GetExports<IPasswordResetProvider>();
_authenticationProviders = appHost.GetExports<IAuthenticationProvider>();
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -102,7 +108,16 @@ namespace Jellyfin.Server.Implementations.Users
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id); public IEnumerable<Guid> UsersIds
{
get
{
using var dbContext = _dbProvider.CreateContext();
return dbContext.Users
.Select(user => user.Id)
.ToList();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public User? GetUserById(Guid id) public User? GetUserById(Guid id)
@ -188,8 +203,24 @@ namespace Jellyfin.Server.Implementations.Users
await dbContext.SaveChangesAsync().ConfigureAwait(false); await dbContext.SaveChangesAsync().ConfigureAwait(false);
} }
internal async Task<User> CreateUserInternalAsync(string name, JellyfinDb dbContext)
{
// TODO: Remove after user item data is migrated.
var max = await dbContext.Users.AnyAsync().ConfigureAwait(false)
? await dbContext.Users.Select(u => u.InternalId).MaxAsync().ConfigureAwait(false)
: 0;
return new User(
name,
_defaultAuthenticationProvider.GetType().FullName,
_defaultPasswordResetProvider.GetType().FullName)
{
InternalId = max + 1
};
}
/// <inheritdoc/> /// <inheritdoc/>
public User CreateUser(string name) public async Task<User> CreateUserAsync(string name)
{ {
if (!IsValidUsername(name)) if (!IsValidUsername(name))
{ {
@ -198,18 +229,10 @@ namespace Jellyfin.Server.Implementations.Users
using var dbContext = _dbProvider.CreateContext(); using var dbContext = _dbProvider.CreateContext();
// TODO: Remove after user item data is migrated. var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0;
var newUser = new User(
name,
_defaultAuthenticationProvider.GetType().FullName,
_defaultPasswordResetProvider.GetType().FullName)
{
InternalId = max + 1
};
dbContext.Users.Add(newUser); dbContext.Users.Add(newUser);
dbContext.SaveChanges(); await dbContext.SaveChangesAsync().ConfigureAwait(false);
OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser)); OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser));
@ -512,7 +535,7 @@ namespace Jellyfin.Server.Implementations.Users
} }
else else
{ {
IncrementInvalidLoginAttemptCount(user); await IncrementInvalidLoginAttemptCount(user).ConfigureAwait(false);
_logger.LogInformation( _logger.LogInformation(
"Authentication request for {UserName} has been denied (IP: {IP}).", "Authentication request for {UserName} has been denied (IP: {IP}).",
user.Username, user.Username,
@ -530,7 +553,12 @@ namespace Jellyfin.Server.Implementations.Users
if (user != null && isInNetwork) if (user != null && isInNetwork)
{ {
var passwordResetProvider = GetPasswordResetProvider(user); var passwordResetProvider = GetPasswordResetProvider(user);
return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); var result = await passwordResetProvider
.StartForgotPasswordProcess(user, isInNetwork)
.ConfigureAwait(false);
await UpdateUserAsync(user).ConfigureAwait(false);
return result;
} }
return new ForgotPasswordResult return new ForgotPasswordResult
@ -561,23 +589,12 @@ namespace Jellyfin.Server.Implementations.Users
} }
/// <inheritdoc /> /// <inheritdoc />
public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders) public async Task InitializeAsync()
{
_authenticationProviders = authenticationProviders.ToArray();
_passwordResetProviders = passwordResetProviders.ToArray();
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
}
/// <inheritdoc />
public void Initialize()
{ {
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist. // TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
using var dbContext = _dbProvider.CreateContext(); using var dbContext = _dbProvider.CreateContext();
if (dbContext.Users.Any()) if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
{ {
return; return;
} }
@ -595,13 +612,13 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Provided username is not valid!", defaultName); throw new ArgumentException("Provided username is not valid!", defaultName);
} }
var newUser = CreateUser(defaultName); var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
newUser.SetPermission(PermissionKind.IsAdministrator, true); newUser.SetPermission(PermissionKind.IsAdministrator, true);
newUser.SetPermission(PermissionKind.EnableContentDeletion, true); newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true); newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
dbContext.Users.Update(newUser); dbContext.Users.Add(newUser);
dbContext.SaveChanges(); await dbContext.SaveChangesAsync().ConfigureAwait(false);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -637,7 +654,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/> /// <inheritdoc/>
public void UpdateConfiguration(Guid userId, UserConfiguration config) public void UpdateConfiguration(Guid userId, UserConfiguration config)
{ {
var dbContext = _dbProvider.CreateContext(); using var dbContext = _dbProvider.CreateContext();
var user = dbContext.Users var user = dbContext.Users
.Include(u => u.Permissions) .Include(u => u.Permissions)
.Include(u => u.Preferences) .Include(u => u.Preferences)
@ -670,7 +687,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/> /// <inheritdoc/>
public void UpdatePolicy(Guid userId, UserPolicy policy) public void UpdatePolicy(Guid userId, UserPolicy policy)
{ {
var dbContext = _dbProvider.CreateContext(); using var dbContext = _dbProvider.CreateContext();
var user = dbContext.Users var user = dbContext.Users
.Include(u => u.Permissions) .Include(u => u.Permissions)
.Include(u => u.Preferences) .Include(u => u.Preferences)
@ -882,7 +899,7 @@ namespace Jellyfin.Server.Implementations.Users
} }
} }
private void IncrementInvalidLoginAttemptCount(User user) private async Task IncrementInvalidLoginAttemptCount(User user)
{ {
user.InvalidLoginAttemptCount++; user.InvalidLoginAttemptCount++;
int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
@ -896,7 +913,7 @@ namespace Jellyfin.Server.Implementations.Users
user.InvalidLoginAttemptCount); user.InvalidLoginAttemptCount);
} }
UpdateUser(user); await UpdateUserAsync(user).ConfigureAwait(false);
} }
} }
} }

View File

@ -9,6 +9,7 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Users; using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
@ -33,9 +34,9 @@ namespace Jellyfin.Server
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param> /// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
public CoreAppHost( public CoreAppHost(
ServerApplicationPaths applicationPaths, IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
StartupOptions options, IStartupOptions options,
IFileSystem fileSystem, IFileSystem fileSystem,
INetworkManager networkManager) INetworkManager networkManager)
: base( : base(

View File

@ -21,7 +21,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.MigrateActivityLogDb), typeof(Routines.MigrateActivityLogDb),
typeof(Routines.RemoveDuplicateExtras), typeof(Routines.RemoveDuplicateExtras),
typeof(Routines.AddDefaultPluginRepository), typeof(Routines.AddDefaultPluginRepository),
typeof(Routines.MigrateUserDb) typeof(Routines.MigrateUserDb),
typeof(Routines.ReaddDefaultPluginRepository)
}; };
/// <summary> /// <summary>

View File

@ -0,0 +1,49 @@
using System;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Updates;
namespace Jellyfin.Server.Migrations.Routines
{
/// <summary>
/// Migration to initialize system configuration with the default plugin repository.
/// </summary>
public class ReaddDefaultPluginRepository : IMigrationRoutine
{
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
{
Name = "Jellyfin Stable",
Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
};
/// <summary>
/// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
/// </summary>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
{
_serverConfigurationManager = serverConfigurationManager;
}
/// <inheritdoc/>
public Guid Id => Guid.Parse("5F86E7F6-D966-4C77-849D-7A7B40B68C4E");
/// <inheritdoc/>
public string Name => "ReaddDefaultPluginRepository";
/// <inheritdoc/>
public bool PerformOnNewInstall => true;
/// <inheritdoc/>
public void Perform()
{
// Only add if repository list is empty
if (_serverConfigurationManager.Configuration.PluginRepositories.Count == 0)
{
_serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
_serverConfigurationManager.SaveConfiguration();
}
}
}
}

View File

@ -343,6 +343,21 @@ namespace Jellyfin.Server
} }
} }
} }
// Bind to unix socket (only on OSX and Linux)
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// TODO: allow configuration of socket path
var socketPath = $"{appPaths.DataPath}/socket.sock";
// Workaround for https://github.com/aspnet/AspNetCore/issues/14134
if (File.Exists(socketPath))
{
File.Delete(socketPath);
}
options.ListenUnixSocket(socketPath);
_logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath);
}
}) })
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig)) .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
.UseSerilog() .UseSerilog()

View File

@ -525,7 +525,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public async Task<object> Post(CreateUserByName request) public async Task<object> Post(CreateUserByName request)
{ {
var newUser = _userManager.CreateUser(request.Name); var newUser = await _userManager.CreateUserAsync(request.Name).ConfigureAwait(false);
// no need to authenticate password for new user // no need to authenticate password for new user
if (request.Password != null) if (request.Password != null)

View File

@ -0,0 +1,33 @@
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Common.Extensions
{
/// <summary>
/// Static class containing extension methods for <see cref="HttpContext"/>.
/// </summary>
public static class HttpContextExtensions
{
private const string ServiceStackRequest = "ServiceStackRequest";
/// <summary>
/// Set the ServiceStack request.
/// </summary>
/// <param name="httpContext">The HttpContext instance.</param>
/// <param name="request">The service stack request instance.</param>
public static void SetServiceStackRequest(this HttpContext httpContext, IRequest request)
{
httpContext.Items[ServiceStackRequest] = request;
}
/// <summary>
/// Get the ServiceStack request.
/// </summary>
/// <param name="httpContext">The HttpContext instance.</param>
/// <returns>The service stack request instance.</returns>
public static IRequest GetServiceStackRequest(this HttpContext httpContext)
{
return (IRequest)httpContext.Items[ServiceStackRequest];
}
}
}

View File

@ -199,7 +199,7 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Updates the item. /// Updates the item.
/// </summary> /// </summary>
void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Events; using MediaBrowser.Model.Events;
@ -55,7 +54,7 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Initializes the user manager and ensures that a user exists. /// Initializes the user manager and ensures that a user exists.
/// </summary> /// </summary>
void Initialize(); Task InitializeAsync();
/// <summary> /// <summary>
/// Gets a user by Id. /// Gets a user by Id.
@ -106,7 +105,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>The created user.</returns> /// <returns>The created user.</returns>
/// <exception cref="ArgumentNullException">name</exception> /// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
User CreateUser(string name); Task<User> CreateUserAsync(string name);
/// <summary> /// <summary>
/// Deletes the specified user. /// Deletes the specified user.
@ -166,8 +165,6 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
Task<PinRedeemResult> RedeemPasswordResetPin(string pin); Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
NameIdPair[] GetAuthenticationProviders(); NameIdPair[] GetAuthenticationProviders();
NameIdPair[] GetPasswordResetProviders(); NameIdPair[] GetPasswordResetProviders();

View File

@ -372,7 +372,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public int GetVideoProfileScore(string profile) public int GetVideoProfileScore(string profile)
{ {
// strip spaces because they may be stripped out on the query string // strip spaces because they may be stripped out on the query string
profile = profile.Replace(" ", ""); profile = profile.Replace(" ", string.Empty, StringComparison.Ordinal);
return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
} }
@ -460,7 +460,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!IsCopyCodec(outputVideoCodec)) if (!IsCopyCodec(outputVideoCodec))
{ {
if (state.IsVideoRequest if (state.IsVideoRequest
&& IsVaapiSupported(state) && _mediaEncoder.SupportsHwaccel("vaapi")
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{ {
if (isVaapiDecoder) if (isVaapiDecoder)
@ -468,13 +468,13 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append("-hwaccel_output_format vaapi ") arg.Append("-hwaccel_output_format vaapi ")
.Append("-vaapi_device ") .Append("-vaapi_device ")
.Append(encodingOptions.VaapiDevice) .Append(encodingOptions.VaapiDevice)
.Append(" "); .Append(' ');
} }
else if (!isVaapiDecoder && isVaapiEncoder) else if (!isVaapiDecoder && isVaapiEncoder)
{ {
arg.Append("-vaapi_device ") arg.Append("-vaapi_device ")
.Append(encodingOptions.VaapiDevice) .Append(encodingOptions.VaapiDevice)
.Append(" "); .Append(' ');
} }
} }
@ -495,13 +495,13 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else else
{ {
arg.Append("-hwaccel qsv -init_hw_device qsv=hw "); arg.Append("-hwaccel qsv ");
} }
} }
if (isWindows) if (isWindows)
{ {
arg.Append("-hwaccel qsv -init_hw_device qsv=hw "); arg.Append("-hwaccel qsv ");
} }
} }
// While using SW decoder // While using SW decoder
@ -1605,6 +1605,13 @@ namespace MediaBrowser.Controller.MediaEncoding
outputSizeParam = outputSizeParam.Slice(index); outputSizeParam = outputSizeParam.Slice(index);
} }
else else
{
index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{ {
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase); index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1) if (index != -1)
@ -1623,7 +1630,16 @@ namespace MediaBrowser.Controller.MediaEncoding
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase); index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
if (index != -1) if (index != -1)
{ {
outputSizeParam = outputSizeParam.Slice(index); outputSizeParam = outputSizeParam.Substring(index);
}
else
{
index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Substring(index);
}
}
} }
} }
} }
@ -1685,7 +1701,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{ {
/* /*
@ -1790,6 +1806,10 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputWidth = width.Value; var outputWidth = width.Value;
var outputHeight = height.Value; var outputHeight = height.Value;
var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase); var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
var isDeintEnabled = state.DeInterlace("h264", true)
|| state.DeInterlace("avc", true)
|| state.DeInterlace("h265", true)
|| state.DeInterlace("hevc", true);
if (!videoWidth.HasValue if (!videoWidth.HasValue
|| outputWidth != videoWidth.Value || outputWidth != videoWidth.Value
@ -1805,7 +1825,7 @@ namespace MediaBrowser.Controller.MediaEncoding
qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
outputWidth, outputWidth,
outputHeight, outputHeight,
(qsv_or_vaapi && state.DeInterlace("h264", true)) ? ":deinterlace=1" : string.Empty)); (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
} }
else else
{ {
@ -1814,7 +1834,7 @@ namespace MediaBrowser.Controller.MediaEncoding
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"{0}=format=nv12{1}", "{0}=format=nv12{1}",
qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi", qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
(qsv_or_vaapi && state.DeInterlace("h264", true)) ? ":deinterlace=1" : string.Empty)); (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
} }
} }
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
@ -2026,7 +2046,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
var request = state.BaseRequest; var request = state.BaseRequest;
var videoStream = state.VideoStream; var videoStream = state.VideoStream;
var filters = new List<string>(); var filters = new List<string>();
@ -2035,23 +2054,31 @@ namespace MediaBrowser.Controller.MediaEncoding
var inputHeight = videoStream?.Height; var inputHeight = videoStream?.Height;
var threeDFormat = state.MediaSource.Video3DFormat; var threeDFormat = state.MediaSource.Video3DFormat;
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
// When the input may or may not be hardware VAAPI decodable // When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) if (isVaapiH264Encoder)
{ {
filters.Add("format=nv12|vaapi"); filters.Add("format=nv12|vaapi");
filters.Add("hwupload"); filters.Add("hwupload");
} }
// When the input may or may not be hardware QSV decodable // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) else if (isLinux && hasGraphicalSubs && isQsvH264Encoder)
{ {
filters.Add("format=nv12|qsv");
filters.Add("hwupload=extra_hw_frames=64"); filters.Add("hwupload=extra_hw_frames=64");
} }
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{ {
var codec = videoStream.Codec.ToLowerInvariant(); var codec = videoStream.Codec.ToLowerInvariant();
var isColorDepth10 = IsColorDepth10(state); var isColorDepth10 = IsColorDepth10(state);
@ -2079,16 +2106,19 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
// Add hardware deinterlace filter before scaling filter // Add hardware deinterlace filter before scaling filter
if (state.DeInterlace("h264", true)) if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true))
{ {
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) if (isVaapiH264Encoder)
{ {
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi")); filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
} }
} }
// Add software deinterlace filter before scaling filter // Add software deinterlace filter before scaling filter
if (state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) if (state.DeInterlace("h264", true)
|| state.DeInterlace("avc", true)
|| state.DeInterlace("h265", true)
|| state.DeInterlace("hevc", true))
{ {
string deintParam; string deintParam;
var inputFramerate = videoStream?.RealFrameRate; var inputFramerate = videoStream?.RealFrameRate;
@ -2105,9 +2135,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(deintParam)) if (!string.IsNullOrEmpty(deintParam))
{ {
if (!string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
&& !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
&& videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) == -1)
{ {
filters.Add(deintParam); filters.Add(deintParam);
} }
@ -2117,12 +2145,10 @@ namespace MediaBrowser.Controller.MediaEncoding
// Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
// Add parameters to use VAAPI with burn-in text subttiles (GH issue #642) // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) if (isVaapiH264Encoder)
{ {
if (state.SubtitleStream != null if (hasTextSubs)
&& state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{ {
// Test passed on Intel and AMD gfx // Test passed on Intel and AMD gfx
filters.Add("hwmap=mode=read+write"); filters.Add("hwmap=mode=read+write");
@ -2132,9 +2158,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var output = string.Empty; var output = string.Empty;
if (state.SubtitleStream != null if (hasTextSubs)
&& state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{ {
var subParam = GetTextSubtitleParam(state); var subParam = GetTextSubtitleParam(state);
@ -2142,7 +2166,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API // Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API
// Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI // Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) if (isVaapiH264Encoder)
{ {
filters.Add("hwmap"); filters.Add("hwmap");
} }

View File

@ -198,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
internal static Version GetFFmpegVersion(string output) internal static Version GetFFmpegVersion(string output)
{ {
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
var match = Regex.Match(output, @"^ffmpeg version n?((?:\d+\.?)+)"); var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
if (match.Success) if (match.Success)
{ {
@ -225,7 +225,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var rc = new StringBuilder(144); var rc = new StringBuilder(144);
foreach (Match m in Regex.Matches( foreach (Match m in Regex.Matches(
output, output,
@"((?<name>lib\w+)\s+(?<major>\d+)\.\s*(?<minor>\d+))", @"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
RegexOptions.Multiline)) RegexOptions.Multiline))
{ {
rc.Append(m.Groups["name"]) rc.Append(m.Groups["name"])

View File

@ -174,7 +174,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
inputFiles = new[] { mediaSource.Path }; inputFiles = new[] { mediaSource.Path };
} }
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), subtitleStream, cancellationToken).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false); var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
@ -428,7 +428,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// FFmpeg automatically convert character encoding when it is UTF-16 // FFmpeg automatically convert character encoding when it is UTF-16
// If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event" // If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event"
if ((inputPath.EndsWith(".smi") || inputPath.EndsWith(".sami")) && (encodingParam == "UTF-16BE" || encodingParam == "UTF-16LE")) if ((inputPath.EndsWith(".smi") || inputPath.EndsWith(".sami")) &&
(encodingParam.Equals("UTF-16BE", StringComparison.OrdinalIgnoreCase) ||
encodingParam.Equals("UTF-16LE", StringComparison.OrdinalIgnoreCase)))
{ {
encodingParam = ""; encodingParam = "";
} }

View File

@ -112,18 +112,20 @@ namespace MediaBrowser.Model.Entities
{ {
get get
{ {
if (Type == MediaStreamType.Audio) switch (Type)
{
case MediaStreamType.Audio:
{ {
// if (!string.IsNullOrEmpty(Title))
//{
// return AddLanguageIfNeeded(Title);
//}
var attributes = new List<string>(); var attributes = new List<string>();
if (!string.IsNullOrEmpty(Language)) if (!string.IsNullOrEmpty(Language))
{ {
attributes.Add(StringHelper.FirstToUpper(Language)); // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes.
string fullLanguage = CultureInfo
.GetCultures(CultureTypes.NeutralCultures)
.FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase))
?.DisplayName;
attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language));
} }
if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase))
@ -137,7 +139,7 @@ namespace MediaBrowser.Model.Entities
if (!string.IsNullOrEmpty(ChannelLayout)) if (!string.IsNullOrEmpty(ChannelLayout))
{ {
attributes.Add(ChannelLayout); attributes.Add(StringHelper.FirstToUpper(ChannelLayout));
} }
else if (Channels.HasValue) else if (Channels.HasValue)
{ {
@ -146,13 +148,28 @@ namespace MediaBrowser.Model.Entities
if (IsDefault) if (IsDefault)
{ {
attributes.Add("Default"); attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault);
} }
return string.Join(" ", attributes); if (!string.IsNullOrEmpty(Title))
{
var result = new StringBuilder(Title);
foreach (var tag in attributes)
{
// Keep Tags that are not already in Title.
if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1)
{
result.Append(" - ").Append(tag);
}
} }
if (Type == MediaStreamType.Video) return result.ToString();
}
return string.Join(" - ", attributes);
}
case MediaStreamType.Video:
{ {
var attributes = new List<string>(); var attributes = new List<string>();
@ -168,17 +185,36 @@ namespace MediaBrowser.Model.Entities
attributes.Add(Codec.ToUpperInvariant()); attributes.Add(Codec.ToUpperInvariant());
} }
if (!string.IsNullOrEmpty(Title))
{
var result = new StringBuilder(Title);
foreach (var tag in attributes)
{
// Keep Tags that are not already in Title.
if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1)
{
result.Append(" - ").Append(tag);
}
}
return result.ToString();
}
return string.Join(" ", attributes); return string.Join(" ", attributes);
} }
if (Type == MediaStreamType.Subtitle) case MediaStreamType.Subtitle:
{ {
var attributes = new List<string>(); var attributes = new List<string>();
if (!string.IsNullOrEmpty(Language)) if (!string.IsNullOrEmpty(Language))
{ {
attributes.Add(StringHelper.FirstToUpper(Language)); // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes.
string fullLanguage = CultureInfo
.GetCultures(CultureTypes.NeutralCultures)
.FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase))
?.DisplayName;
attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language));
} }
else else
{ {
@ -197,24 +233,27 @@ namespace MediaBrowser.Model.Entities
if (!string.IsNullOrEmpty(Title)) if (!string.IsNullOrEmpty(Title))
{ {
return attributes.AsEnumerable() var result = new StringBuilder(Title);
// keep Tags that are not already in Title foreach (var tag in attributes)
.Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) {
// attributes concatenation, starting with Title // Keep Tags that are not already in Title.
.Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr)) if (Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1)
.ToString(); {
result.Append(" - ").Append(tag);
}
}
return result.ToString();
} }
return string.Join(" - ", attributes.ToArray()); return string.Join(" - ", attributes.ToArray());
} }
if (Type == MediaStreamType.Video) default:
{
}
return null; return null;
} }
} }
}
private string GetResolutionText() private string GetResolutionText()
{ {

View File

@ -1,3 +1,4 @@
#pragma warning disable CA1819 // Properties should not return arrays
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -9,21 +10,27 @@ namespace MediaBrowser.Model.Notifications
public NotificationOption(string type) public NotificationOption(string type)
{ {
Type = type; Type = type;
DisabledServices = Array.Empty<string>(); DisabledServices = Array.Empty<string>();
DisabledMonitorUsers = Array.Empty<string>(); DisabledMonitorUsers = Array.Empty<string>();
SendToUsers = Array.Empty<string>(); SendToUsers = Array.Empty<string>();
} }
public string Type { get; set; } public NotificationOption()
{
DisabledServices = Array.Empty<string>();
DisabledMonitorUsers = Array.Empty<string>();
SendToUsers = Array.Empty<string>();
}
public string? Type { get; set; }
/// <summary> /// <summary>
/// User Ids to not monitor (it's opt out). /// Gets or sets user Ids to not monitor (it's opt out).
/// </summary> /// </summary>
public string[] DisabledMonitorUsers { get; set; } public string[] DisabledMonitorUsers { get; set; }
/// <summary> /// <summary>
/// User Ids to send to (if SendToUserMode == Custom) /// Gets or sets user Ids to send to (if SendToUserMode == Custom).
/// </summary> /// </summary>
public string[] SendToUsers { get; set; } public string[] SendToUsers { get; set; }

View File

@ -265,7 +265,7 @@ namespace MediaBrowser.Providers.Manager
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "{0} failed in GetImageInfos for type {1}", provider.GetType().Name, item.GetType().Name); _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
return new List<RemoteImageInfo>(); return new List<RemoteImageInfo>();
} }
} }
@ -430,7 +430,7 @@ namespace MediaBrowser.Providers.Manager
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "{0} failed in Supports for type {1}", provider.GetType().Name, item.GetType().Name); _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
return false; return false;
} }
} }

View File

@ -28,27 +28,29 @@
pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8" pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8"
}; };
$('.configPage').on('pageshow', function () { document.querySelector('.configPage')
.addEventListener('pageshow', function () {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
$('#enable').checked = config.Enable; document.querySelector('#enable').checked = config.Enable;
$('#replaceAlbumName').checked = config.ReplaceAlbumName; document.querySelector('#replaceAlbumName').checked = config.ReplaceAlbumName;
Dashboard.hideLoadingMsg(); Dashboard.hideLoadingMsg();
}); });
}); });
$('.configForm').on('submit', function (e) { document.querySelector('.configForm')
.addEventListener('submit', function (e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
var form = this;
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
config.Enable = $('#enable', form).checked; config.Enable = document.querySelector('#enable').checked;
config.ReplaceAlbumName = $('#replaceAlbumName', form).checked; config.ReplaceAlbumName = document.querySelector('#replaceAlbumName').checked;
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
}); });
e.preventDefault();
return false; return false;
}); });
</script> </script>

View File

@ -36,31 +36,45 @@
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a" uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
}; };
$('.musicBrainzConfigPage').on('pageshow', function () { document.querySelector('.musicBrainzConfigPage')
.addEventListener('pageshow', function () {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) { ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
$('#server').val(config.Server).change(); var server = document.querySelector('#server');
$('#rateLimit').val(config.RateLimit).change(); server.value = config.Server;
$('#enable').checked = config.Enable; server.dispatchEvent(new Event('change', {
$('#replaceArtistName').checked = config.ReplaceArtistName; bubbles: true,
cancelable: false
}));
var rateLimit = document.querySelector('#rateLimit');
rateLimit.value = config.RateLimit;
rateLimit.dispatchEvent(new Event('change', {
bubbles: true,
cancelable: false
}));
document.querySelector('#enable').checked = config.Enable;
document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
Dashboard.hideLoadingMsg(); Dashboard.hideLoadingMsg();
}); });
}); });
$('.musicBrainzConfigForm').on('submit', function (e) { document.querySelector('.musicBrainzConfigForm')
.addEventListener('submit', function (e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
var form = this;
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) { ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
config.Server = $('#server', form).val(); config.Server = document.querySelector('#server').value;
config.RateLimit = $('#rateLimit', form).val(); config.RateLimit = document.querySelector('#rateLimit').value;
config.Enable = $('#enable', form).checked; config.Enable = document.querySelector('#enable').checked;
config.ReplaceArtistName = $('#replaceArtistName', form).checked; config.ReplaceArtistName = document.querySelector('#replaceArtistName').checked;
ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
}); });
e.preventDefault();
return false; return false;
}); });
</script> </script>

View File

@ -24,23 +24,26 @@
pluginId: "a628c0da-fac5-4c7e-9d1a-7134223f14c8" pluginId: "a628c0da-fac5-4c7e-9d1a-7134223f14c8"
}; };
$('.configPage').on('pageshow', function () { document.querySelector('.configPage')
.addEventListener('pageshow', function () {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
$('#castAndCrew').checked = config.CastAndCrew; document.querySelector('#castAndCrew').checked = config.CastAndCrew;
Dashboard.hideLoadingMsg(); Dashboard.hideLoadingMsg();
}); });
}); });
$('.configForm').on('submit', function (e) {
document.querySelector('.configForm')
.addEventListener('submit', function (e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
var form = this;
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
config.CastAndCrew = $('#castAndCrew', form).checked; config.CastAndCrew = document.querySelector('#castAndCrew').checked;
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
}); });
e.preventDefault();
return false; return false;
}); });
</script> </script>

View File

@ -58,7 +58,7 @@ namespace MediaBrowser.Providers.TV
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError(ex, "Error in DummySeasonProvider"); Logger.LogError(ex, "Error in DummySeasonProvider for {ItemPath}", item.Path);
} }
} }

View File

@ -31,7 +31,7 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
#JELLYFIN_SERVICE_OPT="--service" #JELLYFIN_SERVICE_OPT="--service"
# [OPTIONAL] run Jellyfin without the web app # [OPTIONAL] run Jellyfin without the web app
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" #JELLYFIN_NOWEBAPP_OPT="--nowebclient"
# #
# SysV init/Upstart options # SysV init/Upstart options
@ -40,4 +40,4 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
# Application username # Application username
JELLYFIN_USER="jellyfin" JELLYFIN_USER="jellyfin"
# Full application command # Full application command
JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT"