فهرست منبع

Merge remote-tracking branch 'upstream/master'

Petr Janda 5 سال پیش
والد
کامیت
7cde256402

+ 1 - 0
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -37,6 +37,7 @@
     <PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
     <PackageReference Include="sharpcompress" Version="0.24.0" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
+    <PackageReference Include="System.Interactive.Async" Version="4.0.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 96 - 1
Emby.Server.Implementations/Localization/Core/af.json

@@ -1 +1,96 @@
-{}
+{
+    "Artists": "Kunstenare",
+    "Channels": "Kanale",
+    "Folders": "Fouers",
+    "Favorites": "Gunstelinge",
+    "HeaderFavoriteShows": "Gunsteling Vertonings",
+    "ValueSpecialEpisodeName": "Spesiaal - {0}",
+    "HeaderAlbumArtists": "Album Kunstenaars",
+    "Books": "Boeke",
+    "HeaderNextUp": "Volgende",
+    "Movies": "Rolprente",
+    "Shows": "Program",
+    "HeaderContinueWatching": "Hou Aan Kyk",
+    "HeaderFavoriteEpisodes": "Gunsteling Episodes",
+    "Photos": "Fotos",
+    "Playlists": "Speellysse",
+    "HeaderFavoriteArtists": "Gunsteling Kunstenaars",
+    "HeaderFavoriteAlbums": "Gunsteling Albums",
+    "Sync": "Sinkroniseer",
+    "HeaderFavoriteSongs": "Gunsteling Liedjies",
+    "Songs": "Liedjies",
+    "DeviceOnlineWithName": "{0} is verbind",
+    "DeviceOfflineWithName": "{0} het afgesluit",
+    "Collections": "Versamelings",
+    "Inherit": "Ontvang",
+    "HeaderLiveTV": "Live TV",
+    "Application": "Program",
+    "AppDeviceValues": "App: {0}, Toestel: {1}",
+    "VersionNumber": "Weergawe {0}",
+    "ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg",
+    "UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel",
+    "UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel",
+    "UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}",
+    "UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander",
+    "UserOnlineFromDevice": "{0} is aanlyn van {1}",
+    "UserOfflineFromDevice": "{0} is ontkoppel van {1}",
+    "UserLockedOutWithName": "Gebruiker {0} is uitgesluit",
+    "UserDownloadingItemWithValues": "{0} is besig om {1} af te laai",
+    "UserDeletedWithName": "Gebruiker {0} is verwyder",
+    "UserCreatedWithName": "Gebruiker {0} is geskep",
+    "User": "Gebruiker",
+    "TvShows": "TV Programme",
+    "System": "Stelsel",
+    "SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}",
+    "SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}",
+    "StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.",
+    "ServerNameNeedsToBeRestarted": "{0} moet herbegin word",
+    "ScheduledTaskStartedWithName": "{0} het begin",
+    "ScheduledTaskFailedWithName": "{0} het misluk",
+    "ProviderValue": "Voorsiener: {0}",
+    "PluginUpdatedWithName": "{0} was opgedateer",
+    "PluginUninstalledWithName": "{0} was verwyder",
+    "PluginInstalledWithName": "{0} is geïnstalleer",
+    "Plugin": "Inprop module",
+    "NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop",
+    "NotificationOptionVideoPlayback": "Video terugspeel het begin",
+    "NotificationOptionUserLockedOut": "Gebruiker uitgeslyt",
+    "NotificationOptionTaskFailed": "Geskeduleerde taak het misluk",
+    "NotificationOptionServerRestartRequired": "Bediener herbegin nodig",
+    "NotificationOptionPluginUpdateInstalled": "Nuwe inprop module geïnstalleer",
+    "NotificationOptionPluginUninstalled": "Inprop module verwyder",
+    "NotificationOptionPluginInstalled": "Inprop module geïnstalleer",
+    "NotificationOptionPluginError": "Inprop module het misluk",
+    "NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg",
+    "NotificationOptionInstallationFailed": "Installering het misluk",
+    "NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai",
+    "NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop",
+    "NotificationOptionAudioPlayback": "Oudio terugspeel het begin",
+    "NotificationOptionApplicationUpdateInstalled": "Nuwe program weergawe geïnstalleer",
+    "NotificationOptionApplicationUpdateAvailable": "Nuwe program weergawe beskikbaar",
+    "NewVersionIsAvailable": "'n Nuwe Jellyfin Bedienaar weergawe kan afgelaai word.",
+    "NameSeasonUnknown": "Seisoen Onbekend",
+    "NameSeasonNumber": "Seisoen {0}",
+    "NameInstallFailed": "{0} installering het misluk",
+    "MusicVideos": "Musiek videos",
+    "Music": "Musiek",
+    "MixedContent": "Gemengde inhoud",
+    "MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
+    "MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer",
+    "MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
+    "MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
+    "Latest": "Nuutste",
+    "LabelRunningTimeValue": "Lopende tyd: {0}",
+    "LabelIpAddressValue": "IP adres: {0}",
+    "ItemRemovedWithName": "{0} is uit versameling verwyder",
+    "ItemAddedWithName": "{0} is in die versameling",
+    "HomeVideos": "Tuis opnames",
+    "HeaderRecordingGroups": "Groep Opnames",
+    "HeaderCameraUploads": "Kamera Oplaai",
+    "Genres": "Genres",
+    "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
+    "ChapterNameValue": "Hoofstuk",
+    "CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
+    "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
+    "Albums": "Albums"
+}

+ 2 - 2
Emby.Server.Implementations/Localization/Core/de.json

@@ -3,14 +3,14 @@
     "AppDeviceValues": "App: {0}, Gerät: {1}",
     "Application": "Anwendung",
     "Artists": "Interpreten",
-    "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet",
+    "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
     "Books": "Bücher",
     "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
     "Channels": "Kanäle",
     "ChapterNameValue": "Kapitel {0}",
     "Collections": "Sammlungen",
     "DeviceOfflineWithName": "{0} wurde getrennt",
-    "DeviceOnlineWithName": "{0} hat sich verbunden",
+    "DeviceOnlineWithName": "{0} ist verbunden",
     "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
     "Favorites": "Favoriten",
     "Folders": "Verzeichnisse",

+ 3 - 3
Emby.Server.Implementations/Localization/Core/en-US.json

@@ -8,9 +8,9 @@
     "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
     "Channels": "Channels",
     "ChapterNameValue": "Chapter {0}",
-    "Collections": "Versamelings",
-    "DeviceOfflineWithName": "{0} het afgesluit",
-    "DeviceOnlineWithName": "{0} is verbind",
+    "Collections": "Collections",
+    "DeviceOfflineWithName": "{0} has disconnected",
+    "DeviceOnlineWithName": "{0} is connected",
     "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
     "Favorites": "Favorites",
     "Folders": "Folders",

+ 30 - 30
Emby.Server.Implementations/Localization/Core/he.json

@@ -1,41 +1,41 @@
 {
     "Albums": "אלבומים",
-    "AppDeviceValues": "App: {0}, Device: {1}",
+    "AppDeviceValues": "יישום: {0}, מכשיר: {1}",
     "Application": "אפליקציה",
     "Artists": "אמנים",
-    "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
+    "AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
     "Books": "ספרים",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
-    "Channels": "Channels",
-    "ChapterNameValue": "Chapter {0}",
-    "Collections": "Collections",
-    "DeviceOfflineWithName": "{0} has disconnected",
-    "DeviceOnlineWithName": "{0} is connected",
-    "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "Favorites": "Favorites",
-    "Folders": "Folders",
+    "CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
+    "Channels": "ערוצים",
+    "ChapterNameValue": "פרק {0}",
+    "Collections": "קולקציות",
+    "DeviceOfflineWithName": "{0} התנתק",
+    "DeviceOnlineWithName": "{0} מחובר",
+    "FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
+    "Favorites": "אהובים",
+    "Folders": "תיקיות",
     "Genres": "ז'אנרים",
-    "HeaderAlbumArtists": "Album Artists",
-    "HeaderCameraUploads": "Camera Uploads",
-    "HeaderContinueWatching": "המשך בצפייה",
-    "HeaderFavoriteAlbums": "Favorite Albums",
-    "HeaderFavoriteArtists": "Favorite Artists",
-    "HeaderFavoriteEpisodes": "Favorite Episodes",
-    "HeaderFavoriteShows": "Favorite Shows",
-    "HeaderFavoriteSongs": "Favorite Songs",
-    "HeaderLiveTV": "Live TV",
-    "HeaderNextUp": "Next Up",
+    "HeaderAlbumArtists": "אמני האלבום",
+    "HeaderCameraUploads": "העלאות ממצלמה",
+    "HeaderContinueWatching": "המשך לצפות",
+    "HeaderFavoriteAlbums": "אלבומים שאהבתי",
+    "HeaderFavoriteArtists": "אמנים שאהבתי",
+    "HeaderFavoriteEpisodes": "פרקים אהובים",
+    "HeaderFavoriteShows": "תוכניות אהובות",
+    "HeaderFavoriteSongs": "שירים שאהבתי",
+    "HeaderLiveTV": "טלוויזיה בשידור חי",
+    "HeaderNextUp": "הבא",
     "HeaderRecordingGroups": "קבוצות הקלטה",
-    "HomeVideos": "Home videos",
-    "Inherit": "Inherit",
+    "HomeVideos": "סרטונים בייתים",
+    "Inherit": "הורש",
     "ItemAddedWithName": "{0} was added to the library",
-    "ItemRemovedWithName": "{0} was removed from the library",
-    "LabelIpAddressValue": "Ip address: {0}",
-    "LabelRunningTimeValue": "Running time: {0}",
+    "ItemRemovedWithName": "{0} נמחק מהספרייה",
+    "LabelIpAddressValue": "Ip כתובת: {0}",
+    "LabelRunningTimeValue": "משך צפייה: {0}",
     "Latest": "אחרון",
-    "MessageApplicationUpdated": "Jellyfin Server has been updated",
-    "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
+    "MessageApplicationUpdated": "שרת הJellyfin עודכן",
+    "MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
+    "MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
     "MessageServerConfigurationUpdated": "Server configuration has been updated",
     "MixedContent": "תוכן מעורב",
     "Movies": "סרטים",
@@ -50,7 +50,7 @@
     "NotificationOptionAudioPlayback": "Audio playback started",
     "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
     "NotificationOptionCameraImageUploaded": "Camera image uploaded",
-    "NotificationOptionInstallationFailed": "Installation failure",
+    "NotificationOptionInstallationFailed": "התקנה נכשלה",
     "NotificationOptionNewLibraryContent": "New content added",
     "NotificationOptionPluginError": "Plugin failure",
     "NotificationOptionPluginInstalled": "Plugin installed",

+ 3 - 3
Emby.Server.Implementations/Localization/Core/sk.json

@@ -5,7 +5,7 @@
     "Artists": "Umelci",
     "AuthenticationSucceededWithUserName": "{0} úspešne overený",
     "Books": "Knihy",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+    "CameraImageUploadedFrom": "Z {0} bola nahraná nová fotografia",
     "Channels": "Kanály",
     "ChapterNameValue": "Kapitola {0}",
     "Collections": "Zbierky",
@@ -15,9 +15,9 @@
     "Favorites": "Obľúbené",
     "Folders": "Priečinky",
     "Genres": "Žánre",
-    "HeaderAlbumArtists": "Album Artists",
+    "HeaderAlbumArtists": "Albumy umelcov",
     "HeaderCameraUploads": "Nahrané fotografie",
-    "HeaderContinueWatching": "Pokračujte v pozeraní",
+    "HeaderContinueWatching": "Pokračovať v pozeraní",
     "HeaderFavoriteAlbums": "Obľúbené albumy",
     "HeaderFavoriteArtists": "Obľúbení umelci",
     "HeaderFavoriteEpisodes": "Obľúbené epizódy",

+ 8 - 32
Emby.Server.Implementations/Networking/NetworkManager.cs

@@ -7,8 +7,6 @@ using System.Net.NetworkInformation;
 using System.Net.Sockets;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.Networking
@@ -55,10 +53,7 @@ namespace Emby.Server.Implementations.Networking
                 _macAddresses = null;
             }
 
-            if (NetworkChanged != null)
-            {
-                NetworkChanged(this, EventArgs.Empty);
-            }
+            NetworkChanged?.Invoke(this, EventArgs.Empty);
         }
 
         public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
@@ -261,10 +256,10 @@ namespace Emby.Server.Implementations.Networking
                     return true;
                 }
 
-                if (normalizedSubnet.IndexOf('/') != -1)
+                if (normalizedSubnet.Contains('/', StringComparison.Ordinal))
                 {
-                    var ipnetwork = IPNetwork.Parse(normalizedSubnet);
-                    if (ipnetwork.Contains(address))
+                    var ipNetwork = IPNetwork.Parse(normalizedSubnet);
+                    if (ipNetwork.Contains(address))
                     {
                         return true;
                     }
@@ -455,9 +450,9 @@ namespace Emby.Server.Implementations.Networking
 
         public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask)
         {
-             IPAddress network1 = GetNetworkAddress(address1, subnetMask);
-             IPAddress network2 = GetNetworkAddress(address2, subnetMask);
-             return network1.Equals(network2);
+            IPAddress network1 = GetNetworkAddress(address1, subnetMask);
+            IPAddress network2 = GetNetworkAddress(address2, subnetMask);
+            return network1.Equals(network2);
         }
 
         private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
@@ -473,7 +468,7 @@ namespace Emby.Server.Implementations.Networking
             byte[] broadcastAddress = new byte[ipAdressBytes.Length];
             for (int i = 0; i < broadcastAddress.Length; i++)
             {
-                broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
+                broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]);
             }
 
             return new IPAddress(broadcastAddress);
@@ -513,24 +508,5 @@ namespace Emby.Server.Implementations.Networking
 
             return null;
         }
-
-        /// <summary>
-        /// Gets the network shares.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>IEnumerable{NetworkShare}.</returns>
-        public virtual IEnumerable<NetworkShare> GetNetworkShares(string path)
-        {
-            return new List<NetworkShare>();
-        }
-
-        /// <summary>
-        /// Gets available devices within the domain
-        /// </summary>
-        /// <returns>PC's in the Domain</returns>
-        public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices()
-        {
-            return new List<FileSystemEntryInfo>();
-        }
     }
 }

+ 3 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs

@@ -52,7 +52,9 @@ namespace Emby.Server.Implementations.ScheduledTasks
         {
             progress.Report(0);
 
-            var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(cancellationToken).ConfigureAwait(false)).ToList();
+            var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
+                .ToListAsync(cancellationToken)
+                .ConfigureAwait(false);
 
             progress.Report(10);
 

+ 25 - 20
Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Net;
+using System.Net.Mime;
 using MediaBrowser.Common.Net;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http.Extensions;
@@ -14,9 +15,9 @@ namespace Emby.Server.Implementations.SocketSharp
 {
     public class WebSocketSharpRequest : IHttpRequest
     {
-        public const string FormUrlEncoded = "application/x-www-form-urlencoded";
-        public const string MultiPartFormData = "multipart/form-data";
-        public const string Soap11 = "text/xml; charset=utf-8";
+        private const string FormUrlEncoded = "application/x-www-form-urlencoded";
+        private const string MultiPartFormData = "multipart/form-data";
+        private const string Soap11 = "text/xml; charset=utf-8";
 
         private string _remoteIp;
         private Dictionary<string, object> _items;
@@ -77,7 +78,7 @@ namespace Emby.Server.Implementations.SocketSharp
             get =>
                 _responseContentType
                 ?? (_responseContentType = GetResponseContentType(Request));
-            set => this._responseContentType = value;
+            set => _responseContentType = value;
         }
 
         public string PathInfo => Request.Path.Value;
@@ -90,7 +91,6 @@ namespace Emby.Server.Implementations.SocketSharp
 
         public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress);
 
-
         public string HttpMethod => Request.Method;
 
         public string Verb => HttpMethod;
@@ -123,24 +123,29 @@ namespace Emby.Server.Implementations.SocketSharp
                 return specifiedContentType;
             }
 
-            const string serverDefaultContentType = "application/json";
+            const string ServerDefaultContentType = MediaTypeNames.Application.Json;
 
             var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
             string defaultContentType = null;
             if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData))
             {
-                defaultContentType = serverDefaultContentType;
+                defaultContentType = ServerDefaultContentType;
             }
 
             var acceptsAnything = false;
             var hasDefaultContentType = defaultContentType != null;
             if (acceptContentTypes != null)
             {
-                foreach (var acceptsType in acceptContentTypes)
+                foreach (ReadOnlySpan<char> acceptsType in acceptContentTypes)
                 {
-                    // TODO: @bond move to Span when Span.Split lands
-                    // https://github.com/dotnet/corefx/issues/26528
-                    var contentType = acceptsType?.Split(';')[0].Trim();
+                    ReadOnlySpan<char> contentType = acceptsType;
+                    var index = contentType.IndexOf(';');
+                    if (index != -1)
+                    {
+                        contentType = contentType.Slice(0, index);
+                    }
+
+                    contentType = contentType.Trim();
                     acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
 
                     if (acceptsAnything)
@@ -157,7 +162,7 @@ namespace Emby.Server.Implementations.SocketSharp
                     }
                     else
                     {
-                        return serverDefaultContentType;
+                        return ServerDefaultContentType;
                     }
                 }
             }
@@ -168,7 +173,7 @@ namespace Emby.Server.Implementations.SocketSharp
             }
 
             // We could also send a '406 Not Acceptable', but this is allowed also
-            return serverDefaultContentType;
+            return ServerDefaultContentType;
         }
 
         public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
@@ -196,12 +201,12 @@ namespace Emby.Server.Implementations.SocketSharp
 
         private static string GetQueryStringContentType(HttpRequest httpReq)
         {
-            ReadOnlySpan<char> format = httpReq.Query["format"].ToString().AsSpan();
+            ReadOnlySpan<char> format = httpReq.Query["format"].ToString();
             if (format == null)
             {
-                const int formatMaxLength = 4;
-                ReadOnlySpan<char> pi = httpReq.Path.ToString().AsSpan();
-                if (pi == null || pi.Length <= formatMaxLength)
+                const int FormatMaxLength = 4;
+                ReadOnlySpan<char> pi = httpReq.Path.ToString();
+                if (pi == null || pi.Length <= FormatMaxLength)
                 {
                     return null;
                 }
@@ -212,18 +217,18 @@ namespace Emby.Server.Implementations.SocketSharp
                 }
 
                 format = LeftPart(pi, '/');
-                if (format.Length > formatMaxLength)
+                if (format.Length > FormatMaxLength)
                 {
                     return null;
                 }
             }
 
             format = LeftPart(format, '.');
-            if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase))
+            if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
             {
                 return "application/json";
             }
-            else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase))
+            else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase))
             {
                 return "application/xml";
             }

+ 11 - 7
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Updates
             // Package not found.
             if (package == null)
             {
-                return null;
+                return Enumerable.Empty<PackageVersionInfo>();
             }
 
             return GetCompatibleVersions(
@@ -190,19 +190,23 @@ namespace Emby.Server.Implementations.Updates
         }
 
         /// <inheritdoc />
-        public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
+        public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
         {
             var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
 
             var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
 
             // Figure out what needs to be installed
-            return _applicationHost.Plugins.Select(x =>
+            foreach (var plugin in _applicationHost.Plugins)
             {
-                var compatibleversions = GetCompatibleVersions(catalog, x.Name, x.Id, x.Version, systemUpdateLevel);
-                return compatibleversions.FirstOrDefault(y => y.Version > x.Version);
-            }).Where(x => x != null)
-            .Where(x => !CompletedInstallations.Any(y => string.Equals(y.AssemblyGuid, x.guid, StringComparison.OrdinalIgnoreCase)));
+                var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
+                var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
+                if (version != null
+                    && !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
+                {
+                    yield return version;
+                }
+            }
         }
 
         /// <inheritdoc />

+ 7 - 29
MediaBrowser.Api/EnvironmentService.cs

@@ -52,6 +52,7 @@ namespace MediaBrowser.Api
         public bool? IsFile { get; set; }
     }
 
+    [Obsolete]
     [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
     public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
     {
@@ -192,22 +193,18 @@ namespace MediaBrowser.Api
 
             var networkPrefix = UncSeparatorString + UncSeparatorString;
 
-            if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf(UncSeparator) == 1)
+            if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase)
+                && path.LastIndexOf(UncSeparator) == 1)
             {
-                return ToOptimizedResult(GetNetworkShares(path).OrderBy(i => i.Path).ToList());
+                return ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
             }
 
             return ToOptimizedResult(GetFileSystemEntries(request).ToList());
         }
 
+        [Obsolete]
         public object Get(GetNetworkShares request)
-        {
-            var path = request.Path;
-
-            var shares = GetNetworkShares(path).OrderBy(i => i.Path).ToList();
-
-            return ToOptimizedResult(shares);
-        }
+            => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
 
         /// <summary>
         /// Gets the specified request.
@@ -241,26 +238,7 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         public object Get(GetNetworkDevices request)
-        {
-            var result = _networkManager.GetNetworkDevices().ToList();
-
-            return ToOptimizedResult(result);
-        }
-
-        /// <summary>
-        /// Gets the network shares.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
-        private IEnumerable<FileSystemEntryInfo> GetNetworkShares(string path)
-        {
-            return _networkManager.GetNetworkShares(path).Where(s => s.ShareType == NetworkShareType.Disk).Select(c => new FileSystemEntryInfo
-            {
-                Name = c.Name,
-                Path = Path.Combine(path, c.Name),
-                Type = FileSystemEntryType.NetworkShare
-            });
-        }
+            => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>());
 
         /// <summary>
         /// Gets the file system entries.

+ 0 - 15
MediaBrowser.Common/Net/INetworkManager.cs

@@ -4,8 +4,6 @@ using System;
 using System.Collections.Generic;
 using System.Net;
 using System.Net.NetworkInformation;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
 
 namespace MediaBrowser.Common.Net
 {
@@ -36,19 +34,6 @@ namespace MediaBrowser.Common.Net
         /// <returns><c>true</c> if [is in private address space] [the specified endpoint]; otherwise, <c>false</c>.</returns>
         bool IsInPrivateAddressSpace(string endpoint);
 
-        /// <summary>
-        /// Gets the network shares.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>IEnumerable{NetworkShare}.</returns>
-        IEnumerable<NetworkShare> GetNetworkShares(string path);
-
-        /// <summary>
-        /// Gets available devices within the domain
-        /// </summary>
-        /// <returns>PC's in the Domain</returns>
-        IEnumerable<FileSystemEntryInfo> GetNetworkDevices();
-
         /// <summary>
         /// Determines whether [is in local network] [the specified endpoint].
         /// </summary>

+ 1 - 1
MediaBrowser.Common/Updates/IInstallationManager.cs

@@ -92,7 +92,7 @@ namespace MediaBrowser.Common.Updates
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>The available plugin updates.</returns>
-        Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
+        IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
 
         /// <summary>
         /// Installs the package.