Merge branch 'master' into library_scan_speed

This commit is contained in:
BaronGreenback
2020-11-21 22:54:40 +00:00
committed by GitHub
118 changed files with 7464 additions and 1408 deletions

View File

@@ -634,7 +634,7 @@ namespace Emby.Server.Implementations.Channels
{
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
if (query.ChannelIds.Length > 0)
if (query.ChannelIds.Count > 0)
{
// Avoid implicitly captured closure
var ids = query.ChannelIds;

View File

@@ -3611,12 +3611,12 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add($"type in ({inClause})");
}
if (query.ChannelIds.Length == 1)
if (query.ChannelIds.Count == 1)
{
whereClauses.Add("ChannelId=@ChannelId");
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
}
else if (query.ChannelIds.Length > 1)
else if (query.ChannelIds.Count > 1)
{
var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add($"ChannelId in ({inClause})");
@@ -4076,7 +4076,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(clause);
}
if (query.GenreIds.Length > 0)
if (query.GenreIds.Count > 0)
{
var clauses = new List<string>();
var index = 0;
@@ -4097,7 +4097,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(clause);
}
if (query.Genres.Length > 0)
if (query.Genres.Count > 0)
{
var clauses = new List<string>();
var index = 0;

View File

@@ -1,61 +1,38 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Devices;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Caching.Memory;
namespace Emby.Server.Implementations.Devices
{
public class DeviceManager : IDeviceManager
{
private readonly IMemoryCache _memoryCache;
private readonly IJsonSerializer _json;
private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config;
private readonly IAuthenticationRepository _authRepo;
private readonly object _capabilitiesSyncLock = new object();
private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new ();
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
public DeviceManager(
IAuthenticationRepository authRepo,
IJsonSerializer json,
IUserManager userManager,
IServerConfigurationManager config,
IMemoryCache memoryCache)
public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager)
{
_json = json;
_userManager = userManager;
_config = config;
_memoryCache = memoryCache;
_authRepo = authRepo;
}
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
Directory.CreateDirectory(Path.GetDirectoryName(path));
lock (_capabilitiesSyncLock)
{
_memoryCache.Set(deviceId, capabilities);
_json.SerializeToFile(capabilities, path);
}
_capabilitiesMap[deviceId] = capabilities;
}
public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
@@ -72,32 +49,12 @@ namespace Emby.Server.Implementations.Devices
public ClientCapabilities GetCapabilities(string id)
{
if (_memoryCache.TryGetValue(id, out ClientCapabilities result))
{
return result;
}
lock (_capabilitiesSyncLock)
{
var path = Path.Combine(GetDevicePath(id), "capabilities.json");
try
{
return _json.DeserializeFromFile<ClientCapabilities>(path) ?? new ClientCapabilities();
}
catch
{
}
}
return new ClientCapabilities();
return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result)
? result
: new ClientCapabilities();
}
public DeviceInfo GetDevice(string id)
{
return GetDevice(id, true);
}
private DeviceInfo GetDevice(string id, bool includeCapabilities)
{
var session = _authRepo.Get(new AuthenticationInfoQuery
{
@@ -154,16 +111,6 @@ namespace Emby.Server.Implementations.Devices
};
}
private string GetDevicesPath()
{
return Path.Combine(_config.ApplicationPaths.DataPath, "devices");
}
private string GetDevicePath(string id)
{
return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture));
}
public bool CanAccessDevice(User user, string deviceId)
{
if (user == null)

View File

@@ -1503,7 +1503,7 @@ namespace Emby.Server.Implementations.Library
{
if (query.AncestorIds.Length == 0 &&
query.ParentId.Equals(Guid.Empty) &&
query.ChannelIds.Length == 0 &&
query.ChannelIds.Count == 0 &&
query.TopParentIds.Length == 0 &&
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&

View File

@@ -111,11 +111,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
{
using (var client = new TcpClient(new IPEndPoint(remoteIp, HdHomeRunPort)))
using (var stream = client.GetStream())
{
return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);
}
using var client = new TcpClient();
client.Connect(remoteIp, HdHomeRunPort);
using var stream = client.GetStream();
return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);
}
private static async Task<bool> CheckTunerAvailability(NetworkStream stream, int tuner, CancellationToken cancellationToken)
@@ -142,7 +142,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
_remoteEndPoint = new IPEndPoint(remoteIp, HdHomeRunPort);
_tcpClient = new TcpClient(_remoteEndPoint);
_tcpClient = new TcpClient();
_tcpClient.Connect(_remoteEndPoint);
if (!_lockkey.HasValue)
{
@@ -221,30 +222,30 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return;
}
using (var tcpClient = new TcpClient(_remoteEndPoint))
using (var stream = tcpClient.GetStream())
{
var commandList = commands.GetCommands();
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
using var tcpClient = new TcpClient();
tcpClient.Connect(_remoteEndPoint);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
return;
}
using var stream = tcpClient.GetStream();
var commandList = commands.GetCommands();
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
foreach (var command in commandList)
{
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(buffer, receivedBytes, out _))
{
return;
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}

View File

@@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort).ConfigureAwait(false);
localAddress = ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
tcpClient.Close();
}
catch (Exception ex)
@@ -80,6 +80,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
if (localAddress.IsIPv4MappedToIPv6) {
localAddress = localAddress.MapToIPv4();
}
var udpClient = new UdpClient(localPort, AddressFamily.InterNetwork);
var hdHomerunManager = new HdHomerunManager();
@@ -110,12 +114,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>();
await StartStreaming(
_ = StartStreaming(
udpClient,
hdHomerunManager,
remoteAddress,
taskCompletionSource,
LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
LiveStreamCancellationTokenSource.Token);
// OpenedMediaSource.Protocol = MediaProtocol.File;
// OpenedMediaSource.Path = tempFile;
@@ -136,33 +140,30 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return TempFilePath;
}
private Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
return Task.Run(async () =>
using (udpClient)
using (hdHomerunManager)
{
using (udpClient)
using (hdHomerunManager)
try
{
try
{
await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
openTaskCompletionSource.TrySetException(ex);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error opening live stream:");
openTaskCompletionSource.TrySetException(ex);
}
EnableStreamSharing = false;
await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
openTaskCompletionSource.TrySetException(ex);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error opening live stream:");
openTaskCompletionSource.TrySetException(ex);
}
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
});
EnableStreamSharing = false;
}
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
}
private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)

View File

@@ -113,5 +113,10 @@
"TaskCleanTranscode": "Καθαρισμός Kαταλόγου Διακωδικοποιητή",
"TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τις προσθήκες που έχουν ρυθμιστεί για αυτόματη ενημέρωση.",
"TaskUpdatePlugins": "Ενημέρωση Προσθηκών",
"TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας."
"TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας.",
"TaskCleanActivityLogDescription": "Διαγράφει καταχωρήσεις απο το αρχείο καταγραφής δραστηριοτήτων παλαιότερες από την ηλικία που έχει διαμορφωθεί.",
"TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
"Undefined": "Απροσδιόριστο",
"Forced": "Εξαναγκασμένο",
"Default": "Προεπιλογή"
}

View File

@@ -115,5 +115,8 @@
"TaskRefreshChannels": "Csatornák frissítése",
"TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat.",
"TaskCleanActivityLogDescription": "A beállítottnál korábbi bejegyzések törlése a tevékenységnaplóból.",
"TaskCleanActivityLog": "Tevékenységnapló törlése"
"TaskCleanActivityLog": "Tevékenységnapló törlése",
"Undefined": "Meghatározatlan",
"Forced": "Kényszerített",
"Default": "Alapértelmezett"
}

View File

@@ -18,13 +18,12 @@ namespace Emby.Server.Implementations.Networking
public class NetworkManager : INetworkManager
{
private readonly ILogger<NetworkManager> _logger;
private IPAddress[] _localIpAddresses;
private readonly object _localIpAddressSyncLock = new object();
private readonly object _subnetLookupLock = new object();
private readonly Dictionary<string, List<string>> _subnetLookup = new Dictionary<string, List<string>>(StringComparer.Ordinal);
private IPAddress[] _localIpAddresses;
private List<PhysicalAddress> _macAddresses;
/// <summary>
@@ -157,7 +156,9 @@ namespace Emby.Server.Implementations.Networking
return false;
}
byte[] octet = ipAddress.GetAddressBytes();
// GetAddressBytes
Span<byte> octet = stackalloc byte[ipAddress.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
ipAddress.TryWriteBytes(octet, out _);
if ((octet[0] == 10) ||
(octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
@@ -260,7 +261,9 @@ namespace Emby.Server.Implementations.Networking
/// <inheritdoc/>
public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC)
{
byte[] octet = address.GetAddressBytes();
// GetAddressBytes
Span<byte> octet = stackalloc byte[address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16];
address.TryWriteBytes(octet, out _);
if ((octet[0] == 127) || // RFC1122
(octet[0] == 169 && octet[1] == 254)) // RFC3927
@@ -503,18 +506,25 @@ namespace Emby.Server.Implementations.Networking
private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
{
byte[] ipAdressBytes = address.GetAddressBytes();
byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
int size = address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16;
if (ipAdressBytes.Length != subnetMaskBytes.Length)
// GetAddressBytes
Span<byte> ipAddressBytes = stackalloc byte[size];
address.TryWriteBytes(ipAddressBytes, out _);
// GetAddressBytes
Span<byte> subnetMaskBytes = stackalloc byte[size];
subnetMask.TryWriteBytes(subnetMaskBytes, out _);
if (ipAddressBytes.Length != subnetMaskBytes.Length)
{
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
}
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
byte[] broadcastAddress = new byte[ipAddressBytes.Length];
for (int i = 0; i < broadcastAddress.Length; i++)
{
broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
broadcastAddress[i] = (byte)(ipAddressBytes[i] & subnetMaskBytes[i]);
}
return new IPAddress(broadcastAddress);

View File

@@ -150,7 +150,7 @@ namespace Emby.Server.Implementations.Playlists
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
.ConfigureAwait(false);
if (options.ItemIdList.Length > 0)
if (options.ItemIdList.Count > 0)
{
await AddToPlaylistInternal(playlist.Id, options.ItemIdList, user, new DtoOptions(false)
{
@@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.Playlists
return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
}
public Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId)
public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
{
var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
@@ -194,7 +194,7 @@ namespace Emby.Server.Implementations.Playlists
});
}
private async Task AddToPlaylistInternal(Guid playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOptions options)
{
// Retrieve the existing playlist
var playlist = _libraryManager.GetItemById(playlistId) as Playlist

View File

@@ -58,8 +58,7 @@ namespace Emby.Server.Implementations.Session
/// <summary>
/// The active connections.
/// </summary>
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new (StringComparer.OrdinalIgnoreCase);
private Timer _idleTimer;
@@ -196,7 +195,7 @@ namespace Emby.Server.Implementations.Session
{
if (!string.IsNullOrEmpty(info.DeviceId))
{
var capabilities = GetSavedCapabilities(info.DeviceId);
var capabilities = _deviceManager.GetCapabilities(info.DeviceId);
if (capabilities != null)
{
@@ -1677,27 +1676,10 @@ namespace Emby.Server.Implementations.Session
SessionInfo = session
});
try
{
SaveCapabilities(session.DeviceId, capabilities);
}
catch (Exception ex)
{
_logger.LogError("Error saving device capabilities", ex);
}
_deviceManager.SaveCapabilities(session.DeviceId, capabilities);
}
}
private ClientCapabilities GetSavedCapabilities(string deviceId)
{
return _deviceManager.GetCapabilities(deviceId);
}
private void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
_deviceManager.SaveCapabilities(deviceId, capabilities);
}
/// <summary>
/// Converts a BaseItem to a BaseItemInfo.
/// </summary>

View File

@@ -93,17 +93,29 @@ namespace Emby.Server.Implementations.Updates
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
/// <inheritdoc />
public async Task<IReadOnlyList<PackageInfo>> GetPackages(string manifest, CancellationToken cancellationToken = default)
public async Task<IList<PackageInfo>> GetPackages(string manifestName, string manifest, CancellationToken cancellationToken = default)
{
try
{
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.GetAsync(manifest, cancellationToken).ConfigureAwait(false);
.GetAsync(new Uri(manifest), cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
try
{
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
var package = await _jsonSerializer.DeserializeFromStreamAsync<IList<PackageInfo>>(stream).ConfigureAwait(false);
// Store the repository and repository url with each version, as they may be spread apart.
foreach (var entry in package)
{
foreach (var ver in entry.versions)
{
ver.repositoryName = manifestName;
ver.repositoryUrl = manifest;
}
}
return package;
}
catch (SerializationException ex)
{
@@ -123,17 +135,69 @@ namespace Emby.Server.Implementations.Updates
}
}
private static void MergeSort(IList<VersionInfo> source, IList<VersionInfo> dest)
{
int sLength = source.Count - 1;
int dLength = dest.Count;
int s = 0, d = 0;
var sourceVersion = source[0].VersionNumber;
var destVersion = dest[0].VersionNumber;
while (d < dLength)
{
if (sourceVersion.CompareTo(destVersion) >= 0)
{
if (s < sLength)
{
sourceVersion = source[++s].VersionNumber;
}
else
{
// Append all of destination to the end of source.
while (d < dLength)
{
source.Add(dest[d++]);
}
break;
}
}
else
{
source.Insert(s++, dest[d++]);
if (d >= dLength)
{
break;
}
sLength++;
destVersion = dest[d].VersionNumber;
}
}
}
/// <inheritdoc />
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
{
var result = new List<PackageInfo>();
foreach (RepositoryInfo repository in _config.Configuration.PluginRepositories)
{
foreach (var package in await GetPackages(repository.Url, cancellationToken).ConfigureAwait(true))
if (repository.Enabled)
{
package.repositoryName = repository.Name;
package.repositoryUrl = repository.Url;
result.Add(package);
// Where repositories have the same content, the details of the first is taken.
foreach (var package in await GetPackages(repository.Name, repository.Url, cancellationToken).ConfigureAwait(true))
{
var existing = FilterPackages(result, package.name, Guid.Parse(package.guid)).FirstOrDefault();
if (existing != null)
{
// Assumption is both lists are ordered, so slot these into the correct place.
MergeSort(existing.versions, package.versions);
}
else
{
result.Add(package);
}
}
}
}
@@ -144,7 +208,8 @@ namespace Emby.Server.Implementations.Updates
public IEnumerable<PackageInfo> FilterPackages(
IEnumerable<PackageInfo> availablePackages,
string name = null,
Guid guid = default)
Guid guid = default,
Version specificVersion = null)
{
if (name != null)
{
@@ -156,6 +221,11 @@ namespace Emby.Server.Implementations.Updates
availablePackages = availablePackages.Where(x => Guid.Parse(x.guid) == guid);
}
if (specificVersion != null)
{
availablePackages = availablePackages.Where(x => x.versions.Where(y => y.VersionNumber.Equals(specificVersion)).Any());
}
return availablePackages;
}
@@ -167,7 +237,7 @@ namespace Emby.Server.Implementations.Updates
Version minVersion = null,
Version specificVersion = null)
{
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
var package = FilterPackages(availablePackages, name, guid, specificVersion).FirstOrDefault();
// Package not found in repository
if (package == null)
@@ -181,21 +251,21 @@ namespace Emby.Server.Implementations.Updates
if (specificVersion != null)
{
availableVersions = availableVersions.Where(x => new Version(x.version) == specificVersion);
availableVersions = availableVersions.Where(x => x.VersionNumber.Equals(specificVersion));
}
else if (minVersion != null)
{
availableVersions = availableVersions.Where(x => new Version(x.version) >= minVersion);
availableVersions = availableVersions.Where(x => x.VersionNumber >= minVersion);
}
foreach (var v in availableVersions.OrderByDescending(x => x.version))
foreach (var v in availableVersions.OrderByDescending(x => x.VersionNumber))
{
yield return new InstallationInfo
{
Changelog = v.changelog,
Guid = new Guid(package.guid),
Name = package.name,
Version = new Version(v.version),
Version = v.VersionNumber,
SourceUrl = v.sourceUrl,
Checksum = v.checksum
};
@@ -333,7 +403,7 @@ namespace Emby.Server.Implementations.Updates
string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false);
.GetAsync(new Uri(package.SourceUrl), cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
// CA5351: Do Not Use Broken Cryptographic Algorithms