|
@@ -1,20 +1,18 @@
|
|
#pragma warning disable CS1591
|
|
#pragma warning disable CS1591
|
|
-
|
|
|
|
using System;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection;
|
|
-using System.Text;
|
|
|
|
using System.Text.Json;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
using Emby.Dlna.Profiles;
|
|
using Emby.Dlna.Profiles;
|
|
using Emby.Dlna.Server;
|
|
using Emby.Dlna.Server;
|
|
|
|
+using Jellyfin.Extensions.Json;
|
|
using MediaBrowser.Common.Configuration;
|
|
using MediaBrowser.Common.Configuration;
|
|
using MediaBrowser.Common.Extensions;
|
|
using MediaBrowser.Common.Extensions;
|
|
-using MediaBrowser.Common.Json;
|
|
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller.Dlna;
|
|
using MediaBrowser.Controller.Dlna;
|
|
using MediaBrowser.Controller.Drawing;
|
|
using MediaBrowser.Controller.Drawing;
|
|
@@ -36,7 +34,7 @@ namespace Emby.Dlna
|
|
private readonly ILogger<DlnaManager> _logger;
|
|
private readonly ILogger<DlnaManager> _logger;
|
|
private readonly IServerApplicationHost _appHost;
|
|
private readonly IServerApplicationHost _appHost;
|
|
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
|
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
|
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
|
|
|
|
|
|
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
|
|
|
|
|
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
|
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
|
|
|
|
|
@@ -94,12 +92,14 @@ namespace Emby.Dlna
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
public DeviceProfile GetDefaultProfile()
|
|
public DeviceProfile GetDefaultProfile()
|
|
{
|
|
{
|
|
return new DefaultProfile();
|
|
return new DefaultProfile();
|
|
}
|
|
}
|
|
|
|
|
|
- public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
|
+ public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
|
|
{
|
|
{
|
|
if (deviceInfo == null)
|
|
if (deviceInfo == null)
|
|
{
|
|
{
|
|
@@ -109,109 +109,57 @@ namespace Emby.Dlna
|
|
var profile = GetProfiles()
|
|
var profile = GetProfiles()
|
|
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
|
|
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
|
|
|
|
|
|
- if (profile != null)
|
|
|
|
|
|
+ if (profile == null)
|
|
{
|
|
{
|
|
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
|
|
|
|
|
+ _logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- LogUnmatchedProfile(deviceInfo);
|
|
|
|
|
|
+ _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
|
|
}
|
|
}
|
|
|
|
|
|
return profile;
|
|
return profile;
|
|
}
|
|
}
|
|
|
|
|
|
- private void LogUnmatchedProfile(DeviceIdentification profile)
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Attempts to match a device with a profile.
|
|
|
|
+ /// Rules:
|
|
|
|
+ /// - If the profile field has no value, the field matches irregardless of its contents.
|
|
|
|
+ /// - the profile field can be an exact match, or a reg exp.
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
|
|
|
|
+ /// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
|
|
|
|
+ /// <returns><b>True</b> if they match.</returns>
|
|
|
|
+ public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
|
{
|
|
{
|
|
- var builder = new StringBuilder();
|
|
|
|
-
|
|
|
|
- builder.AppendLine("No matching device profile found. The default will need to be used.");
|
|
|
|
- builder.Append("FriendlyName:").AppendLine(profile.FriendlyName);
|
|
|
|
- builder.Append("Manufacturer:").AppendLine(profile.Manufacturer);
|
|
|
|
- builder.Append("ManufacturerUrl:").AppendLine(profile.ManufacturerUrl);
|
|
|
|
- builder.Append("ModelDescription:").AppendLine(profile.ModelDescription);
|
|
|
|
- builder.Append("ModelName:").AppendLine(profile.ModelName);
|
|
|
|
- builder.Append("ModelNumber:").AppendLine(profile.ModelNumber);
|
|
|
|
- builder.Append("ModelUrl:").AppendLine(profile.ModelUrl);
|
|
|
|
- builder.Append("SerialNumber:").AppendLine(profile.SerialNumber);
|
|
|
|
-
|
|
|
|
- _logger.LogInformation(builder.ToString());
|
|
|
|
|
|
+ return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
|
|
|
|
+ && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
|
|
}
|
|
}
|
|
|
|
|
|
- private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
|
|
|
|
|
+ private bool IsRegexOrSubstringMatch(string input, string pattern)
|
|
{
|
|
{
|
|
- if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
|
|
|
|
- {
|
|
|
|
- if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
|
|
|
|
- {
|
|
|
|
- if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
|
|
|
|
- {
|
|
|
|
- if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
|
|
|
|
- {
|
|
|
|
- if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.ModelName))
|
|
|
|
- {
|
|
|
|
- if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
|
|
|
|
|
|
+ if (string.IsNullOrEmpty(pattern))
|
|
{
|
|
{
|
|
- if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
|
|
|
|
- {
|
|
|
|
- if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ // In profile identification: An empty pattern matches anything.
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
- if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
|
|
|
|
|
|
+ if (string.IsNullOrEmpty(input))
|
|
{
|
|
{
|
|
- if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ // The profile contains a value, and the device doesn't.
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private bool IsRegexOrSubstringMatch(string input, string pattern)
|
|
|
|
- {
|
|
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
|
|
|
|
+ return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
|
}
|
|
}
|
|
catch (ArgumentException ex)
|
|
catch (ArgumentException ex)
|
|
{
|
|
{
|
|
@@ -220,7 +168,8 @@ namespace Emby.Dlna
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public DeviceProfile GetProfile(IHeaderDictionary headers)
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
|
+ public DeviceProfile? GetProfile(IHeaderDictionary headers)
|
|
{
|
|
{
|
|
if (headers == null)
|
|
if (headers == null)
|
|
{
|
|
{
|
|
@@ -228,15 +177,13 @@ namespace Emby.Dlna
|
|
}
|
|
}
|
|
|
|
|
|
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
|
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
|
-
|
|
|
|
- if (profile != null)
|
|
|
|
|
|
+ if (profile == null)
|
|
{
|
|
{
|
|
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
|
|
|
|
|
+ _logger.LogDebug("No matching device profile found. {@Headers}", headers);
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
- var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
|
|
|
|
- _logger.LogDebug("No matching device profile found. {0}", headerString);
|
|
|
|
|
|
+ _logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
|
}
|
|
}
|
|
|
|
|
|
return profile;
|
|
return profile;
|
|
@@ -286,19 +233,19 @@ namespace Emby.Dlna
|
|
return xmlFies
|
|
return xmlFies
|
|
.Select(i => ParseProfileFile(i, type))
|
|
.Select(i => ParseProfileFile(i, type))
|
|
.Where(i => i != null)
|
|
.Where(i => i != null)
|
|
- .ToList();
|
|
|
|
|
|
+ .ToList()!; // We just filtered out all the nulls
|
|
}
|
|
}
|
|
catch (IOException)
|
|
catch (IOException)
|
|
{
|
|
{
|
|
- return new List<DeviceProfile>();
|
|
|
|
|
|
+ return Array.Empty<DeviceProfile>();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
|
|
|
|
|
|
+ private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
|
|
{
|
|
{
|
|
lock (_profiles)
|
|
lock (_profiles)
|
|
{
|
|
{
|
|
- if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
|
|
|
|
|
|
+ if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
|
|
{
|
|
{
|
|
return profileTuple.Item2;
|
|
return profileTuple.Item2;
|
|
}
|
|
}
|
|
@@ -326,14 +273,20 @@ namespace Emby.Dlna
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public DeviceProfile GetProfile(string id)
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
|
+ public DeviceProfile? GetProfile(string id)
|
|
{
|
|
{
|
|
if (string.IsNullOrEmpty(id))
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
{
|
|
throw new ArgumentNullException(nameof(id));
|
|
throw new ArgumentNullException(nameof(id));
|
|
}
|
|
}
|
|
|
|
|
|
- var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
+ var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
|
|
|
|
+
|
|
|
|
+ if (info == null)
|
|
|
|
+ {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
|
|
return ParseProfileFile(info.Path, info.Info.Type);
|
|
return ParseProfileFile(info.Path, info.Info.Type);
|
|
}
|
|
}
|
|
@@ -350,6 +303,7 @@ namespace Emby.Dlna
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
|
|
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
|
|
{
|
|
{
|
|
return GetProfileInfosInternal().Select(i => i.Info);
|
|
return GetProfileInfosInternal().Select(i => i.Info);
|
|
@@ -357,17 +311,14 @@ namespace Emby.Dlna
|
|
|
|
|
|
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
|
|
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
|
|
{
|
|
{
|
|
- return new InternalProfileInfo
|
|
|
|
- {
|
|
|
|
- Path = file.FullName,
|
|
|
|
-
|
|
|
|
- Info = new DeviceProfileInfo
|
|
|
|
|
|
+ return new InternalProfileInfo(
|
|
|
|
+ new DeviceProfileInfo
|
|
{
|
|
{
|
|
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
|
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
|
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
|
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
|
Type = type
|
|
Type = type
|
|
- }
|
|
|
|
- };
|
|
|
|
|
|
+ },
|
|
|
|
+ file.FullName);
|
|
}
|
|
}
|
|
|
|
|
|
private async Task ExtractSystemProfilesAsync()
|
|
private async Task ExtractSystemProfilesAsync()
|
|
@@ -387,15 +338,20 @@ namespace Emby.Dlna
|
|
systemProfilesPath,
|
|
systemProfilesPath,
|
|
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
|
|
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
|
|
|
|
|
|
- using (var stream = _assembly.GetManifestResourceStream(name))
|
|
|
|
|
|
+ // The stream should exist as we just got its name from GetManifestResourceNames
|
|
|
|
+ using (var stream = _assembly.GetManifestResourceStream(name)!)
|
|
{
|
|
{
|
|
|
|
+ var length = stream.Length;
|
|
var fileInfo = _fileSystem.GetFileInfo(path);
|
|
var fileInfo = _fileSystem.GetFileInfo(path);
|
|
|
|
|
|
- if (!fileInfo.Exists || fileInfo.Length != stream.Length)
|
|
|
|
|
|
+ if (!fileInfo.Exists || fileInfo.Length != length)
|
|
{
|
|
{
|
|
Directory.CreateDirectory(systemProfilesPath);
|
|
Directory.CreateDirectory(systemProfilesPath);
|
|
|
|
|
|
- using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
|
|
|
|
|
+ var fileOptions = AsyncFile.WriteOptions;
|
|
|
|
+ fileOptions.Mode = FileMode.Create;
|
|
|
|
+ fileOptions.PreallocationSize = length;
|
|
|
|
+ using (var fileStream = new FileStream(path, fileOptions))
|
|
{
|
|
{
|
|
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
|
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
|
}
|
|
}
|
|
@@ -407,6 +363,7 @@ namespace Emby.Dlna
|
|
Directory.CreateDirectory(UserProfilesPath);
|
|
Directory.CreateDirectory(UserProfilesPath);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
public void DeleteProfile(string id)
|
|
public void DeleteProfile(string id)
|
|
{
|
|
{
|
|
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
|
|
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
|
|
@@ -424,6 +381,7 @@ namespace Emby.Dlna
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
public void CreateProfile(DeviceProfile profile)
|
|
public void CreateProfile(DeviceProfile profile)
|
|
{
|
|
{
|
|
profile = ReserializeProfile(profile);
|
|
profile = ReserializeProfile(profile);
|
|
@@ -439,7 +397,8 @@ namespace Emby.Dlna
|
|
SaveProfile(profile, path, DeviceProfileType.User);
|
|
SaveProfile(profile, path, DeviceProfileType.User);
|
|
}
|
|
}
|
|
|
|
|
|
- public void UpdateProfile(DeviceProfile profile)
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
|
+ public void UpdateProfile(string profileId, DeviceProfile profile)
|
|
{
|
|
{
|
|
profile = ReserializeProfile(profile);
|
|
profile = ReserializeProfile(profile);
|
|
|
|
|
|
@@ -453,7 +412,7 @@ namespace Emby.Dlna
|
|
throw new ArgumentException("Profile is missing Name");
|
|
throw new ArgumentException("Profile is missing Name");
|
|
}
|
|
}
|
|
|
|
|
|
- var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
+ var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
|
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
|
var path = Path.Combine(UserProfilesPath, newFilename);
|
|
var path = Path.Combine(UserProfilesPath, newFilename);
|
|
@@ -497,9 +456,11 @@ namespace Emby.Dlna
|
|
|
|
|
|
var json = JsonSerializer.Serialize(profile, _jsonOptions);
|
|
var json = JsonSerializer.Serialize(profile, _jsonOptions);
|
|
|
|
|
|
- return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions);
|
|
|
|
|
|
+ // Output can't be null if the input isn't null
|
|
|
|
+ return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
|
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
|
{
|
|
{
|
|
var profile = GetDefaultProfile();
|
|
var profile = GetDefaultProfile();
|
|
@@ -509,26 +470,37 @@ namespace Emby.Dlna
|
|
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
|
|
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
|
|
}
|
|
}
|
|
|
|
|
|
- public ImageStream GetIcon(string filename)
|
|
|
|
|
|
+ /// <inheritdoc />
|
|
|
|
+ public ImageStream? GetIcon(string filename)
|
|
{
|
|
{
|
|
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
|
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
|
? ImageFormat.Png
|
|
? ImageFormat.Png
|
|
: ImageFormat.Jpg;
|
|
: ImageFormat.Jpg;
|
|
|
|
|
|
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
|
|
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
|
|
|
|
+ var stream = _assembly.GetManifestResourceStream(resource);
|
|
|
|
+ if (stream == null)
|
|
|
|
+ {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
|
|
- return new ImageStream
|
|
|
|
|
|
+ return new ImageStream(stream)
|
|
{
|
|
{
|
|
- Format = format,
|
|
|
|
- Stream = _assembly.GetManifestResourceStream(resource)
|
|
|
|
|
|
+ Format = format
|
|
};
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
private class InternalProfileInfo
|
|
private class InternalProfileInfo
|
|
{
|
|
{
|
|
- internal DeviceProfileInfo Info { get; set; }
|
|
|
|
|
|
+ internal InternalProfileInfo(DeviceProfileInfo info, string path)
|
|
|
|
+ {
|
|
|
|
+ Info = info;
|
|
|
|
+ Path = path;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ internal DeviceProfileInfo Info { get; }
|
|
|
|
|
|
- internal string Path { get; set; }
|
|
|
|
|
|
+ internal string Path { get; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -553,7 +525,7 @@ namespace Emby.Dlna
|
|
|
|
|
|
private void DumpProfiles()
|
|
private void DumpProfiles()
|
|
{
|
|
{
|
|
- DeviceProfile[] list = new []
|
|
|
|
|
|
+ DeviceProfile[] list = new[]
|
|
{
|
|
{
|
|
new SamsungSmartTvProfile(),
|
|
new SamsungSmartTvProfile(),
|
|
new XboxOneProfile(),
|
|
new XboxOneProfile(),
|