mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-02 22:08:27 +01:00
Merge branch 'master' into NetworkPR2
This commit is contained in:
130
Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs
Normal file
130
Emby.Server.Implementations/Library/ImageFetcherPostScanTask.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// A library post scan/refresh task for pre-fetching remote images.
|
||||
/// </summary>
|
||||
public class ImageFetcherPostScanTask : ILibraryPostScanTask
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly ILogger<ImageFetcherPostScanTask> _logger;
|
||||
private readonly SemaphoreSlim _imageFetcherLock;
|
||||
|
||||
private ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)> _queuedItems;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ImageFetcherPostScanTask"/> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">An instance of <see cref="ILibraryManager"/>.</param>
|
||||
/// <param name="providerManager">An instance of <see cref="IProviderManager"/>.</param>
|
||||
/// <param name="logger">An instance of <see cref="ILogger{ImageFetcherPostScanTask}"/>.</param>
|
||||
public ImageFetcherPostScanTask(
|
||||
ILibraryManager libraryManager,
|
||||
IProviderManager providerManager,
|
||||
ILogger<ImageFetcherPostScanTask> logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_providerManager = providerManager;
|
||||
_logger = logger;
|
||||
_queuedItems = new ConcurrentDictionary<Guid, (BaseItem item, ItemUpdateType updateReason)>();
|
||||
_imageFetcherLock = new SemaphoreSlim(1, 1);
|
||||
_libraryManager.ItemAdded += OnLibraryManagerItemAddedOrUpdated;
|
||||
_libraryManager.ItemUpdated += OnLibraryManagerItemAddedOrUpdated;
|
||||
_providerManager.RefreshCompleted += OnProviderManagerRefreshCompleted;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
// Sometimes a library scan will cause this to run twice if there's an item refresh going on.
|
||||
await _imageFetcherLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var itemGuids = _queuedItems.Keys.ToList();
|
||||
|
||||
for (var i = 0; i < itemGuids.Count; i++)
|
||||
{
|
||||
if (!_queuedItems.TryGetValue(itemGuids[i], out var queuedItem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemId = queuedItem.item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
var itemType = queuedItem.item.GetType();
|
||||
_logger.LogDebug(
|
||||
"Updating remote images for item {ItemId} with media type {ItemMediaType}",
|
||||
itemId,
|
||||
itemType);
|
||||
try
|
||||
{
|
||||
await _libraryManager.UpdateImagesAsync(queuedItem.item, queuedItem.updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to fetch images for {Type} item with id {ItemId}", itemType, itemId);
|
||||
}
|
||||
|
||||
_queuedItems.TryRemove(queuedItem.item.Id, out _);
|
||||
}
|
||||
|
||||
if (itemGuids.Count > 0)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Finished updating/pre-fetching {NumberOfImages} images. Elapsed time: {TimeElapsed}s.",
|
||||
itemGuids.Count.ToString(CultureInfo.InvariantCulture),
|
||||
(DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("No images were updated.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_imageFetcherLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLibraryManagerItemAddedOrUpdated(object sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
if (!_queuedItems.ContainsKey(itemChangeEventArgs.Item.Id) && itemChangeEventArgs.Item.ImageInfos.Length > 0)
|
||||
{
|
||||
_queuedItems.AddOrUpdate(
|
||||
itemChangeEventArgs.Item.Id,
|
||||
(itemChangeEventArgs.Item, itemChangeEventArgs.UpdateReason),
|
||||
(key, existingValue) => existingValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnProviderManagerRefreshCompleted(object sender, GenericEventArgs<BaseItem> e)
|
||||
{
|
||||
if (!_queuedItems.ContainsKey(e.Argument.Id) && e.Argument.ImageInfos.Length > 0)
|
||||
{
|
||||
_queuedItems.AddOrUpdate(
|
||||
e.Argument.Id,
|
||||
(e.Argument, ItemUpdateType.None),
|
||||
(key, existingValue) => existingValue);
|
||||
}
|
||||
|
||||
// The RefreshCompleted event is a bit awkward in that it seems to _only_ be fired on
|
||||
// the item that was refreshed regardless of children refreshes. So we take it as a signal
|
||||
// that the refresh is entirely completed.
|
||||
Run(null, CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -858,7 +858,21 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{Person}.</returns>
|
||||
public Person GetPerson(string name)
|
||||
{
|
||||
return CreateItemByName<Person>(Person.GetPath, name, new DtoOptions(true));
|
||||
var path = Person.GetPath(name);
|
||||
var id = GetItemByNameId<Person>(path);
|
||||
if (!(GetItemById(id) is Person item))
|
||||
{
|
||||
item = new Person
|
||||
{
|
||||
Name = name,
|
||||
Id = id,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
DateModified = DateTime.UtcNow,
|
||||
Path = path
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1941,19 +1955,9 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
public Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
ProviderManager.SaveMetadata(item, updateReason);
|
||||
}
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
|
||||
await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
|
||||
}
|
||||
RunMetadataSavers(items, updateReason);
|
||||
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
|
||||
@@ -1984,12 +1988,27 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
=> UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
|
||||
|
||||
public void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
ProviderManager.SaveMetadata(item, updateReason);
|
||||
}
|
||||
|
||||
item.DateLastSaved = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reports the item removed.
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,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)
|
||||
@@ -103,6 +103,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();
|
||||
|
||||
@@ -133,12 +137,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;
|
||||
@@ -159,33 +163,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)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"Albums": "Albums",
|
||||
"Artists": "Các Nghệ Sĩ",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
|
||||
"TaskDownloadMissingSubtitles": "Tải xuống phụ đề bị thiếu",
|
||||
"TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",
|
||||
"TaskRefreshChannelsDescription": "Làm mới thông tin kênh internet.",
|
||||
"TaskRefreshChannels": "Làm Mới Kênh",
|
||||
"TaskCleanTranscodeDescription": "Xóa các tệp chuyển mã cũ hơn một ngày.",
|
||||
@@ -24,11 +24,11 @@
|
||||
"TaskUpdatePluginsDescription": "Tải xuống và cài đặt các bản cập nhật cho các plugin được định cấu hình để cập nhật tự động.",
|
||||
"TaskUpdatePlugins": "Cập Nhật Plugins",
|
||||
"TaskRefreshPeopleDescription": "Cập nhật thông tin chi tiết cho diễn viên và đạo diễn trong thư viện phương tiện của bạn.",
|
||||
"TaskRefreshPeople": "Làm mới Người dùng",
|
||||
"TaskRefreshPeople": "Làm Mới Người Dùng",
|
||||
"TaskCleanLogsDescription": "Xóa tập tin nhật ký cũ hơn {0} ngày.",
|
||||
"TaskCleanLogs": "Làm sạch nhật ký",
|
||||
"TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm các tệp mới và làm mới thông tin chi tiết.",
|
||||
"TaskRefreshLibrary": "Quét Thư viện Phương tiện",
|
||||
"TaskCleanLogs": "Làm Sạch Thư Mục Nhật Ký",
|
||||
"TaskRefreshLibraryDescription": "Quét thư viện phương tiện của bạn để tìm tệp mới và làm mới dữ liệu mô tả.",
|
||||
"TaskRefreshLibrary": "Quét Thư Viện Phương Tiện",
|
||||
"TaskRefreshChapterImagesDescription": "Tạo hình thu nhỏ cho video có các phân cảnh.",
|
||||
"TaskRefreshChapterImages": "Trích Xuất Ảnh Phân Cảnh",
|
||||
"TaskCleanCacheDescription": "Xóa các tệp cache không còn cần thiết của hệ thống.",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user