mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-31 21:08:27 +01:00
Merge branch 'master' into sort-nfo-data
This commit is contained in:
@@ -68,7 +68,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
var semaphoreCount = config.Configuration.ParallelImageEncodingLimit;
|
||||
if (semaphoreCount < 1)
|
||||
{
|
||||
semaphoreCount = 2 * Environment.ProcessorCount;
|
||||
semaphoreCount = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
_parallelEncodingLimit = new(semaphoreCount);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
namespace Jellyfin.Extensions.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert comma delimited string to array of type.
|
||||
/// Convert comma delimited string to collection of type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert to.</typeparam>
|
||||
public sealed class JsonCommaDelimitedArrayConverter<T> : JsonDelimitedArrayConverter<T>
|
||||
public sealed class JsonCommaDelimitedCollectionConverter<T> : JsonDelimitedCollectionConverter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonCommaDelimitedArrayConverter{T}"/> class.
|
||||
/// Initializes a new instance of the <see cref="JsonCommaDelimitedCollectionConverter{T}"/> class.
|
||||
/// </summary>
|
||||
public JsonCommaDelimitedArrayConverter() : base()
|
||||
public JsonCommaDelimitedCollectionConverter() : base()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Extensions.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Json comma delimited array converter factory.
|
||||
/// Json comma delimited collection converter factory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
|
||||
/// </remarks>
|
||||
public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory
|
||||
public class JsonCommaDelimitedCollectionConverterFactory : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return true;
|
||||
return typeToConvert.IsArray
|
||||
|| (typeToConvert.IsGenericType
|
||||
&& (typeToConvert.GetGenericTypeDefinition().IsAssignableFrom(typeof(IReadOnlyCollection<>)) || typeToConvert.GetGenericTypeDefinition().IsAssignableFrom(typeof(IReadOnlyList<>))));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0];
|
||||
return (JsonConverter?)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType));
|
||||
return (JsonConverter?)Activator.CreateInstance(typeof(JsonCommaDelimitedCollectionConverter<>).MakeGenericType(structType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ namespace Jellyfin.Extensions.Json.Converters
|
||||
/// Convert delimited string to array of type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert to.</typeparam>
|
||||
public abstract class JsonDelimitedArrayConverter<T> : JsonConverter<T[]>
|
||||
public abstract class JsonDelimitedCollectionConverter<T> : JsonConverter<IReadOnlyCollection<T>>
|
||||
{
|
||||
private readonly TypeConverter _typeConverter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonDelimitedArrayConverter{T}"/> class.
|
||||
/// Initializes a new instance of the <see cref="JsonDelimitedCollectionConverter{T}"/> class.
|
||||
/// </summary>
|
||||
protected JsonDelimitedArrayConverter()
|
||||
protected JsonDelimitedCollectionConverter()
|
||||
{
|
||||
_typeConverter = TypeDescriptor.GetConverter(typeof(T));
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace Jellyfin.Extensions.Json.Converters
|
||||
protected virtual char Delimiter { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
public override IReadOnlyCollection<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
@@ -56,35 +56,21 @@ namespace Jellyfin.Extensions.Json.Converters
|
||||
}
|
||||
}
|
||||
|
||||
return typedValues.ToArray();
|
||||
if (typeToConvert.IsArray)
|
||||
{
|
||||
return typedValues.ToArray();
|
||||
}
|
||||
|
||||
return typedValues;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<T[]>(ref reader, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options)
|
||||
public override void Write(Utf8JsonWriter writer, IReadOnlyCollection<T>? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is not null)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
if (value.Length > 0)
|
||||
{
|
||||
foreach (var it in value)
|
||||
{
|
||||
if (it is not null)
|
||||
{
|
||||
writer.WriteStringValue(it.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
}
|
||||
JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ namespace Jellyfin.Extensions.Json.Converters
|
||||
/// Convert Pipe delimited string to array of type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert to.</typeparam>
|
||||
public sealed class JsonPipeDelimitedArrayConverter<T> : JsonDelimitedArrayConverter<T>
|
||||
public sealed class JsonPipeDelimitedCollectionConverter<T> : JsonDelimitedCollectionConverter<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPipeDelimitedArrayConverter{T}"/> class.
|
||||
/// Initializes a new instance of the <see cref="JsonPipeDelimitedCollectionConverter{T}"/> class.
|
||||
/// </summary>
|
||||
public JsonPipeDelimitedArrayConverter() : base()
|
||||
public JsonPipeDelimitedCollectionConverter() : base()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Jellyfin.Extensions.Json.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Json Pipe delimited array converter factory.
|
||||
/// Json Pipe delimited collection converter factory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
|
||||
/// </remarks>
|
||||
public class JsonPipeDelimitedArrayConverterFactory : JsonConverterFactory
|
||||
public class JsonPipeDelimitedCollectionConverterFactory : JsonConverterFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return true;
|
||||
return typeToConvert.IsArray
|
||||
|| (typeToConvert.IsGenericType
|
||||
&& (typeToConvert.GetGenericTypeDefinition().IsAssignableFrom(typeof(IReadOnlyCollection<>)) || typeToConvert.GetGenericTypeDefinition().IsAssignableFrom(typeof(IReadOnlyList<>))));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0];
|
||||
return (JsonConverter?)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType));
|
||||
return (JsonConverter?)Activator.CreateInstance(typeof(JsonPipeDelimitedCollectionConverter<>).MakeGenericType(structType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities.Libraries;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.LiveTv.Configuration;
|
||||
@@ -210,7 +209,7 @@ public class GuideManager : IGuideManager
|
||||
progress.Report(15);
|
||||
|
||||
numComplete = 0;
|
||||
var programs = new List<LiveTvProgram>();
|
||||
var programIds = new List<Guid>();
|
||||
var channels = new List<Guid>();
|
||||
|
||||
var guideDays = GetGuideDays();
|
||||
@@ -243,8 +242,8 @@ public class GuideManager : IGuideManager
|
||||
DtoOptions = new DtoOptions(true)
|
||||
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||
|
||||
var newPrograms = new List<Guid>();
|
||||
var updatedPrograms = new List<Guid>();
|
||||
var newPrograms = new List<LiveTvProgram>();
|
||||
var updatedPrograms = new List<LiveTvProgram>();
|
||||
|
||||
foreach (var program in channelPrograms)
|
||||
{
|
||||
@@ -252,14 +251,14 @@ public class GuideManager : IGuideManager
|
||||
var id = programItem.Id;
|
||||
if (isNew)
|
||||
{
|
||||
newPrograms.Add(id);
|
||||
newPrograms.Add(programItem);
|
||||
}
|
||||
else if (isUpdated)
|
||||
{
|
||||
updatedPrograms.Add(id);
|
||||
updatedPrograms.Add(programItem);
|
||||
}
|
||||
|
||||
programs.Add(programItem);
|
||||
programIds.Add(programItem.Id);
|
||||
|
||||
isMovie |= program.IsMovie;
|
||||
isSeries |= program.IsSeries;
|
||||
@@ -276,21 +275,21 @@ public class GuideManager : IGuideManager
|
||||
|
||||
if (newPrograms.Count > 0)
|
||||
{
|
||||
var newProgramDtos = programs.Where(b => newPrograms.Contains(b.Id)).ToList();
|
||||
_libraryManager.CreateOrUpdateItems(newProgramDtos, null, cancellationToken);
|
||||
_libraryManager.CreateItems(newPrograms, currentChannel, cancellationToken);
|
||||
|
||||
await PreCacheImages(newPrograms, maxCacheDate).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (updatedPrograms.Count > 0)
|
||||
{
|
||||
var updatedProgramDtos = programs.Where(b => updatedPrograms.Contains(b.Id)).ToList();
|
||||
await _libraryManager.UpdateItemsAsync(
|
||||
updatedProgramDtos,
|
||||
updatedPrograms,
|
||||
currentChannel,
|
||||
ItemUpdateType.MetadataImport,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await PreCacheImages(programs, maxCacheDate).ConfigureAwait(false);
|
||||
await PreCacheImages(updatedPrograms, maxCacheDate).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
currentChannel.IsMovie = isMovie;
|
||||
currentChannel.IsNews = isNews;
|
||||
@@ -326,7 +325,6 @@ public class GuideManager : IGuideManager
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
var programIds = programs.Select(p => p.Id).ToList();
|
||||
return new Tuple<List<Guid>, List<Guid>>(channels, programIds);
|
||||
}
|
||||
|
||||
@@ -502,35 +500,27 @@ public class GuideManager : IGuideManager
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
var seriesId = info.SeriesId;
|
||||
|
||||
if (!item.ParentId.Equals(channel.Id))
|
||||
var channelId = channel.Id;
|
||||
if (!item.ParentId.Equals(channelId))
|
||||
{
|
||||
item.ParentId = channel.Id;
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.ParentId = channel.Id;
|
||||
|
||||
item.Audio = info.Audio;
|
||||
item.ChannelId = channel.Id;
|
||||
item.CommunityRating ??= info.CommunityRating;
|
||||
if ((item.CommunityRating ?? 0).Equals(0))
|
||||
{
|
||||
item.CommunityRating = null;
|
||||
}
|
||||
|
||||
item.ChannelId = channelId;
|
||||
item.CommunityRating = info.CommunityRating;
|
||||
item.EpisodeTitle = info.EpisodeTitle;
|
||||
item.ExternalId = info.Id;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal))
|
||||
var seriesId = info.SeriesId;
|
||||
if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.ExternalSeriesId = seriesId;
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.ExternalSeriesId = seriesId;
|
||||
|
||||
var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
|
||||
|
||||
if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle))
|
||||
{
|
||||
item.SeriesName = info.Name;
|
||||
@@ -578,7 +568,6 @@ public class GuideManager : IGuideManager
|
||||
}
|
||||
|
||||
item.Tags = tags.ToArray();
|
||||
|
||||
item.Genres = info.Genres.ToArray();
|
||||
|
||||
if (info.IsHD ?? false)
|
||||
@@ -589,41 +578,35 @@ public class GuideManager : IGuideManager
|
||||
|
||||
item.IsMovie = info.IsMovie;
|
||||
item.IsRepeat = info.IsRepeat;
|
||||
|
||||
if (item.IsSeries != isSeries)
|
||||
{
|
||||
item.IsSeries = isSeries;
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.IsSeries = isSeries;
|
||||
|
||||
item.Name = info.Name;
|
||||
item.OfficialRating ??= info.OfficialRating;
|
||||
item.Overview ??= info.Overview;
|
||||
item.OfficialRating = info.OfficialRating;
|
||||
item.Overview = info.Overview;
|
||||
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
|
||||
item.ProviderIds = info.ProviderIds;
|
||||
|
||||
foreach (var providerId in info.SeriesProviderIds)
|
||||
{
|
||||
info.ProviderIds["Series" + providerId.Key] = providerId.Value;
|
||||
}
|
||||
|
||||
item.ProviderIds = info.ProviderIds;
|
||||
if (item.StartDate != info.StartDate)
|
||||
{
|
||||
item.StartDate = info.StartDate;
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.StartDate = info.StartDate;
|
||||
|
||||
if (item.EndDate != info.EndDate)
|
||||
{
|
||||
item.EndDate = info.EndDate;
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
item.EndDate = info.EndDate;
|
||||
|
||||
item.ProductionYear = info.ProductionYear;
|
||||
|
||||
if (!isSeries || info.IsRepeat)
|
||||
{
|
||||
item.PremiereDate = info.OriginalAirDate;
|
||||
@@ -632,37 +615,35 @@ public class GuideManager : IGuideManager
|
||||
item.IndexNumber = info.EpisodeNumber;
|
||||
item.ParentIndexNumber = info.SeasonNumber;
|
||||
|
||||
forceUpdate = forceUpdate || UpdateImages(item, info);
|
||||
forceUpdate |= UpdateImages(item, info);
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
item.OnMetadataChanged();
|
||||
|
||||
return (item, isNew, false);
|
||||
return (item, true, false);
|
||||
}
|
||||
|
||||
var isUpdated = false;
|
||||
if (forceUpdate || string.IsNullOrWhiteSpace(info.Etag))
|
||||
var isUpdated = forceUpdate;
|
||||
var etag = info.Etag;
|
||||
if (string.IsNullOrWhiteSpace(etag))
|
||||
{
|
||||
isUpdated = true;
|
||||
}
|
||||
else
|
||||
else if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var etag = info.Etag;
|
||||
|
||||
if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.SetProviderId(EtagKey, etag);
|
||||
isUpdated = true;
|
||||
}
|
||||
item.SetProviderId(EtagKey, etag);
|
||||
isUpdated = true;
|
||||
}
|
||||
|
||||
if (isUpdated)
|
||||
{
|
||||
item.OnMetadataChanged();
|
||||
|
||||
return (item, false, true);
|
||||
}
|
||||
|
||||
return (item, isNew, isUpdated);
|
||||
return (item, false, false);
|
||||
}
|
||||
|
||||
private static bool UpdateImages(BaseItem item, ProgramInfo info)
|
||||
@@ -679,7 +660,9 @@ public class GuideManager : IGuideManager
|
||||
updated |= UpdateImage(ImageType.Logo, item, info);
|
||||
|
||||
// Backdrop
|
||||
return updated || UpdateImage(ImageType.Backdrop, item, info);
|
||||
updated |= UpdateImage(ImageType.Backdrop, item, info);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private static bool UpdateImage(ImageType imageType, BaseItem item, ProgramInfo info)
|
||||
@@ -689,7 +672,7 @@ public class GuideManager : IGuideManager
|
||||
var newImagePath = imageType switch
|
||||
{
|
||||
ImageType.Primary => info.ImagePath,
|
||||
_ => string.Empty
|
||||
_ => null
|
||||
};
|
||||
var newImageUrl = imageType switch
|
||||
{
|
||||
@@ -697,12 +680,12 @@ public class GuideManager : IGuideManager
|
||||
ImageType.Logo => info.LogoImageUrl,
|
||||
ImageType.Primary => info.ImageUrl,
|
||||
ImageType.Thumb => info.ThumbImageUrl,
|
||||
_ => string.Empty
|
||||
_ => null
|
||||
};
|
||||
|
||||
var differentImage = newImageUrl?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false
|
||||
|| newImagePath?.Equals(currentImagePath, StringComparison.OrdinalIgnoreCase) == false;
|
||||
if (!differentImage)
|
||||
var sameImage = (currentImagePath?.Equals(newImageUrl, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
|| (currentImagePath?.Equals(newImagePath, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
if (sameImage)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -757,6 +740,7 @@ public class GuideManager : IGuideManager
|
||||
var imageInfo = program.ImageInfos[i];
|
||||
if (!imageInfo.IsLocalFile)
|
||||
{
|
||||
_logger.LogDebug("Caching image locally: {Url}", imageInfo.Path);
|
||||
try
|
||||
{
|
||||
program.ImageInfos[i] = await _libraryManager.ConvertImageToLocal(
|
||||
|
||||
@@ -49,11 +49,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
/// </summary>
|
||||
private bool _eventfire;
|
||||
|
||||
/// <summary>
|
||||
/// List of all interface MAC addresses.
|
||||
/// </summary>
|
||||
private IReadOnlyList<PhysicalAddress> _macAddresses;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary containing interface addresses and their subnets.
|
||||
/// </summary>
|
||||
@@ -91,7 +86,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
_startupConfig = startupConfig;
|
||||
_initLock = new();
|
||||
_interfaces = new List<IPData>();
|
||||
_macAddresses = new List<PhysicalAddress>();
|
||||
_publishedServerUrls = new List<PublishedServerUriOverride>();
|
||||
_networkEventLock = new();
|
||||
_remoteAddressFilter = new List<IPNetwork>();
|
||||
@@ -215,7 +209,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
|
||||
/// Generate a list of all active mac addresses that aren't loopback addresses.
|
||||
/// </summary>
|
||||
private void InitializeInterfaces()
|
||||
{
|
||||
@@ -224,7 +217,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
_logger.LogDebug("Refreshing interfaces.");
|
||||
|
||||
var interfaces = new List<IPData>();
|
||||
var macAddresses = new List<PhysicalAddress>();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -236,13 +228,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
try
|
||||
{
|
||||
var ipProperties = adapter.GetIPProperties();
|
||||
var mac = adapter.GetPhysicalAddress();
|
||||
|
||||
// Populate MAC list
|
||||
if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && !PhysicalAddress.None.Equals(mac))
|
||||
{
|
||||
macAddresses.Add(mac);
|
||||
}
|
||||
|
||||
// Populate interface list
|
||||
foreach (var info in ipProperties.UnicastAddresses)
|
||||
@@ -302,7 +287,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
_logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
|
||||
_logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
|
||||
|
||||
_macAddresses = macAddresses;
|
||||
_interfaces = interfaces;
|
||||
}
|
||||
}
|
||||
@@ -711,13 +695,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<PhysicalAddress> GetMacAddresses()
|
||||
{
|
||||
// Populated in construction - so always has values.
|
||||
return _macAddresses;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyList<IPData> GetLoopbacks()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user