diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
index 7fba2184a..d9c1669b0 100644
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ b/Emby.Dlna/Api/DlnaServerService.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
namespace Emby.Dlna.Api
{
@@ -108,7 +109,7 @@ namespace Emby.Dlna.Api
public string Filename { get; set; }
}
- public class DlnaServerService : IService, IRequiresRequest
+ public class DlnaServerService : IService
{
private const string XMLContentType = "text/xml; charset=UTF-8";
@@ -127,11 +128,13 @@ namespace Emby.Dlna.Api
public DlnaServerService(
IDlnaManager dlnaManager,
IHttpResultFactory httpResultFactory,
- IServerConfigurationManager configurationManager)
+ IServerConfigurationManager configurationManager,
+ IHttpContextAccessor httpContextAccessor)
{
_dlnaManager = dlnaManager;
_resultFactory = httpResultFactory;
_configurationManager = configurationManager;
+ Request = httpContextAccessor?.HttpContext.GetServiceStackRequest() ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private string GetHeader(string name)
diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs
index 56c90c8b3..7d02f5e96 100644
--- a/Emby.Dlna/Eventing/EventManager.cs
+++ b/Emby.Dlna/Eventing/EventManager.cs
@@ -152,11 +152,15 @@ namespace Emby.Dlna.Eventing
builder.Append("");
foreach (var key in stateVariables.Keys)
{
- builder.Append("");
- builder.Append("<" + key + ">");
- builder.Append(stateVariables[key]);
- builder.Append("" + key + ">");
- builder.Append("");
+ builder.Append("")
+ .Append('<')
+ .Append(key)
+ .Append('>')
+ .Append(stateVariables[key])
+ .Append("")
+ .Append(key)
+ .Append('>')
+ .Append("");
}
builder.Append("");
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index c5080b90f..72834c69d 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -4,12 +4,12 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Security;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Emby.Dlna.Common;
-using Emby.Dlna.Server;
using Emby.Dlna.Ssdp;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -334,7 +334,7 @@ namespace Emby.Dlna.PlayTo
return string.Empty;
}
- return DescriptionXmlBuilder.Escape(value);
+ return SecurityElement.Escape(value);
}
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
index 7143c3109..bca9e81cd 100644
--- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Security;
using System.Text;
using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna;
@@ -64,10 +65,10 @@ namespace Emby.Dlna.Server
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("");
builder.Append("1");
@@ -76,7 +77,9 @@ namespace Emby.Dlna.Server
if (!EnableAbsoluteUrls)
{
- builder.Append("" + Escape(_serverAddress) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_serverAddress))
+ .Append("");
}
AppendDeviceInfo(builder);
@@ -93,91 +96,14 @@ namespace Emby.Dlna.Server
AppendIconList(builder);
- builder.Append("" + Escape(_serverAddress) + "/web/index.html");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_serverAddress))
+ .Append("/web/index.html");
AppendServiceList(builder);
builder.Append("");
}
- private static readonly char[] s_escapeChars = new char[]
- {
- '<',
- '>',
- '"',
- '\'',
- '&'
- };
-
- private static readonly string[] s_escapeStringPairs = new[]
- {
- "<",
- "<",
- ">",
- ">",
- "\"",
- """,
- "'",
- "'",
- "&",
- "&"
- };
-
- 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);
- }
-
- /// Replaces invalid XML characters in a string with their valid XML equivalent.
- /// The input string with invalid characters replaced.
- /// The string within which to escape invalid characters.
- 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)
{
builder.Append("");
@@ -187,32 +113,54 @@ namespace Emby.Dlna.Server
builder.Append("urn:schemas-upnp-org:device:MediaServer:1");
- builder.Append("" + Escape(GetFriendlyName()) + "");
- builder.Append("" + Escape(_profile.Manufacturer ?? string.Empty) + "");
- builder.Append("" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(GetFriendlyName()))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty))
+ .Append("");
- builder.Append("" + Escape(_profile.ModelDescription ?? string.Empty) + "");
- builder.Append("" + Escape(_profile.ModelName ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty))
+ .Append("");
- builder.Append("" + Escape(_profile.ModelNumber ?? string.Empty) + "");
- builder.Append("" + Escape(_profile.ModelUrl ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty))
+ .Append("");
if (string.IsNullOrEmpty(_profile.SerialNumber))
{
- builder.Append("" + Escape(_serverId) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_serverId))
+ .Append("");
}
else
{
- builder.Append("" + Escape(_profile.SerialNumber) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.SerialNumber))
+ .Append("");
}
builder.Append("");
- builder.Append("uuid:" + Escape(_serverUdn) + "");
+ builder.Append("uuid:")
+ .Append(SecurityElement.Escape(_serverUdn))
+ .Append("");
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
{
- builder.Append("" + Escape(_profile.SonyAggregationFlags) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(_profile.SonyAggregationFlags))
+ .Append("");
}
}
@@ -250,11 +198,21 @@ namespace Emby.Dlna.Server
{
builder.Append("");
- builder.Append("" + Escape(icon.MimeType ?? string.Empty) + "");
- builder.Append("" + Escape(icon.Width.ToString(_usCulture)) + "");
- builder.Append("" + Escape(icon.Height.ToString(_usCulture)) + "");
- builder.Append("" + Escape(icon.Depth ?? string.Empty) + "");
- builder.Append("" + BuildUrl(icon.Url) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture)))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.Height.ToString(_usCulture)))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(icon.Url))
+ .Append("");
builder.Append("");
}
@@ -270,11 +228,21 @@ namespace Emby.Dlna.Server
{
builder.Append("");
- builder.Append("" + Escape(service.ServiceType ?? string.Empty) + "");
- builder.Append("" + Escape(service.ServiceId ?? string.Empty) + "");
- builder.Append("" + BuildUrl(service.ScpdUrl) + "");
- builder.Append("" + BuildUrl(service.ControlUrl) + "");
- builder.Append("" + BuildUrl(service.EventSubUrl) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(service.ScpdUrl))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(service.ControlUrl))
+ .Append("");
+ builder.Append("")
+ .Append(BuildUrl(service.EventSubUrl))
+ .Append("");
builder.Append("");
}
@@ -298,7 +266,7 @@ namespace Emby.Dlna.Server
url = _serverAddress.TrimEnd('/') + url;
}
- return Escape(url);
+ return SecurityElement.Escape(url);
}
private IEnumerable GetIcons()
diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs
index af557aa14..6c7d6f846 100644
--- a/Emby.Dlna/Service/ServiceXmlBuilder.cs
+++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs
@@ -1,9 +1,9 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Security;
using System.Text;
using Emby.Dlna.Common;
-using Emby.Dlna.Server;
namespace Emby.Dlna.Service
{
@@ -37,7 +37,9 @@ namespace Emby.Dlna.Service
{
builder.Append("");
- builder.Append("" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(item.Name ?? string.Empty))
+ .Append("");
builder.Append("");
@@ -45,9 +47,15 @@ namespace Emby.Dlna.Service
{
builder.Append("");
- builder.Append("" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "");
- builder.Append("" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "");
- builder.Append("" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(argument.Name ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
+ .Append("");
builder.Append("");
}
@@ -68,17 +76,25 @@ namespace Emby.Dlna.Service
{
var sendEvents = item.SendsEvents ? "yes" : "no";
- builder.Append("");
+ builder.Append("");
- builder.Append("" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "");
- builder.Append("" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(item.Name ?? string.Empty))
+ .Append("");
+ builder.Append("")
+ .Append(SecurityElement.Escape(item.DataType ?? string.Empty))
+ .Append("");
if (item.AllowedValues.Length > 0)
{
builder.Append("");
foreach (var allowedValue in item.AllowedValues)
{
- builder.Append("" + DescriptionXmlBuilder.Escape(allowedValue) + "");
+ builder.Append("")
+ .Append(SecurityElement.Escape(allowedValue))
+ .Append("");
}
builder.Append("");
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 1b343790e..d1e17f416 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -136,8 +136,8 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{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})*",
+ @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
};
CleanStrings = new[]
@@ -277,7 +277,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
- new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?[\w\s]+?)\s(?\d{1,3})(-(?\d{2,3}))*[^\\\/x]*$")
+ new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?[\w\s]+?)\s(?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/x]*$")
{
IsNamed = true
},
@@ -300,32 +300,32 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
// [bar] Foo - 1 [baz]
- new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[-\s_]+(?\d+).*$")
+ new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[-\s_]+(?[0-9]+).*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)[sS]?(?\d+)[xX](?\d+)[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)[sS]?(?[0-9]+)[xX](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)[sS](?\d+)[x,X]?[eE](?\d+)[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)[sS](?[0-9]+)[x,X]?[eE](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d+))[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]+))[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d+)[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]+)[^\\\/]*$")
{
IsNamed = true
},
// "01.avi"
- new EpisodeExpression(@".*[\\\/](?\d+)(-(?\d+))*\.\w+$")
+ new EpisodeExpression(@".*[\\\/](?[0-9]+)(-(?[0-9]+))*\.\w+$")
{
IsOptimistic = true,
IsNamed = true
@@ -335,34 +335,34 @@ namespace Emby.Naming.Common
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
// "01 - blah.avi", "01-blah.avi"
- new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\s?-\s?[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?[0-9]{1,3})(-(?[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
{
IsOptimistic = true,
IsNamed = true
},
// "01.blah.avi"
- new EpisodeExpression(@".*(\\|\/)(?\d{1,3})(-(?\d{2,3}))*\.[^\\\/]+$")
+ new EpisodeExpression(@".*(\\|\/)(?[0-9]{1,3})(-(?[0-9]{2,3}))*\.[^\\\/]+$")
{
IsOptimistic = 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"
- new EpisodeExpression(@".*[\\\/][^\\\/]* - (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$")
+ new EpisodeExpression(@".*[\\\/][^\\\/]* - (?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/]*$")
{
IsOptimistic = true,
IsNamed = true
},
// "01 episode title.avi"
- new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?\d{1,3})([^\\\/]*)$")
+ new EpisodeExpression(@"[Ss]eason[\._ ](?[0-9]+)[\\\/](?[0-9]{1,3})([^\\\/]*)$")
{
IsOptimistic = true,
IsNamed = true
},
// "Episode 16", "Episode 16 - Title"
- new EpisodeExpression(@".*[\\\/][^\\\/]* (?\d{1,3})(-(?\d{2,3}))*[^\\\/]*$")
+ new EpisodeExpression(@".*[\\\/][^\\\/]* (?[0-9]{1,3})(-(?[0-9]{2,3}))*[^\\\/]*$")
{
IsOptimistic = true,
IsNamed = true
@@ -625,17 +625,17 @@ namespace Emby.Naming.Common
AudioBookPartsExpressions = new[]
{
// Detect specified chapters, like CH 01
- @"ch(?:apter)?[\s_-]?(?\d+)",
+ @"ch(?:apter)?[\s_-]?(?[0-9]+)",
// Detect specified parts, like Part 02
- @"p(?:ar)?t[\s_-]?(?\d+)",
+ @"p(?:ar)?t[\s_-]?(?[0-9]+)",
// Chapter is often beginning of filename
- @"^(?\d+)",
+ "^(?[0-9]+)",
// Part if often ending of filename
- @"(?\d+)$",
+ "(?[0-9]+)$",
// Sometimes named as 0001_005 (chapter_part)
- @"(?\d+)_(?\d+)",
+ "(?[0-9]+)_(?[0-9]+)",
// Some audiobooks are ripped from cd's, and will be named by disk number.
- @"dis(?:c|k)[\s_-]?(?\d+)"
+ @"dis(?:c|k)[\s_-]?(?[0-9]+)"
};
var extensions = VideoFileExtensions.ToList();
@@ -675,16 +675,16 @@ namespace Emby.Naming.Common
MultipleEpisodeExpressions = new string[]
{
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[eExX](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)[sS]?(?\d{1,4})[xX](?\d{1,3})(-[xE]?[eE]?(?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )\d{1,4}[xX][eE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?\d{1,4})[xX](?\d{1,3}))(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})((-| - )?[xXeE](?\d{1,3}))+[^\\\/]*$",
- @".*(\\|\/)(?[^\\\/]*)[sS](?\d{1,4})[xX\.]?[eE](?\d{1,3})(-[xX]?[eE]?(?\d{1,3}))+[^\\\/]*$"
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)[sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3})(-[xE]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?[0-9]{1,4})[xX](?[0-9]{1,3}))(-[xX]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]{1,3})((-| - )?[xXeE](?[0-9]{1,3}))+[^\\\/]*$",
+ @".*(\\|\/)(?[^\\\/]*)[sS](?[0-9]{1,4})[xX\.]?[eE](?[0-9]{1,3})(-[xX]?[eE]?(?[0-9]{1,3}))+[^\\\/]*$"
}.Select(i => new EpisodeExpression(i)
{
IsNamed = true
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index f6077400d..45d1bb01c 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -192,7 +192,7 @@ namespace Emby.Server.Implementations
/// Gets or sets the application paths.
///
/// The application paths.
- protected ServerApplicationPaths ApplicationPaths { get; set; }
+ protected IServerApplicationPaths ApplicationPaths { get; set; }
///
/// Gets or sets all concrete types.
@@ -236,7 +236,7 @@ namespace Emby.Server.Implementations
/// Initializes a new instance of the class.
///
public ApplicationHost(
- ServerApplicationPaths applicationPaths,
+ IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
@@ -795,7 +795,6 @@ namespace Emby.Server.Implementations
Resolve().AddParts(GetExports());
Resolve().AddParts(GetExports(), GetExports());
- Resolve().AddParts(GetExports(), GetExports());
Resolve().AddParts(GetExports());
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index a6390b1ef..9f5566424 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -1110,7 +1110,8 @@ namespace Emby.Server.Implementations.Data
continue;
}
- str.Append(ToValueString(i) + "|");
+ str.Append(ToValueString(i))
+ .Append('|');
}
str.Length -= 1; // Remove last |
@@ -2471,7 +2472,7 @@ namespace Emby.Server.Implementations.Data
var item = query.SimilarTo;
var builder = new StringBuilder();
- builder.Append("(");
+ builder.Append('(');
if (string.IsNullOrEmpty(item.OfficialRating))
{
@@ -2509,7 +2510,7 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrEmpty(query.SearchTerm))
{
var builder = new StringBuilder();
- builder.Append("(");
+ builder.Append('(');
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)
{
- insertText.Append(",");
+ insertText.Append(',');
}
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))
{
- insertText.Append("@" + column + index + ",");
+ insertText.Append('@')
+ .Append(column)
+ .Append(index)
+ .Append(',');
}
insertText.Length -= 1;
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
index 590eee1b4..6fce8de44 100644
--- a/Emby.Server.Implementations/HttpServer/FileWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IStreamHelper _streamHelper;
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
///
/// The _options.
@@ -49,7 +48,6 @@ namespace Emby.Server.Implementations.HttpServer
}
_streamHelper = streamHelper;
- _fileSystem = fileSystem;
Path = path;
_logger = logger;
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 318bc6a24..c9f802a51 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -13,26 +13,22 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.HttpServer.Security
{
public class AuthService : IAuthService
{
- private readonly ILogger _logger;
private readonly IAuthorizationContext _authorizationContext;
private readonly ISessionManager _sessionManager;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
public AuthService(
- ILogger logger,
IAuthorizationContext authorizationContext,
IServerConfigurationManager config,
ISessionManager sessionManager,
INetworkManager networkManager)
{
- _logger = logger;
_authorizationContext = authorizationContext;
_config = config;
_sessionManager = sessionManager;
diff --git a/Emby.Server.Implementations/Images/ArtistImageProvider.cs b/Emby.Server.Implementations/Images/ArtistImageProvider.cs
index 52896720e..bf57382ed 100644
--- a/Emby.Server.Implementations/Images/ArtistImageProvider.cs
+++ b/Emby.Server.Implementations/Images/ArtistImageProvider.cs
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -25,14 +24,9 @@ namespace Emby.Server.Implementations.Images
///
public class ArtistImageProvider : BaseDynamicImageProvider
{
- ///
- /// The library manager.
- ///
- private readonly ILibraryManager _libraryManager;
-
- public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor)
+ : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
- _libraryManager = libraryManager;
}
///
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 77b2c0a69..3380e29d4 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library
if (parent != null)
{
// 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))
{
return true;
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index ab39a7223..236453e80 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -11,6 +11,17 @@ namespace Emby.Server.Implementations.Library
{
public class ExclusiveLiveStream : ILiveStream
{
+ private readonly Func _closeFn;
+
+ public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func closeFn)
+ {
+ MediaSource = mediaSource;
+ EnableStreamSharing = false;
+ _closeFn = closeFn;
+ ConsumerCount = 1;
+ UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ }
+
public int ConsumerCount { get; set; }
public string OriginalStreamId { get; set; }
@@ -21,18 +32,7 @@ namespace Emby.Server.Implementations.Library
public MediaSourceInfo MediaSource { get; set; }
- public string UniqueId { get; private set; }
-
- private Func _closeFn;
-
- public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func closeFn)
- {
- MediaSource = mediaSource;
- EnableStreamSharing = false;
- _closeFn = closeFn;
- ConsumerCount = 1;
- UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
- }
+ public string UniqueId { get; }
public Task Close()
{
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 302d2a092..9165ede35 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -60,6 +60,8 @@ namespace Emby.Server.Implementations.Library
///
public class LibraryManager : ILibraryManager
{
+ private const string ShortcutFileExtension = ".mblink";
+
private readonly ILogger _logger;
private readonly ITaskManager _taskManager;
private readonly IUserManager _userManager;
@@ -75,63 +77,24 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary _libraryItemsCache;
private readonly IImageProcessor _imageProcessor;
+ ///
+ /// The _root folder sync lock.
+ ///
+ private readonly object _rootFolderSyncLock = new object();
+ private readonly object _userRootFolderSyncLock = new object();
+
+ private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
+
private NamingOptions _namingOptions;
private string[] _videoFileExtensions;
- private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
-
- private IProviderManager ProviderManager => _providerManagerFactory.Value;
-
- private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
-
///
- /// Gets or sets the postscan tasks.
+ /// The _root folder.
///
- /// The postscan tasks.
- private ILibraryPostScanTask[] PostscanTasks { get; set; }
+ private volatile AggregateFolder _rootFolder;
+ private volatile UserRootFolder _userRootFolder;
- ///
- /// Gets or sets the intro providers.
- ///
- /// The intro providers.
- private IIntroProvider[] IntroProviders { get; set; }
-
- ///
- /// Gets or sets the list of entity resolution ignore rules.
- ///
- /// The entity resolution ignore rules.
- private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
-
- ///
- /// Gets or sets the list of currently registered entity resolvers.
- ///
- /// The entity resolvers enumerable.
- private IItemResolver[] EntityResolvers { get; set; }
-
- private IMultiItemResolver[] MultiItemResolvers { get; set; }
-
- ///
- /// Gets or sets the comparers.
- ///
- /// The comparers.
- private IBaseItemComparer[] Comparers { get; set; }
-
- ///
- /// Occurs when [item added].
- ///
- public event EventHandler ItemAdded;
-
- ///
- /// Occurs when [item updated].
- ///
- public event EventHandler ItemUpdated;
-
- ///
- /// Occurs when [item removed].
- ///
- public event EventHandler ItemRemoved;
-
- public bool IsScanRunning { get; private set; }
+ private bool _wizardCompleted;
///
/// Initializes a new instance of the class.
@@ -186,37 +149,19 @@ namespace Emby.Server.Implementations.Library
}
///
- /// Adds the parts.
+ /// Occurs when [item added].
///
- /// The rules.
- /// The resolvers.
- /// The intro providers.
- /// The item comparers.
- /// The post scan tasks.
- public void AddParts(
- IEnumerable rules,
- IEnumerable resolvers,
- IEnumerable introProviders,
- IEnumerable itemComparers,
- IEnumerable postscanTasks)
- {
- EntityResolutionIgnoreRules = rules.ToArray();
- EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
- MultiItemResolvers = EntityResolvers.OfType().ToArray();
- IntroProviders = introProviders.ToArray();
- Comparers = itemComparers.ToArray();
- PostscanTasks = postscanTasks.ToArray();
- }
+ public event EventHandler ItemAdded;
///
- /// The _root folder.
+ /// Occurs when [item updated].
///
- private volatile AggregateFolder _rootFolder;
+ public event EventHandler ItemUpdated;
///
- /// The _root folder sync lock.
+ /// Occurs when [item removed].
///
- private readonly object _rootFolderSyncLock = new object();
+ public event EventHandler ItemRemoved;
///
/// 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;
+
+ ///
+ /// Gets or sets the postscan tasks.
+ ///
+ /// The postscan tasks.
+ private ILibraryPostScanTask[] PostscanTasks { get; set; }
+
+ ///
+ /// Gets or sets the intro providers.
+ ///
+ /// The intro providers.
+ private IIntroProvider[] IntroProviders { get; set; }
+
+ ///
+ /// Gets or sets the list of entity resolution ignore rules.
+ ///
+ /// The entity resolution ignore rules.
+ private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
+
+ ///
+ /// Gets or sets the list of currently registered entity resolvers.
+ ///
+ /// The entity resolvers enumerable.
+ private IItemResolver[] EntityResolvers { get; set; }
+
+ private IMultiItemResolver[] MultiItemResolvers { get; set; }
+
+ ///
+ /// Gets or sets the comparers.
+ ///
+ /// The comparers.
+ private IBaseItemComparer[] Comparers { get; set; }
+
+ public bool IsScanRunning { get; private set; }
+
+ ///
+ /// Adds the parts.
+ ///
+ /// The rules.
+ /// The resolvers.
+ /// The intro providers.
+ /// The item comparers.
+ /// The post scan tasks.
+ public void AddParts(
+ IEnumerable rules,
+ IEnumerable resolvers,
+ IEnumerable introProviders,
+ IEnumerable itemComparers,
+ IEnumerable postscanTasks)
+ {
+ EntityResolutionIgnoreRules = rules.ToArray();
+ EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
+ MultiItemResolvers = EntityResolvers.OfType().ToArray();
+ IntroProviders = introProviders.ToArray();
+ Comparers = itemComparers.ToArray();
+ PostscanTasks = postscanTasks.ToArray();
+ }
///
/// Records the configuration values.
@@ -775,14 +781,11 @@ namespace Emby.Server.Implementations.Library
return rootFolder;
}
- private volatile UserRootFolder _userRootFolder;
- private readonly object _syncLock = new object();
-
public Folder GetUserRootFolder()
{
if (_userRootFolder == null)
{
- lock (_syncLock)
+ lock (_userRootFolderSyncLock)
{
if (_userRootFolder == null)
{
@@ -1332,7 +1335,7 @@ namespace Emby.Server.Implementations.Library
return new QueryResult
{
- Items = _itemRepository.GetItemList(query).ToArray()
+ Items = _itemRepository.GetItemList(query)
};
}
@@ -1463,11 +1466,9 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItems(query);
}
- var list = _itemRepository.GetItemList(query);
-
return new QueryResult
{
- 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();
- if (outdated.Length == 0)
+ // Skip image processing if current or live tv source
+ if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{
RegisterItem(item);
return;
@@ -1945,12 +1947,9 @@ namespace Emby.Server.Implementations.Library
///
/// Updates the item.
///
- public void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ public void UpdateItems(IReadOnlyList items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
- // Don't iterate multiple times
- var itemsList = items.ToList();
-
- foreach (var item in itemsList)
+ foreach (var item in items)
{
if (item.IsFileProtocol)
{
@@ -1962,11 +1961,11 @@ namespace Emby.Server.Implementations.Library
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
}
- _itemRepository.SaveItems(itemsList, cancellationToken);
+ _itemRepository.SaveItems(items, cancellationToken);
if (ItemUpdated != null)
{
- foreach (var item in itemsList)
+ foreach (var item in items)
{
// With the live tv guide this just creates too much noise
if (item.SourceType != SourceType.Library)
@@ -2189,8 +2188,6 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
}
- private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
-
public UserView GetNamedView(
User user,
string name,
@@ -2488,14 +2485,9 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
- var episodeInfo = episode.IsFileProtocol ?
- resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) :
- new Naming.TV.EpisodeInfo();
-
- if (episodeInfo == null)
- {
- episodeInfo = new Naming.TV.EpisodeInfo();
- }
+ var episodeInfo = episode.IsFileProtocol
+ ? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo()
+ : new Naming.TV.EpisodeInfo();
try
{
@@ -2503,11 +2495,13 @@ namespace Emby.Server.Implementations.Library
if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
{
// Read from metadata
- var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- MediaSource = episode.GetMediaSources(false)[0],
- MediaType = DlnaProfileType.Video
- }, CancellationToken.None).GetAwaiter().GetResult();
+ var mediaInfo = _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaSource = episode.GetMediaSources(false)[0],
+ MediaType = DlnaProfileType.Video
+ },
+ CancellationToken.None).GetAwaiter().GetResult();
if (mediaInfo.ParentIndexNumber > 0)
{
episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
@@ -2665,7 +2659,7 @@ namespace Emby.Server.Implementations.Library
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)
{
@@ -2682,9 +2676,7 @@ namespace Emby.Server.Implementations.Library
.Select(video =>
{
// 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 (dbItem != null)
+ if (GetItemById(video.Id) is Trailer dbItem)
{
video = dbItem;
}
@@ -3011,8 +3003,6 @@ namespace Emby.Server.Implementations.Library
});
}
- private const string ShortcutFileExtension = ".mblink";
-
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
{
AddMediaPathInternal(virtualFolderName, pathInfo, true);
@@ -3206,7 +3196,8 @@ namespace Emby.Server.Implementations.Library
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)
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 9b9f53049..041619d1e 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.Library
{
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger _logger;
-
- private IJsonSerializer _json;
- private IApplicationPaths _appPaths;
+ private readonly IJsonSerializer _json;
+ private readonly IApplicationPaths _appPaths;
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
{
@@ -72,13 +71,14 @@ namespace Emby.Server.Implementations.Library
mediaSource.AnalyzeDurationMs = 3000;
- mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- MediaSource = mediaSource,
- MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
- ExtractChapters = false
-
- }, cancellationToken).ConfigureAwait(false);
+ mediaInfo = await _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaSource = mediaSource,
+ MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
+ ExtractChapters = false
+ },
+ cancellationToken).ConfigureAwait(false);
if (cacheFilePath != null)
{
@@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library
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)
{
@@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library
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.BitRate.HasValue)
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index e5df20622..bd59ee0e4 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -29,6 +29,9 @@ namespace Emby.Server.Implementations.Library
{
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 IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
@@ -40,6 +43,11 @@ namespace Emby.Server.Implementations.Library
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
+ private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
+
+ private readonly object _disposeLock = new object();
+
private IMediaSourceProvider[] _providers;
public MediaSourceManager(
@@ -368,7 +376,6 @@ namespace Emby.Server.Implementations.Library
}
}
-
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
? Array.Empty() : NormalizeLanguage(user.SubtitleLanguagePreference);
@@ -451,9 +458,6 @@ namespace Emby.Server.Implementations.Library
.ToList();
}
- private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase);
- private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
-
public async Task> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
{
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 GetProvider(string key)
{
if (string.IsNullOrEmpty(key))
@@ -884,7 +885,6 @@ namespace Emby.Server.Implementations.Library
GC.SuppressFinalize(this);
}
- private readonly object _disposeLock = new object();
///
/// Releases unmanaged and - optionally - managed resources.
///
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index ca904c4ec..179e0ed98 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
}
// 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)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index c8e41001a..3332e1806 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -1,6 +1,5 @@
using System.Globalization;
using Emby.Naming.TV;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
@@ -13,7 +12,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
///
public class SeasonResolver : FolderResolver
{
- private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly ILogger _logger;
@@ -21,17 +19,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
///
/// Initializes a new instance of the class.
///
- /// The config.
/// The library manager.
/// The localization.
/// The logger.
public SeasonResolver(
- IServerConfigurationManager config,
ILibraryManager libraryManager,
ILocalizationManager localization,
ILogger logger)
{
- _config = config;
_libraryManager = libraryManager;
_localization = localization;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 3df9cc06f..d67c9e542 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -20,13 +20,11 @@ namespace Emby.Server.Implementations.Library
{
public class SearchEngine : ISearchEngine
{
- private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
- public SearchEngine(ILogger logger, ILibraryManager libraryManager, IUserManager userManager)
+ public SearchEngine(ILibraryManager libraryManager, IUserManager userManager)
{
- _logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
}
@@ -34,11 +32,7 @@ namespace Emby.Server.Implementations.Library
public QueryResult GetSearchHints(SearchQuery query)
{
User user = null;
-
- if (query.UserId.Equals(Guid.Empty))
- {
- }
- else
+ if (query.UserId != Guid.Empty)
{
user = _userManager.GetUserById(query.UserId);
}
@@ -48,19 +42,19 @@ namespace Emby.Server.Implementations.Library
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)
{
- results = results.Take(query.Limit.Value).ToList();
+ results = results.GetRange(0, query.Limit.Value);
}
return new QueryResult
{
TotalRecordCount = totalRecordCount,
- Items = results.ToArray()
+ Items = results
};
}
@@ -85,7 +79,7 @@ namespace Emby.Server.Implementations.Library
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();
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 175b3af57..f9e5e6bbc 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
namespace Emby.Server.Implementations.Library
@@ -28,18 +27,15 @@ namespace Emby.Server.Implementations.Library
private readonly ConcurrentDictionary _userData =
new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
- private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly IUserManager _userManager;
private readonly IUserDataRepository _repository;
public UserDataManager(
- ILogger logger,
IServerConfigurationManager config,
IUserManager userManager,
IUserDataRepository repository)
{
- _logger = logger;
_config = config;
_userManager = userManager;
_repository = repository;
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 4c1de3bcc..1b075d86a 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Library;
@@ -28,7 +29,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
@@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.LiveTv
private readonly ILibraryManager _libraryManager;
private readonly ITaskManager _taskManager;
private readonly ILocalizationManager _localization;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IChannelManager _channelManager;
private readonly LiveTvDtoService _tvDtoService;
@@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv
ILibraryManager libraryManager,
ITaskManager taskManager,
ILocalizationManager localization,
- IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IChannelManager channelManager,
LiveTvDtoService liveTvDtoService)
@@ -85,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv
_libraryManager = libraryManager;
_taskManager = taskManager;
_localization = localization;
- _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_dtoService = dtoService;
_userDataManager = userDataManager;
@@ -2234,7 +2231,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{
- info = _jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info));
+ info = JsonSerializer.Deserialize(JsonSerializer.Serialize(info));
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
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
- info = _jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(info));
+ info = JsonSerializer.Deserialize(JsonSerializer.Serialize(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
index 3fe15ec68..81096026b 100644
--- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
@@ -7,7 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
@@ -37,7 +36,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly IJsonSerializer _jsonSerializer;
private readonly IApplicationPaths _applicationPaths;
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
///
/// Initializes a new instance of the class.
@@ -45,17 +43,14 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// The application paths.
/// The json serializer.
/// The logger.
- /// The filesystem manager.
public TaskManager(
IApplicationPaths applicationPaths,
IJsonSerializer jsonSerializer,
- ILogger logger,
- IFileSystem fileSystem)
+ ILogger logger)
{
_applicationPaths = applicationPaths;
_jsonSerializer = jsonSerializer;
_logger = logger;
- _fileSystem = fileSystem;
ScheduledTasks = Array.Empty();
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index 3854be703..8439f8a99 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -14,7 +14,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
-using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks
@@ -24,11 +23,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
///
public class ChapterImagesTask : IScheduledTask
{
- ///
- /// The _logger.
- ///
- private readonly ILogger _logger;
-
///
/// The _library manager.
///
@@ -46,7 +40,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// Initializes a new instance of the class.
///
public ChapterImagesTask(
- ILoggerFactory loggerFactory,
ILibraryManager libraryManager,
IItemRepository itemRepo,
IApplicationPaths appPaths,
@@ -54,7 +47,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
IFileSystem fileSystem,
ILocalizationManager localization)
{
- _logger = loggerFactory.CreateLogger();
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_appPaths = appPaths;
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index 857df591a..d884d4f37 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -189,5 +189,4 @@ namespace Emby.Server.Implementations.Services
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
}
}
-
}
diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs
index e758cdd4d..c87e418c2 100644
--- a/Emby.Server.Implementations/Services/ServiceHandler.cs
+++ b/Emby.Server.Implementations/Services/ServiceHandler.cs
@@ -7,6 +7,7 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -82,7 +83,8 @@ namespace Emby.Server.Implementations.Services
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false);
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
-
+
+ httpRes.HttpContext.SetServiceStackRequest(httpReq);
var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
// Apply response filters
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
index 69c6844f8..442b2ab1c 100644
--- a/Emby.Server.Implementations/Services/ServicePath.cs
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -488,7 +488,8 @@ namespace Emby.Server.Implementations.Services
sb.Append(value);
for (var j = pathIx + 1; j < requestComponents.Length; j++)
{
- sb.Append(PathSeperatorChar + requestComponents[j]);
+ sb.Append(PathSeperatorChar)
+ .Append(requestComponents[j]);
}
value = sb.ToString();
@@ -505,7 +506,8 @@ namespace Emby.Server.Implementations.Services
pathIx++;
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
- sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
+ sb.Append(PathSeperatorChar)
+ .Append(requestComponents[pathIx++]);
}
value = sb.ToString();
diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs
index 04f134b8b..93fb22c4e 100644
--- a/Jellyfin.Api/Controllers/StartupController.cs
+++ b/Jellyfin.Api/Controllers/StartupController.cs
@@ -46,14 +46,12 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Configuration")]
public StartupConfigurationDto GetStartupConfiguration()
{
- var result = new StartupConfigurationDto
+ return new StartupConfigurationDto
{
UICulture = _config.Configuration.UICulture,
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
};
-
- return result;
}
///
@@ -92,10 +90,10 @@ namespace Jellyfin.Api.Controllers
///
/// The first user.
[HttpGet("User")]
- public StartupUserDto GetFirstUser()
+ public async Task GetFirstUser()
{
// 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();
return new StartupUserDto
{
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
index 53120a763..acc6eec1c 100644
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDb.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using System.Linq;
using Jellyfin.Data;
using Jellyfin.Data.Entities;
@@ -133,6 +134,18 @@ namespace Jellyfin.Server.Implementations
return base.SaveChanges();
}
+ ///
+ public override void Dispose()
+ {
+ foreach (var entry in ChangeTracker.Entries())
+ {
+ entry.State = EntityState.Detached;
+ }
+
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
///
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
index cf5a01f08..007c29643 100644
--- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
+using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
@@ -23,7 +24,7 @@ namespace Jellyfin.Server.Implementations.Users
private const string BaseResetFileName = "passwordreset";
private readonly IJsonSerializer _jsonSerializer;
- private readonly IUserManager _userManager;
+ private readonly IApplicationHost _appHost;
private readonly string _passwordResetFileBase;
private readonly string _passwordResetFileBaseDir;
@@ -33,16 +34,17 @@ namespace Jellyfin.Server.Implementations.Users
///
/// The configuration manager.
/// The JSON serializer.
- /// The user manager.
+ /// The application host.
public DefaultPasswordResetProvider(
IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer,
- IUserManager userManager)
+ IApplicationHost appHost)
{
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
_jsonSerializer = jsonSerializer;
- _userManager = userManager;
+ _appHost = appHost;
+ // TODO: Remove the circular dependency on UserManager
}
///
@@ -54,6 +56,7 @@ namespace Jellyfin.Server.Implementations.Users
///
public async Task RedeemPasswordResetPin(string pin)
{
+ var userManager = _appHost.Resolve();
var usersReset = new List();
foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
{
@@ -72,10 +75,10 @@ namespace Jellyfin.Server.Implementations.Users
pin.Replace("-", string.Empty, StringComparison.Ordinal),
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");
- await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ await userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
usersReset.Add(resetUser.Username);
File.Delete(resetFile);
}
@@ -121,7 +124,6 @@ namespace Jellyfin.Server.Implementations.Users
}
user.EasyPassword = pin;
- await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
return new ForgotPasswordResult
{
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 343f452c7..e49a99fe5 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -39,12 +39,11 @@ namespace Jellyfin.Server.Implementations.Users
private readonly IApplicationHost _appHost;
private readonly IImageProcessor _imageProcessor;
private readonly ILogger _logger;
-
- private IAuthenticationProvider[] _authenticationProviders = null!;
- private DefaultAuthenticationProvider _defaultAuthenticationProvider = null!;
- private InvalidAuthProvider _invalidAuthProvider = null!;
- private IPasswordResetProvider[] _passwordResetProviders = null!;
- private DefaultPasswordResetProvider _defaultPasswordResetProvider = null!;
+ private readonly IReadOnlyCollection _passwordResetProviders;
+ private readonly IReadOnlyCollection _authenticationProviders;
+ private readonly InvalidAuthProvider _invalidAuthProvider;
+ private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
+ private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
///
/// Initializes a new instance of the class.
@@ -69,6 +68,13 @@ namespace Jellyfin.Server.Implementations.Users
_appHost = appHost;
_imageProcessor = imageProcessor;
_logger = logger;
+
+ _passwordResetProviders = appHost.GetExports();
+ _authenticationProviders = appHost.GetExports();
+
+ _invalidAuthProvider = _authenticationProviders.OfType().First();
+ _defaultAuthenticationProvider = _authenticationProviders.OfType().First();
+ _defaultPasswordResetProvider = _passwordResetProviders.OfType().First();
}
///
@@ -102,7 +108,16 @@ namespace Jellyfin.Server.Implementations.Users
}
///
- public IEnumerable UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id);
+ public IEnumerable UsersIds
+ {
+ get
+ {
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Select(user => user.Id)
+ .ToList();
+ }
+ }
///
public User? GetUserById(Guid id)
@@ -188,8 +203,24 @@ namespace Jellyfin.Server.Implementations.Users
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
+ internal async Task 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
+ };
+ }
+
///
- public User CreateUser(string name)
+ public async Task CreateUserAsync(string name)
{
if (!IsValidUsername(name))
{
@@ -198,18 +229,10 @@ namespace Jellyfin.Server.Implementations.Users
using var dbContext = _dbProvider.CreateContext();
- // TODO: Remove after user item data is migrated.
- var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0;
+ var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
- var newUser = new User(
- name,
- _defaultAuthenticationProvider.GetType().FullName,
- _defaultPasswordResetProvider.GetType().FullName)
- {
- InternalId = max + 1
- };
dbContext.Users.Add(newUser);
- dbContext.SaveChanges();
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
OnUserCreated?.Invoke(this, new GenericEventArgs(newUser));
@@ -512,7 +535,7 @@ namespace Jellyfin.Server.Implementations.Users
}
else
{
- IncrementInvalidLoginAttemptCount(user);
+ await IncrementInvalidLoginAttemptCount(user).ConfigureAwait(false);
_logger.LogInformation(
"Authentication request for {UserName} has been denied (IP: {IP}).",
user.Username,
@@ -530,7 +553,12 @@ namespace Jellyfin.Server.Implementations.Users
if (user != null && isInNetwork)
{
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
@@ -560,24 +588,13 @@ namespace Jellyfin.Server.Implementations.Users
};
}
- ///
- public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders)
- {
- _authenticationProviders = authenticationProviders.ToArray();
- _passwordResetProviders = passwordResetProviders.ToArray();
-
- _invalidAuthProvider = _authenticationProviders.OfType().First();
- _defaultAuthenticationProvider = _authenticationProviders.OfType().First();
- _defaultPasswordResetProvider = _passwordResetProviders.OfType().First();
- }
-
///
- public void Initialize()
+ public async Task InitializeAsync()
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
using var dbContext = _dbProvider.CreateContext();
- if (dbContext.Users.Any())
+ if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
{
return;
}
@@ -595,13 +612,13 @@ namespace Jellyfin.Server.Implementations.Users
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.EnableContentDeletion, true);
newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
- dbContext.Users.Update(newUser);
- dbContext.SaveChanges();
+ dbContext.Users.Add(newUser);
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
///
@@ -637,7 +654,7 @@ namespace Jellyfin.Server.Implementations.Users
///
public void UpdateConfiguration(Guid userId, UserConfiguration config)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
var user = dbContext.Users
.Include(u => u.Permissions)
.Include(u => u.Preferences)
@@ -670,7 +687,7 @@ namespace Jellyfin.Server.Implementations.Users
///
public void UpdatePolicy(Guid userId, UserPolicy policy)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
var user = dbContext.Users
.Include(u => u.Permissions)
.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++;
int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
@@ -896,7 +913,7 @@ namespace Jellyfin.Server.Implementations.Users
user.InvalidLoginAttemptCount);
}
- UpdateUser(user);
+ await UpdateUserAsync(user).ConfigureAwait(false);
}
}
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 207eaa98d..71b0fd8f3 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -9,6 +9,7 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
@@ -33,9 +34,9 @@ namespace Jellyfin.Server
/// The to be used by the .
/// The to be used by the .
public CoreAppHost(
- ServerApplicationPaths applicationPaths,
+ IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
- StartupOptions options,
+ IStartupOptions options,
IFileSystem fileSystem,
INetworkManager networkManager)
: base(
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index fe8910c3c..98a90500c 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -21,7 +21,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.MigrateActivityLogDb),
typeof(Routines.RemoveDuplicateExtras),
typeof(Routines.AddDefaultPluginRepository),
- typeof(Routines.MigrateUserDb)
+ typeof(Routines.MigrateUserDb),
+ typeof(Routines.ReaddDefaultPluginRepository)
};
///
diff --git a/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs
new file mode 100644
index 000000000..b281b5cc0
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs
@@ -0,0 +1,49 @@
+using System;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Updates;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ ///
+ /// Migration to initialize system configuration with the default plugin repository.
+ ///
+ 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"
+ };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
+ {
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
+ ///
+ public Guid Id => Guid.Parse("5F86E7F6-D966-4C77-849D-7A7B40B68C4E");
+
+ ///
+ public string Name => "ReaddDefaultPluginRepository";
+
+ ///
+ public bool PerformOnNewInstall => true;
+
+ ///
+ public void Perform()
+ {
+ // Only add if repository list is empty
+ if (_serverConfigurationManager.Configuration.PluginRepositories.Count == 0)
+ {
+ _serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
+ _serverConfigurationManager.SaveConfiguration();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index ef3ebe90c..444a91c02 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -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))
.UseSerilog()
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index 6e9d788dc..440d1840d 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -525,7 +525,7 @@ namespace MediaBrowser.Api
/// System.Object.
public async Task