Browse Source

Merge pull request #2847 from MediaBrowser/beta

Beta
Luke 8 years ago
parent
commit
f3ee129bd9
100 changed files with 1340 additions and 1594 deletions
  1. 14 4
      Emby.Dlna/ContentDirectory/ContentDirectory.cs
  2. 25 18
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  3. 3 17
      Emby.Dlna/Didl/DidlBuilder.cs
  4. 2 5
      Emby.Dlna/Didl/Filter.cs
  5. 0 1
      Emby.Dlna/Didl/StringWriterWithEncoding.cs
  6. 5 5
      Emby.Dlna/PlayTo/PlayToController.cs
  7. 1 1
      Emby.Dlna/PlayTo/PlayToManager.cs
  8. 135 135
      Emby.Dlna/Profiles/PanasonicVieraProfile.cs
  9. 5 4
      Emby.Dlna/Ssdp/Extensions.cs
  10. 3 3
      Emby.Drawing.ImageMagick/ImageHelpers.cs
  11. 3 4
      Emby.Drawing.ImageMagick/ImageMagickEncoder.cs
  12. 6 6
      Emby.Drawing.ImageMagick/StripCollageBuilder.cs
  13. 88 12
      Emby.Drawing/ImageProcessor.cs
  14. 6 6
      Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
  15. 3 3
      Emby.Server.Implementations/Activity/ActivityManager.cs
  16. 3 4
      Emby.Server.Implementations/Activity/ActivityRepository.cs
  17. 20 13
      Emby.Server.Implementations/ApplicationHost.cs
  18. 16 24
      Emby.Server.Implementations/Channels/ChannelManager.cs
  19. 23 12
      Emby.Server.Implementations/Collections/CollectionManager.cs
  20. 4 5
      Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
  21. 17 23
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  22. 7 8
      Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
  23. 2 3
      Emby.Server.Implementations/Data/SqliteUserRepository.cs
  24. 5 1
      Emby.Server.Implementations/Devices/DeviceRepository.cs
  25. 84 31
      Emby.Server.Implementations/Dto/DtoService.cs
  26. 3 4
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  27. 5 5
      Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  28. 1 1
      Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
  29. 1 1
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  30. 5 3
      Emby.Server.Implementations/IO/LibraryMonitor.cs
  31. 27 30
      Emby.Server.Implementations/Library/LibraryManager.cs
  32. 1 1
      Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
  33. 13 4
      Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
  34. 1 1
      Emby.Server.Implementations/Library/SearchEngine.cs
  35. 7 7
      Emby.Server.Implementations/Library/UserDataManager.cs
  36. 34 34
      Emby.Server.Implementations/Library/UserManager.cs
  37. 11 10
      Emby.Server.Implementations/Library/UserViewManager.cs
  38. 105 60
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  39. 4 0
      Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
  40. 21 10
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  41. 19 32
      Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
  42. 135 93
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  43. 16 6
      Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
  44. 1 1
      Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
  45. 71 57
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  46. 6 12
      Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
  47. 16 34
      Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
  48. 5 77
      Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
  49. 0 179
      Emby.Server.Implementations/Localization/Core/core.json
  50. 0 14
      Emby.Server.Implementations/Localization/Core/en-US.json
  51. 10 10
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  52. 3 10
      Emby.Server.Implementations/Logging/SimpleLogManager.cs
  53. 7 2
      Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
  54. 16 16
      Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs
  55. 1 1
      Emby.Server.Implementations/Notifications/NotificationManager.cs
  56. 2 2
      Emby.Server.Implementations/Notifications/Notifications.cs
  57. 2 2
      Emby.Server.Implementations/Playlists/PlaylistManager.cs
  58. 3 4
      Emby.Server.Implementations/Security/AuthenticationRepository.cs
  59. 7 33
      Emby.Server.Implementations/Services/ServiceController.cs
  60. 4 4
      Emby.Server.Implementations/Services/ServiceHandler.cs
  61. 65 58
      Emby.Server.Implementations/Session/SessionManager.cs
  62. 4 4
      Emby.Server.Implementations/Social/SharingManager.cs
  63. 2 4
      Emby.Server.Implementations/Social/SharingRepository.cs
  64. 1 1
      Emby.Server.Implementations/Sorting/ArtistComparer.cs
  65. 3 3
      Emby.Server.Implementations/TV/TVSeriesManager.cs
  66. 74 56
      Emby.Server.Implementations/Updates/InstallationManager.cs
  67. 1 1
      Emby.Server.Implementations/packages.config
  68. 20 4
      MediaBrowser.Api/BaseApiService.cs
  69. 4 4
      MediaBrowser.Api/ChannelService.cs
  70. 2 3
      MediaBrowser.Api/ConfigurationService.cs
  71. 1 2
      MediaBrowser.Api/Devices/DeviceService.cs
  72. 1 3
      MediaBrowser.Api/DisplayPreferencesService.cs
  73. 0 1
      MediaBrowser.Api/Dlna/DlnaServerService.cs
  74. 4 5
      MediaBrowser.Api/Dlna/DlnaService.cs
  75. 3 6
      MediaBrowser.Api/EnvironmentService.cs
  76. 1 1
      MediaBrowser.Api/FilterService.cs
  77. 8 52
      MediaBrowser.Api/GamesService.cs
  78. 2 2
      MediaBrowser.Api/IHasItemFields.cs
  79. 1 14
      MediaBrowser.Api/Images/ImageService.cs
  80. 5 5
      MediaBrowser.Api/Images/RemoteImageService.cs
  81. 6 6
      MediaBrowser.Api/ItemUpdateService.cs
  82. 8 8
      MediaBrowser.Api/Library/LibraryService.cs
  83. 11 9
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  84. 6 7
      MediaBrowser.Api/LocalizationService.cs
  85. 0 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  86. 4 5
      MediaBrowser.Api/Movies/CollectionService.cs
  87. 5 5
      MediaBrowser.Api/Movies/MoviesService.cs
  88. 2 1
      MediaBrowser.Api/Movies/TrailersService.cs
  89. 9 3
      MediaBrowser.Api/Music/InstantMixService.cs
  90. 1 1
      MediaBrowser.Api/NotificationsService.cs
  91. 0 162
      MediaBrowser.Api/PackageReviewService.cs
  92. 14 13
      MediaBrowser.Api/PackageService.cs
  93. 5 7
      MediaBrowser.Api/PlaylistService.cs
  94. 5 6
      MediaBrowser.Api/PluginService.cs
  95. 1 1
      MediaBrowser.Api/Reports/Data/ReportBuilder.cs
  96. 2 2
      MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
  97. 4 4
      MediaBrowser.Api/SearchService.cs
  98. 6 9
      MediaBrowser.Api/Session/SessionsService.cs
  99. 4 4
      MediaBrowser.Api/SimilarItemsHelper.cs
  100. 2 3
      MediaBrowser.Api/Social/SharingService.cs

+ 14 - 4
Emby.Dlna/ContentDirectory/ContentDirectory.cs

@@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Globalization;
@@ -129,9 +128,20 @@ namespace Emby.Dlna.ContentDirectory
                 }
             }
 
-            // No configuration so it's going to be pretty arbitrary
-            return _userManager.Users.FirstOrDefault(i => i.Policy.IsAdministrator) ??
-                _userManager.Users.First();
+            foreach (var user in _userManager.Users)
+            {
+                if (user.Policy.IsAdministrator)
+                {
+                    return user;
+                }
+            }
+
+            foreach (var user in _userManager.Users)
+            {
+                return user;
+            }
+
+            return null;
         }
 
         public void Dispose()

+ 25 - 18
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -487,6 +487,11 @@ namespace Emby.Dlna.ContentDirectory
                 return GetMusicArtistItems(item, null, user, sort, startIndex, limit);
             }
 
+            if (item is Genre)
+            {
+                return GetGenreItems(item, null, user, sort, startIndex, limit);
+            }
+
             var collectionFolder = item as ICollectionFolder;
             if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
             {
@@ -503,23 +508,6 @@ namespace Emby.Dlna.ContentDirectory
 
             if (stubType.HasValue)
             {
-                if (stubType.Value == StubType.People)
-                {
-                    var items = _libraryManager.GetPeopleItems(new InternalPeopleQuery
-                    {
-                        ItemId = item.Id
-
-                    });
-
-                    var result = new QueryResult<ServerItem>
-                    {
-                        Items = items.Select(i => new ServerItem(i)).ToArray(items.Count),
-                        TotalRecordCount = items.Count
-                    };
-
-                    return ApplyPaging(result, startIndex, limit);
-                }
-
                 var person = item as Person;
                 if (person != null)
                 {
@@ -1173,6 +1161,26 @@ namespace Emby.Dlna.ContentDirectory
             return ToResult(result);
         }
 
+        private QueryResult<ServerItem> GetGenreItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit)
+        {
+            var query = new InternalItemsQuery(user)
+            {
+                Recursive = true,
+                ParentId = parentId,
+                GenreIds = new[] { item.Id.ToString("N") },
+                IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name },
+                Limit = limit,
+                StartIndex = startIndex,
+                DtoOptions = GetDtoOptions()
+            };
+
+            SetSorting(query, sort, false);
+
+            var result = _libraryManager.GetItemsResult(query);
+
+            return ToResult(result);
+        }
+
         private QueryResult<ServerItem> GetMusicGenreItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit)
         {
             var query = new InternalItemsQuery(user)
@@ -1331,7 +1339,6 @@ namespace Emby.Dlna.ContentDirectory
     public enum StubType
     {
         Folder = 0,
-        People = 1,
         Latest = 2,
         Playlists = 3,
         Albums = 4,

+ 3 - 17
Emby.Dlna/Didl/DidlBuilder.cs

@@ -198,7 +198,7 @@ namespace Emby.Dlna.Didl
                 streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions
                 {
                     ItemId = GetClientId(video),
-                    MediaSources = sources,
+                    MediaSources = sources.ToArray(sources.Count),
                     Profile = _profile,
                     DeviceId = deviceId,
                     MaxBitrate = _profile.MaxStreamingBitrate
@@ -236,7 +236,7 @@ namespace Emby.Dlna.Didl
                 AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
             }
 
-            var subtitleProfiles = streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken)
+            var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken)
                 .Where(subtitle => subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
                 .ToList();
 
@@ -391,14 +391,6 @@ namespace Emby.Dlna.Didl
 
         private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
         {
-            if (itemStubType.HasValue && itemStubType.Value == StubType.People)
-            {
-                if (item is Video)
-                {
-                    return _localization.GetLocalizedString("HeaderCastCrew");
-                }
-                return _localization.GetLocalizedString("HeaderPeople");
-            }
             if (itemStubType.HasValue && itemStubType.Value == StubType.Latest)
             {
                 return _localization.GetLocalizedString("ViewTypeMusicLatest");
@@ -513,7 +505,7 @@ namespace Emby.Dlna.Didl
                 streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions
                 {
                     ItemId = GetClientId(audio),
-                    MediaSources = sources,
+                    MediaSources = sources.ToArray(sources.Count),
                     Profile = _profile,
                     DeviceId = deviceId
                 });
@@ -961,12 +953,6 @@ namespace Emby.Dlna.Didl
 
         private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
         {
-            if (stubType.HasValue && stubType.Value == StubType.People)
-            {
-                AddEmbeddedImageAsCover("people", writer);
-                return;
-            }
-
             ImageDownloadInfo imageInfo = null;
 
             if (context is UserView)

+ 2 - 5
Emby.Dlna/Didl/Filter.cs

@@ -1,13 +1,12 @@
 using MediaBrowser.Model.Extensions;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace Emby.Dlna.Didl
 {
     public class Filter
     {
-        private readonly List<string> _fields;
+        private readonly string[] _fields;
         private readonly bool _all;
 
         public Filter()
@@ -20,9 +19,7 @@ namespace Emby.Dlna.Didl
         {
             _all = StringHelper.EqualsIgnoreCase(filter, "*");
 
-            var list = (filter ?? string.Empty).Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList();
-
-            _fields = list;
+            _fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
         }
 
         public bool Contains(string field)

+ 0 - 1
Emby.Dlna/Didl/StringWriterWithEncoding.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 

+ 5 - 5
Emby.Dlna/PlayTo/PlayToController.cs

@@ -13,13 +13,13 @@ using MediaBrowser.Model.System;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Extensions;
 
 namespace Emby.Dlna.PlayTo
 {
@@ -48,7 +48,7 @@ namespace Emby.Dlna.PlayTo
         {
             get
             {
-                var lastDateKnownActivity = new[] { _creationTime, _device.DateLastActivity }.Max();
+                var lastDateKnownActivity = _creationTime > _device.DateLastActivity ? _creationTime : _device.DateLastActivity;
 
                 if (DateTime.UtcNow >= lastDateKnownActivity.AddSeconds(120))
                 {
@@ -564,7 +564,7 @@ namespace Emby.Dlna.PlayTo
                     streamInfo.TargetVideoCodecTag,
                     streamInfo.IsTargetAVC);
 
-                return list.FirstOrDefault();
+                return list.Count == 0 ? null : list[0];
             }
 
             return null;
@@ -589,7 +589,7 @@ namespace Emby.Dlna.PlayTo
                     StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildVideoItem(new VideoOptions
                     {
                         ItemId = item.Id.ToString("N"),
-                        MediaSources = mediaSources,
+                        MediaSources = mediaSources.ToArray(mediaSources.Count),
                         Profile = profile,
                         DeviceId = deviceId,
                         MaxBitrate = profile.MaxStreamingBitrate,
@@ -609,7 +609,7 @@ namespace Emby.Dlna.PlayTo
                     StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildAudioItem(new AudioOptions
                     {
                         ItemId = item.Id.ToString("N"),
-                        MediaSources = mediaSources,
+                        MediaSources = mediaSources.ToArray(mediaSources.Count),
                         Profile = profile,
                         DeviceId = deviceId,
                         MaxBitrate = profile.MaxStreamingBitrate,

+ 1 - 1
Emby.Dlna/PlayTo/PlayToManager.cs

@@ -182,7 +182,7 @@ namespace Emby.Dlna.PlayTo
                     {
                         PlayableMediaTypes = profile.GetSupportedMediaTypes(),
 
-                        SupportedCommands = new List<string>
+                        SupportedCommands = new string[]
                         {
                             GeneralCommandType.VolumeDown.ToString(),
                             GeneralCommandType.VolumeUp.ToString(),

+ 135 - 135
Emby.Dlna/Profiles/PanasonicVieraProfile.cs

@@ -16,14 +16,14 @@ namespace Emby.Dlna.Profiles
                 Manufacturer = "Panasonic",
 
                 Headers = new[]
-               {
-                   new HttpHeaderInfo
-                   {
-                       Name = "User-Agent",
-                       Value = "Panasonic MIL DLNA",
-                       Match = HeaderMatchType.Substring
-                   }
-               }
+                {
+                    new HttpHeaderInfo
+                    {
+                        Name = "User-Agent",
+                        Value = "Panasonic MIL DLNA",
+                        Match = HeaderMatchType.Substring
+                    }
+                }
             };
 
             AddXmlRootAttribute("xmlns:pv", "http://www.pv.com/pvns/");
@@ -31,105 +31,105 @@ namespace Emby.Dlna.Profiles
             TimelineOffsetSeconds = 10;
 
             TranscodingProfiles = new[]
-           {
-               new TranscodingProfile
-               {
-                   Container = "mp3",
-                   AudioCodec = "mp3",
-                   Type = DlnaProfileType.Audio
-               },
-               new TranscodingProfile
-               {
-                   Container = "ts",
-                   AudioCodec = "ac3",
-                   VideoCodec = "h264",
-                   Type = DlnaProfileType.Video
-               },
-               new TranscodingProfile
-               {
-                   Container = "jpeg",
-                   Type = DlnaProfileType.Photo
-               }
-           };
+            {
+                new TranscodingProfile
+                {
+                    Container = "mp3",
+                    AudioCodec = "mp3",
+                    Type = DlnaProfileType.Audio
+                },
+                new TranscodingProfile
+                {
+                    Container = "ts",
+                    AudioCodec = "ac3",
+                    VideoCodec = "h264",
+                    Type = DlnaProfileType.Video
+                },
+                new TranscodingProfile
+                {
+                    Container = "jpeg",
+                    Type = DlnaProfileType.Photo
+                }
+            };
 
             DirectPlayProfiles = new[]
-           {
-               new DirectPlayProfile
-               {
-                   Container = "mpeg,mpg",
-                   VideoCodec = "mpeg2video,mpeg4",
-                   AudioCodec = "ac3,mp3,pcm_dvd",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "mkv",
-                   VideoCodec = "h264,mpeg2video",
-                   AudioCodec = "aac,ac3,dca,mp3,mp2,pcm,dts",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "ts",
-                   VideoCodec = "h264,mpeg2video",
-                   AudioCodec = "aac,mp3,mp2",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "mp4,m4v",
-                   VideoCodec = "h264",
-                   AudioCodec = "aac,ac3,mp3,pcm",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "mov",
-                   VideoCodec = "h264",
-                   AudioCodec = "aac,pcm",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "avi",
-                   VideoCodec = "mpeg4",
-                   AudioCodec = "pcm",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "flv",
-                   VideoCodec = "h264",
-                   AudioCodec = "aac",
-                   Type = DlnaProfileType.Video
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "mp3",
-                   AudioCodec = "mp3",
-                   Type = DlnaProfileType.Audio
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "mp4",
-                   AudioCodec = "aac",
-                   Type = DlnaProfileType.Audio
-               },
-
-               new DirectPlayProfile
-               {
-                   Container = "jpeg",
-                   Type = DlnaProfileType.Photo
-               }
-           };
+            {
+                new DirectPlayProfile
+                {
+                    Container = "mpeg,mpg",
+                    VideoCodec = "mpeg2video,mpeg4",
+                    AudioCodec = "ac3,mp3,pcm_dvd",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "mkv",
+                    VideoCodec = "h264,mpeg2video",
+                    AudioCodec = "aac,ac3,dca,mp3,mp2,pcm,dts",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "ts",
+                    VideoCodec = "h264,mpeg2video",
+                    AudioCodec = "aac,mp3,mp2",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "mp4,m4v",
+                    VideoCodec = "h264",
+                    AudioCodec = "aac,ac3,mp3,pcm",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "mov",
+                    VideoCodec = "h264",
+                    AudioCodec = "aac,pcm",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "avi",
+                    VideoCodec = "mpeg4",
+                    AudioCodec = "pcm",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "flv",
+                    VideoCodec = "h264",
+                    AudioCodec = "aac",
+                    Type = DlnaProfileType.Video
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "mp3",
+                    AudioCodec = "mp3",
+                    Type = DlnaProfileType.Audio
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "mp4",
+                    AudioCodec = "aac",
+                    Type = DlnaProfileType.Audio
+                },
+
+                new DirectPlayProfile
+                {
+                    Container = "jpeg",
+                    Type = DlnaProfileType.Photo
+                }
+            };
 
             ContainerProfiles = new[]
             {
@@ -156,35 +156,35 @@ namespace Emby.Dlna.Profiles
             };
 
             CodecProfiles = new[]
-           {
-               new CodecProfile
-               {
-                   Type = CodecType.Video,
-
-                   Conditions = new[]
-                   {
-                       new ProfileCondition
-                       {
-                           Condition = ProfileConditionType.LessThanEqual,
-                           Property = ProfileConditionValue.Width,
-                           Value = "1920"
-                       },
-                       new ProfileCondition
-                       {
-                           Condition = ProfileConditionType.LessThanEqual,
-                           Property = ProfileConditionValue.Height,
-                           Value = "1080"
-                       },
-                       new ProfileCondition
-                       {
-                           Condition = ProfileConditionType.LessThanEqual,
-                           Property = ProfileConditionValue.VideoBitDepth,
-                           Value = "8",
-                           IsRequired = false
-                       }
-                   }
-               }
-           };
+            {
+                new CodecProfile
+                {
+                    Type = CodecType.Video,
+
+                    Conditions = new[]
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Width,
+                            Value = "1920"
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Height,
+                            Value = "1080"
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.VideoBitDepth,
+                            Value = "8",
+                            IsRequired = false
+                        }
+                    }
+                }
+            };
 
             SubtitleProfiles = new[]
             {
@@ -218,4 +218,4 @@ namespace Emby.Dlna.Profiles
             };
         }
     }
-}
+}

+ 5 - 4
Emby.Dlna/Ssdp/Extensions.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
 using System.Xml.Linq;
@@ -24,10 +23,12 @@ namespace Emby.Dlna.Ssdp
 
         public static string GetDescendantValue(this XElement container, XName name)
         {
-            var node = container.Descendants(name)
-                .FirstOrDefault();
+            foreach (var node in container.Descendants(name))
+            {
+                return node.Value;
+            }
 
-            return node == null ? null : node.Value;
+            return null;
         }
     }
 }

+ 3 - 3
Emby.Drawing.ImageMagick/ImageHelpers.cs

@@ -6,13 +6,13 @@ namespace Emby.Drawing.ImageMagick
 {
     internal static class ImageHelpers
     {
-        internal static List<string> ProjectPaths(List<string> paths, int count)
+        internal static List<string> ProjectPaths(string[] paths, int count)
         {
             if (count <= 0)
             {
                 throw new ArgumentOutOfRangeException("count");
             }
-            if (paths.Count == 0)
+            if (paths.Length == 0)
             {
                 throw new ArgumentOutOfRangeException("paths");
             }
@@ -24,7 +24,7 @@ namespace Emby.Drawing.ImageMagick
             return list.Take(count).ToList();
         }
 
-        private static void AddToList(List<string> list, List<string> paths, int count)
+        private static void AddToList(List<string> list, string[] paths, int count)
         {
             while (list.Count < count)
             {

+ 3 - 4
Emby.Drawing.ImageMagick/ImageMagickEncoder.cs

@@ -7,7 +7,6 @@ using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Logging;
 using System;
 using System.IO;
-using System.Linq;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.System;
 
@@ -306,15 +305,15 @@ namespace Emby.Drawing.ImageMagick
 
             if (ratio >= 1.4)
             {
-                new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
+                new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height);
             }
             else if (ratio >= .9)
             {
-                new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
+                new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height);
             }
             else
             {
-                new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
+                new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths, options.OutputPath, options.Width, options.Height);
             }
         }
 

+ 6 - 6
Emby.Drawing.ImageMagick/StripCollageBuilder.cs

@@ -19,7 +19,7 @@ namespace Emby.Drawing.ImageMagick
             _fileSystem = fileSystem;
         }
 
-        public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height)
+        public void BuildPosterCollage(string[] paths, string outputPath, int width, int height)
         {
             using (var wand = BuildPosterCollageWand(paths, width, height))
             {
@@ -27,7 +27,7 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        public void BuildSquareCollage(List<string> paths, string outputPath, int width, int height)
+        public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
         {
             using (var wand = BuildSquareCollageWand(paths, width, height))
             {
@@ -35,7 +35,7 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        public void BuildThumbCollage(List<string> paths, string outputPath, int width, int height)
+        public void BuildThumbCollage(string[] paths, string outputPath, int width, int height)
         {
             using (var wand = BuildThumbCollageWand(paths, width, height))
             {
@@ -43,7 +43,7 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        private MagickWand BuildPosterCollageWand(List<string> paths, int width, int height)
+        private MagickWand BuildPosterCollageWand(string[] paths, int width, int height)
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
             using (var wandImages = new MagickWand(inputPaths.ToArray()))
@@ -108,7 +108,7 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        private MagickWand BuildThumbCollageWand(List<string> paths, int width, int height)
+        private MagickWand BuildThumbCollageWand(string[] paths, int width, int height)
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
             using (var wandImages = new MagickWand(inputPaths.ToArray()))
@@ -173,7 +173,7 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
+        private MagickWand BuildSquareCollageWand(string[] paths, int width, int height)
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
             var outputWand = new MagickWand(width, height, new PixelWand("none", 1));

+ 88 - 12
Emby.Drawing/ImageProcessor.cs

@@ -18,6 +18,7 @@ using System.Threading.Tasks;
 using MediaBrowser.Model.IO;
 using Emby.Drawing.Common;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Threading;
 using MediaBrowser.Model.Extensions;
@@ -44,7 +45,7 @@ namespace Emby.Drawing
         /// Image processors are specialized metadata providers that run after the normal ones
         /// </summary>
         /// <value>The image enhancers.</value>
-        public IEnumerable<IImageEnhancer> ImageEnhancers { get; private set; }
+        public IImageEnhancer[] ImageEnhancers { get; private set; }
 
         /// <summary>
         /// The _logger
@@ -56,22 +57,24 @@ namespace Emby.Drawing
         private readonly IServerApplicationPaths _appPaths;
         private IImageEncoder _imageEncoder;
         private readonly Func<ILibraryManager> _libraryManager;
+        private readonly Func<IMediaEncoder> _mediaEncoder;
 
         public ImageProcessor(ILogger logger,
             IServerApplicationPaths appPaths,
             IFileSystem fileSystem,
             IJsonSerializer jsonSerializer,
             IImageEncoder imageEncoder,
-            Func<ILibraryManager> libraryManager, ITimerFactory timerFactory)
+            Func<ILibraryManager> libraryManager, ITimerFactory timerFactory, Func<IMediaEncoder> mediaEncoder)
         {
             _logger = logger;
             _fileSystem = fileSystem;
             _jsonSerializer = jsonSerializer;
             _imageEncoder = imageEncoder;
             _libraryManager = libraryManager;
+            _mediaEncoder = mediaEncoder;
             _appPaths = appPaths;
 
-            ImageEnhancers = new List<IImageEnhancer>();
+            ImageEnhancers = new IImageEnhancer[] { };
             _saveImageSizeTimer = timerFactory.Create(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite);
             ImageHelper.ImageProcessor = this;
 
@@ -120,7 +123,36 @@ namespace Emby.Drawing
         {
             get
             {
-                return _imageEncoder.SupportedInputFormats;
+                return new string[]
+                {
+                    "tiff",
+                    "jpeg",
+                    "jpg",
+                    "png",
+                    "aiff",
+                    "cr2",
+                    "crw",
+                    "dng", 
+
+                    // Remove until supported
+                    //"nef", 
+                    "orf",
+                    "pef",
+                    "arw",
+                    "webp",
+                    "gif",
+                    "bmp",
+                    "erf",
+                    "raf",
+                    "rw2",
+                    "nrw",
+                    "dng",
+                    "ico",
+                    "astc",
+                    "ktx",
+                    "pkm",
+                    "wbmp"
+                };
             }
         }
 
@@ -203,6 +235,10 @@ namespace Emby.Drawing
                 return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
 
+            var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
+            originalImagePath = supportedImageInfo.Item1;
+            dateModified = supportedImageInfo.Item2;
+
             if (options.Enhancers.Count > 0)
             {
                 if (item == null)
@@ -618,7 +654,7 @@ namespace Emby.Drawing
 
             var supportedEnhancers = GetSupportedEnhancers(item, image.Type);
 
-            return GetImageCacheTag(item, image, supportedEnhancers.ToList());
+            return GetImageCacheTag(item, image, supportedEnhancers);
         }
 
         /// <summary>
@@ -663,6 +699,42 @@ namespace Emby.Drawing
             return string.Join("|", cacheKeys.ToArray(cacheKeys.Count)).GetMD5().ToString("N");
         }
 
+        private async Task<Tuple<string, DateTime>> GetSupportedImage(string originalImagePath, DateTime dateModified)
+        {
+            var inputFormat = (Path.GetExtension(originalImagePath) ?? string.Empty)
+                .TrimStart('.')
+                .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
+
+            if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase))
+            {
+                try
+                {
+                    var filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N");
+
+                    var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + ".webp");
+
+                    var file = _fileSystem.GetFileInfo(outputPath);
+                    if (!file.Exists)
+                    {
+                        await _mediaEncoder().ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
+                        dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
+                    }
+                    else
+                    {
+                        dateModified = file.LastWriteTimeUtc;
+                    }
+
+                    originalImagePath = outputPath;
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Image conversion failed for {0}", ex, originalImagePath);
+                }
+            }
+
+            return new Tuple<string, DateTime>(originalImagePath, dateModified);
+        }
+
         /// <summary>
         /// Gets the enhanced image.
         /// </summary>
@@ -672,7 +744,7 @@ namespace Emby.Drawing
         /// <returns>Task{System.String}.</returns>
         public async Task<string> GetEnhancedImage(IHasMetadata item, ImageType imageType, int imageIndex)
         {
-            var enhancers = GetSupportedEnhancers(item, imageType).ToList();
+            var enhancers = GetSupportedEnhancers(item, imageType);
 
             var imageInfo = item.GetImageInfo(imageType, imageIndex);
 
@@ -866,21 +938,25 @@ namespace Emby.Drawing
             _logger.Info("Completed creation of image collage and saved to {0}", options.OutputPath);
         }
 
-        public IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasMetadata item, ImageType imageType)
+        public List<IImageEnhancer> GetSupportedEnhancers(IHasMetadata item, ImageType imageType)
         {
-            return ImageEnhancers.Where(i =>
+            var list = new List<IImageEnhancer>();
+
+            foreach (var i in ImageEnhancers)
             {
                 try
                 {
-                    return i.Supports(item, imageType);
+                    if (i.Supports(item, imageType))
+                    {
+                        list.Add(i);
+                    }
                 }
                 catch (Exception ex)
                 {
                     _logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name);
-
-                    return false;
                 }
-            });
+            }
+            return list;
         }
 
         private bool _disposed;

+ 6 - 6
Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs

@@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Activity
             {
                 // Don't report theme song or local trailer playback
                 return;
-            } 
+            }
 
             if (e.Users.Count == 0)
             {
@@ -160,8 +160,8 @@ namespace Emby.Server.Implementations.Activity
             {
                 // Don't report theme song or local trailer playback
                 return;
-            } 
-            
+            }
+
             if (e.Users.Count == 0)
             {
                 return;
@@ -416,7 +416,7 @@ namespace Emby.Server.Implementations.Activity
             {
                 return;
             }
-            
+
             var time = result.EndTimeUtc - result.StartTimeUtc;
             var runningTime = string.Format(_localization.GetLocalizedString("LabelRunningTimeValue"), ToUserFriendlyString(time));
 
@@ -444,11 +444,11 @@ namespace Emby.Server.Implementations.Activity
             }
         }
 
-        private async void CreateLogEntry(ActivityLogEntry entry)
+        private void CreateLogEntry(ActivityLogEntry entry)
         {
             try
             {
-                await _activityManager.Create(entry).ConfigureAwait(false);
+                _activityManager.Create(entry);
             }
             catch
             {

+ 3 - 3
Emby.Server.Implementations/Activity/ActivityManager.cs

@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Activity
     public class ActivityManager : IActivityManager
     {
         public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
-        
+
         private readonly IActivityRepository _repo;
         private readonly ILogger _logger;
         private readonly IUserManager _userManager;
@@ -25,12 +25,12 @@ namespace Emby.Server.Implementations.Activity
             _userManager = userManager;
         }
 
-        public async Task Create(ActivityLogEntry entry)
+        public void Create(ActivityLogEntry entry)
         {
             entry.Id = Guid.NewGuid().ToString("N");
             entry.Date = DateTime.UtcNow;
 
-            await _repo.Create(entry).ConfigureAwait(false);
+            _repo.Create(entry);
 
             EventHelper.FireEventIfNotNull(EntryCreated, this, new GenericEventArgs<ActivityLogEntry>(entry), _logger);
         }

+ 3 - 4
Emby.Server.Implementations/Activity/ActivityRepository.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
-using System.Threading.Tasks;
 using Emby.Server.Implementations.Data;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Activity;
@@ -41,12 +40,12 @@ namespace Emby.Server.Implementations.Activity
 
         private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries";
 
-        public Task Create(ActivityLogEntry entry)
+        public void Create(ActivityLogEntry entry)
         {
-            return Update(entry);
+            Update(entry);
         }
 
-        public async Task Update(ActivityLogEntry entry)
+        public void Update(ActivityLogEntry entry)
         {
             if (entry == null)
             {

+ 20 - 13
Emby.Server.Implementations/ApplicationHost.cs

@@ -227,6 +227,8 @@ namespace Emby.Server.Implementations
 
         protected IEnvironmentInfo EnvironmentInfo { get; set; }
 
+        private IBlurayExaminer BlurayExaminer { get; set; }
+
         public PackageVersionClass SystemUpdateLevel
         {
             get
@@ -424,11 +426,6 @@ namespace Emby.Server.Implementations
 
             SetBaseExceptionMessage();
 
-            if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
-            {
-                fileSystem.AddShortcutHandler(new LnkShortcutHandler());
-            }
-
             fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
         }
 
@@ -866,7 +863,7 @@ namespace Emby.Server.Implementations
             SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager, FileSystemManager, CryptographyProvider);
             RegisterSingleInstance(SecurityManager);
 
-            InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager, CryptographyProvider);
+            InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager, CryptographyProvider, PackageRuntime);
             RegisterSingleInstance(InstallationManager);
 
             ZipClient = new ZipClient(FileSystemManager);
@@ -889,7 +886,8 @@ namespace Emby.Server.Implementations
             ITextEncoding textEncoding = new TextEncoding.TextEncoding(FileSystemManager, LogManager.GetLogger("TextEncoding"), JsonSerializer);
             RegisterSingleInstance(textEncoding);
             Utilities.EncodingHelper = textEncoding;
-            RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer(FileSystemManager, textEncoding));
+            BlurayExaminer = new BdInfoExaminer(FileSystemManager, textEncoding);
+            RegisterSingleInstance(BlurayExaminer);
 
             RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
 
@@ -1050,7 +1048,15 @@ namespace Emby.Server.Implementations
 
             SetStaticProperties();
 
-            await ((UserManager)UserManager).Initialize().ConfigureAwait(false);
+            ((UserManager)UserManager).Initialize();
+        }
+
+        protected virtual string PackageRuntime
+        {
+            get
+            {
+                return "netframework";
+            }
         }
 
         public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, bool isStartup)
@@ -1199,7 +1205,7 @@ namespace Emby.Server.Implementations
 
         private IImageProcessor GetImageProcessor()
         {
-            return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, () => LibraryManager, TimerFactory);
+            return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, () => LibraryManager, TimerFactory, () => MediaEncoder);
         }
 
         protected virtual FFMpegInstallInfo GetFfmpegInstallInfo()
@@ -1332,7 +1338,8 @@ namespace Emby.Server.Implementations
                 ProcessFactory,
                 (Environment.ProcessorCount > 2 ? 14000 : 40000),
                 EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows,
-                EnvironmentInfo);
+                EnvironmentInfo,
+                BlurayExaminer);
 
             MediaEncoder = mediaEncoder;
             RegisterSingleInstance(MediaEncoder);
@@ -1858,9 +1865,9 @@ namespace Emby.Server.Implementations
                 HasPendingRestart = HasPendingRestart,
                 Version = ApplicationVersion.ToString(),
                 WebSocketPortNumber = HttpPort,
-                FailedPluginAssemblies = FailedAssemblies.ToList(),
-                InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToList(),
-                CompletedInstallations = InstallationManager.CompletedInstallations.ToList(),
+                FailedPluginAssemblies = FailedAssemblies.ToArray(),
+                InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToArray(),
+                CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
                 Id = SystemId,
                 ProgramDataPath = ApplicationPaths.ProgramDataPath,
                 LogPath = ApplicationPaths.LogDirectoryPath,

+ 16 - 24
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -182,10 +182,8 @@ namespace Emby.Server.Implementations.Channels
             {
             };
 
-            var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+            var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
                 .ConfigureAwait(false));
-            var returnItems = returnList
-                .ToArray(returnList.Count);
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -431,7 +429,7 @@ namespace Emby.Server.Implementations.Channels
 
             if (isNew)
             {
-                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+                _libraryManager.CreateItem(item, cancellationToken);
             }
             else if (forceUpdate)
             {
@@ -464,14 +462,14 @@ namespace Emby.Server.Implementations.Channels
             return _libraryManager.GetItemById(id) as Channel;
         }
 
-        public IEnumerable<ChannelFeatures> GetAllChannelFeatures()
+        public ChannelFeatures[] GetAllChannelFeatures()
         {
             return _libraryManager.GetItemIds(new InternalItemsQuery
             {
                 IncludeItemTypes = new[] { typeof(Channel).Name },
                 SortBy = new[] { ItemSortBy.SortName }
 
-            }).Select(i => GetChannelFeatures(i.ToString("N")));
+            }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray();
         }
 
         public ChannelFeatures GetChannelFeatures(string id)
@@ -511,10 +509,10 @@ namespace Emby.Server.Implementations.Channels
             {
                 CanFilter = !features.MaxPageSize.HasValue,
                 CanSearch = provider is ISearchableChannel,
-                ContentTypes = features.ContentTypes,
-                DefaultSortFields = features.DefaultSortFields,
+                ContentTypes = features.ContentTypes.ToArray(),
+                DefaultSortFields = features.DefaultSortFields.ToArray(),
                 MaxPageSize = features.MaxPageSize,
-                MediaTypes = features.MediaTypes,
+                MediaTypes = features.MediaTypes.ToArray(),
                 SupportsSortOrderToggle = features.SupportsSortOrderToggle,
                 SupportsLatestMedia = supportsLatest,
                 Name = channel.Name,
@@ -566,12 +564,10 @@ namespace Emby.Server.Implementations.Channels
 
             var dtoOptions = new DtoOptions()
             {
-                Fields = query.Fields.ToList()
+                Fields = query.Fields
             };
 
-            var returnList = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false));
-            var returnItems = returnList
-                .ToArray(returnList.Count);
+            var returnItems = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false));
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -833,13 +829,11 @@ namespace Emby.Server.Implementations.Channels
 
             var dtoOptions = new DtoOptions()
             {
-                Fields = query.Fields.ToList()
+                Fields = query.Fields
             };
 
-            var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+            var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
                 .ConfigureAwait(false));
-            var returnItems = returnList
-                .ToArray(returnList.Count);
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -987,13 +981,11 @@ namespace Emby.Server.Implementations.Channels
 
             var dtoOptions = new DtoOptions()
             {
-                Fields = query.Fields.ToList()
+                Fields = query.Fields
             };
 
-            var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+            var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
                 .ConfigureAwait(false));
-            var returnItems = returnList
-                .ToArray(returnList.Count);
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -1338,7 +1330,7 @@ namespace Emby.Server.Implementations.Channels
             var hasArtists = item as IHasArtist;
             if (hasArtists != null)
             {
-                hasArtists.Artists = info.Artists;
+                hasArtists.Artists = info.Artists.ToArray();
             }
 
             var hasAlbumArtists = item as IHasAlbumArtist;
@@ -1396,11 +1388,11 @@ namespace Emby.Server.Implementations.Channels
 
             if (isNew)
             {
-                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+                _libraryManager.CreateItem(item, cancellationToken);
 
                 if (info.People != null && info.People.Count > 0)
                 {
-                    await _libraryManager.UpdatePeople(item, info.People ?? new List<PersonInfo>()).ConfigureAwait(false);
+                    _libraryManager.UpdatePeople(item, info.People ?? new List<PersonInfo>());
                 }
             }
             else if (forceUpdate)

+ 23 - 12
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -84,15 +84,15 @@ namespace Emby.Server.Implementations.Collections
                     ProviderIds = options.ProviderIds,
                     Shares = options.UserIds.Select(i => new Share
                     {
-                        UserId = i.ToString("N"),
+                        UserId = i,
                         CanEdit = true
 
                     }).ToList()
                 };
 
-                await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false);
+                parentFolder.AddChild(collection, CancellationToken.None);
 
-                if (options.ItemIdList.Count > 0)
+                if (options.ItemIdList.Length > 0)
                 {
                     await AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(_fileSystem)
                     {
@@ -149,12 +149,17 @@ namespace Emby.Server.Implementations.Collections
             return GetCollectionsFolder(string.Empty);
         }
 
-        public Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
+        public Task AddToCollection(Guid collectionId, IEnumerable<string> ids)
         {
             return AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(_fileSystem));
         }
 
-        private async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
+        public Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
+        {
+            return AddToCollection(collectionId, ids.Select(i => i.ToString("N")), true, new MetadataRefreshOptions(_fileSystem));
+        }
+
+        private async Task AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         {
             var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
 
@@ -165,11 +170,12 @@ namespace Emby.Server.Implementations.Collections
 
             var list = new List<LinkedChild>();
             var itemList = new List<BaseItem>();
-            var currentLinkedChildren = collection.GetLinkedChildren().ToList();
+            var currentLinkedChildrenIds = collection.GetLinkedChildren().Select(i => i.Id).ToList();
 
-            foreach (var itemId in ids)
+            foreach (var id in ids)
             {
-                var item = _libraryManager.GetItemById(itemId);
+                var guidId = new Guid(id);
+                var item = _libraryManager.GetItemById(guidId);
 
                 if (string.IsNullOrWhiteSpace(item.Path))
                 {
@@ -183,7 +189,7 @@ namespace Emby.Server.Implementations.Collections
 
                 itemList.Add(item);
 
-                if (currentLinkedChildren.All(i => i.Id != itemId))
+                if (!currentLinkedChildrenIds.Contains(guidId))
                 {
                     list.Add(LinkedChild.Create(item));
                 }
@@ -213,6 +219,11 @@ namespace Emby.Server.Implementations.Collections
             }
         }
 
+        public Task RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
+        {
+            return RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
+        }
+
         public async Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
         {
             var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
@@ -225,11 +236,11 @@ namespace Emby.Server.Implementations.Collections
             var list = new List<LinkedChild>();
             var itemList = new List<BaseItem>();
 
-            foreach (var itemId in itemIds)
+            foreach (var guidId in itemIds)
             {
-                var childItem = _libraryManager.GetItemById(itemId);
+                var childItem = _libraryManager.GetItemById(guidId);
 
-                var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value == itemId) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
+                var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value == guidId) || (childItem != null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
 
                 if (child == null)
                 {

+ 4 - 5
Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Persistence;
@@ -75,7 +74,7 @@ namespace Emby.Server.Implementations.Data
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
+        public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
         {
             if (displayPreferences == null)
             {
@@ -123,7 +122,7 @@ namespace Emby.Server.Implementations.Data
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public async Task SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
+        public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
         {
             if (displayPreferences == null)
             {
@@ -226,9 +225,9 @@ namespace Emby.Server.Implementations.Data
             }
         }
 
-        public Task SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
+        public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
         {
-            return SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
+            SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
         }
 
         public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)

+ 17 - 23
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -132,8 +132,7 @@ namespace Emby.Server.Implementations.Data
         /// <summary>
         /// Opens the connection to the database
         /// </summary>
-        /// <returns>Task.</returns>
-        public async Task Initialize(SqliteUserDataRepository userDataRepo)
+        public void Initialize(SqliteUserDataRepository userDataRepo)
         {
             using (var connection = CreateConnection())
             {
@@ -149,7 +148,7 @@ namespace Emby.Server.Implementations.Data
 
                                 "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))",
                                 "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
-                                "create index if not exists idx_AncestorIds2 on AncestorIds(AncestorIdText)",
+                                "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)",
 
                                 "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)",
 
@@ -308,6 +307,7 @@ namespace Emby.Server.Implementations.Data
                 "drop index if exists idx_TypeSeriesPresentationUniqueKey2",
                 "drop index if exists idx_AncestorIds3",
                 "drop index if exists idx_AncestorIds4",
+                "drop index if exists idx_AncestorIds2",
 
                 "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)",
                 "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
@@ -599,16 +599,15 @@ namespace Emby.Server.Implementations.Data
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public Task SaveItem(BaseItem item, CancellationToken cancellationToken)
+        public void SaveItem(BaseItem item, CancellationToken cancellationToken)
         {
             if (item == null)
             {
                 throw new ArgumentNullException("item");
             }
 
-            return SaveItems(new List<BaseItem> { item }, cancellationToken);
+            SaveItems(new List<BaseItem> { item }, cancellationToken);
         }
 
         /// <summary>
@@ -616,13 +615,12 @@ namespace Emby.Server.Implementations.Data
         /// </summary>
         /// <param name="items">The items.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">
         /// items
         /// or
         /// cancellationToken
         /// </exception>
-        public async Task SaveItems(List<BaseItem> items, CancellationToken cancellationToken)
+        public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken)
         {
             if (items == null)
             {
@@ -1027,9 +1025,9 @@ namespace Emby.Server.Implementations.Data
             var hasArtists = item as IHasArtist;
             if (hasArtists != null)
             {
-                if (hasArtists.Artists.Count > 0)
+                if (hasArtists.Artists.Length > 0)
                 {
-                    artists = string.Join("|", hasArtists.Artists.ToArray());
+                    artists = string.Join("|", hasArtists.Artists);
                 }
             }
             saveItemStatement.TryBind("@Artists", artists);
@@ -1907,7 +1905,7 @@ namespace Emby.Server.Implementations.Data
                 var hasArtists = item as IHasArtist;
                 if (hasArtists != null && !reader.IsDBNull(index))
                 {
-                    hasArtists.Artists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+                    hasArtists.Artists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                 }
                 index++;
 
@@ -1958,22 +1956,18 @@ namespace Emby.Server.Implementations.Data
         /// Gets the critic reviews.
         /// </summary>
         /// <param name="itemId">The item id.</param>
-        /// <returns>Task{IEnumerable{ItemReview}}.</returns>
         public List<ItemReview> GetCriticReviews(Guid itemId)
         {
             return new List<ItemReview>();
         }
 
-        private readonly Task _cachedTask = Task.FromResult(true);
         /// <summary>
         /// Saves the critic reviews.
         /// </summary>
         /// <param name="itemId">The item id.</param>
         /// <param name="criticReviews">The critic reviews.</param>
-        /// <returns>Task.</returns>
-        public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
+        public void SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
         {
-            return _cachedTask;
         }
 
         /// <summary>
@@ -2078,7 +2072,7 @@ namespace Emby.Server.Implementations.Data
         /// <summary>
         /// Saves the chapters.
         /// </summary>
-        public async Task SaveChapters(Guid id, List<ChapterInfo> chapters)
+        public void SaveChapters(Guid id, List<ChapterInfo> chapters)
         {
             CheckDisposed();
 
@@ -4653,12 +4647,12 @@ namespace Emby.Server.Implementations.Data
             typeof(AggregateFolder)
         };
 
-        public async Task UpdateInheritedValues(CancellationToken cancellationToken)
+        public void UpdateInheritedValues(CancellationToken cancellationToken)
         {
-            await UpdateInheritedTags(cancellationToken).ConfigureAwait(false);
+            UpdateInheritedTags(cancellationToken);
         }
 
-        private async Task UpdateInheritedTags(CancellationToken cancellationToken)
+        private void UpdateInheritedTags(CancellationToken cancellationToken)
         {
             var newValues = new List<Tuple<Guid, string[]>>();
 
@@ -4753,7 +4747,7 @@ limit 100";
             return new[] { value }.Where(IsValidType);
         }
 
-        public async Task DeleteItem(Guid id, CancellationToken cancellationToken)
+        public void DeleteItem(Guid id, CancellationToken cancellationToken)
         {
             if (id == Guid.Empty)
             {
@@ -5484,7 +5478,7 @@ limit 100";
             }
         }
 
-        public async Task UpdatePeople(Guid itemId, List<PersonInfo> people)
+        public void UpdatePeople(Guid itemId, List<PersonInfo> people)
         {
             if (itemId == Guid.Empty)
             {
@@ -5614,7 +5608,7 @@ limit 100";
             }
         }
 
-        public async Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)
+        public void SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)
         {
             CheckDisposed();
 

+ 7 - 8
Emby.Server.Implementations/Data/SqliteUserDataRepository.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading;
-using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Persistence;
@@ -153,7 +152,7 @@ namespace Emby.Server.Implementations.Data
         /// userId
         /// or
         /// userDataId</exception>
-        public Task SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
+        public void SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
         {
             if (userData == null)
             {
@@ -168,10 +167,10 @@ namespace Emby.Server.Implementations.Data
                 throw new ArgumentNullException("key");
             }
 
-            return PersistUserData(userId, key, userData, cancellationToken);
+            PersistUserData(userId, key, userData, cancellationToken);
         }
 
-        public Task SaveAllUserData(Guid userId, IEnumerable<UserItemData> userData, CancellationToken cancellationToken)
+        public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
         {
             if (userData == null)
             {
@@ -182,7 +181,7 @@ namespace Emby.Server.Implementations.Data
                 throw new ArgumentNullException("userId");
             }
 
-            return PersistAllUserData(userId, userData.ToList(), cancellationToken);
+            PersistAllUserData(userId, userData, cancellationToken);
         }
 
         /// <summary>
@@ -193,7 +192,7 @@ namespace Emby.Server.Implementations.Data
         /// <param name="userData">The user data.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        public async Task PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
+        public void PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -264,7 +263,7 @@ namespace Emby.Server.Implementations.Data
         /// <summary>
         /// Persist all user data for the specified user
         /// </summary>
-        private async Task PersistAllUserData(Guid userId, List<UserItemData> userDataList, CancellationToken cancellationToken)
+        private void PersistAllUserData(Guid userId, UserItemData[] userDataList, CancellationToken cancellationToken)
         {
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -349,7 +348,7 @@ namespace Emby.Server.Implementations.Data
         /// </summary>
         /// <param name="userId"></param>
         /// <returns></returns>
-        public IEnumerable<UserItemData> GetAllUserData(Guid userId)
+        public List<UserItemData> GetAllUserData(Guid userId)
         {
             if (userId == Guid.Empty)
             {

+ 2 - 3
Emby.Server.Implementations/Data/SqliteUserRepository.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Persistence;
@@ -72,7 +71,7 @@ namespace Emby.Server.Implementations.Data
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">user</exception>
-        public async Task SaveUser(User user, CancellationToken cancellationToken)
+        public void SaveUser(User user, CancellationToken cancellationToken)
         {
             if (user == null)
             {
@@ -139,7 +138,7 @@ namespace Emby.Server.Implementations.Data
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">user</exception>
-        public async Task DeleteUser(User user, CancellationToken cancellationToken)
+        public void DeleteUser(User user, CancellationToken cancellationToken)
         {
             if (user == null)
             {

+ 5 - 1
Emby.Server.Implementations/Devices/DeviceRepository.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Extensions;
 
 namespace Emby.Server.Implementations.Devices
 {
@@ -199,7 +200,10 @@ namespace Emby.Server.Implementations.Devices
                 }
 
                 history.DeviceId = deviceId;
-                history.FilesUploaded.Add(file);
+
+                var list = history.FilesUploaded.ToList();
+                list.Add(file);
+                history.FilesUploaded = list.ToArray(list.Count);
 
                 _json.SerializeToFile(history, path);
             }

+ 84 - 31
Emby.Server.Implementations/Dto/DtoService.cs

@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Dto
         /// <param name="owner">The owner.</param>
         /// <returns>Task{DtoBaseItem}.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null)
+        public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null)
         {
             var options = new DtoOptions
             {
@@ -87,7 +87,17 @@ namespace Emby.Server.Implementations.Dto
             return GetBaseItemDto(item, options, user, owner);
         }
 
-        public async Task<List<BaseItemDto>> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
+        public Task<BaseItemDto[]> GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
+        {
+            return GetBaseItemDtos(items, items.Count, options, user, owner);
+        }
+
+        public Task<BaseItemDto[]> GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null)
+        {
+            return GetBaseItemDtos(items, items.Length, options, user, owner);
+        }
+
+        public async Task<BaseItemDto[]> GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null)
         {
             if (items == null)
             {
@@ -101,17 +111,14 @@ namespace Emby.Server.Implementations.Dto
 
             var syncDictionary = GetSyncedItemProgress(options);
 
-            var list = new List<BaseItemDto>();
+            var returnItems = new BaseItemDto[itemCount];
             var programTuples = new List<Tuple<BaseItem, BaseItemDto>>();
             var channelTuples = new List<Tuple<BaseItemDto, LiveTvChannel>>();
 
-            var refreshQueue = options.Fields.Contains(ItemFields.RefreshState)
-                ? _providerManager.GetRefreshQueue()
-                : null;
-
+            var index = 0;
             foreach (var item in items)
             {
-                var dto = GetBaseItemDtoInternal(item, options, refreshQueue, user, owner);
+                var dto = GetBaseItemDtoInternal(item, options, user, owner);
 
                 var tvChannel = item as LiveTvChannel;
                 if (tvChannel != null)
@@ -144,7 +151,8 @@ namespace Emby.Server.Implementations.Dto
 
                 FillSyncInfo(dto, item, options, user, syncDictionary);
 
-                list.Add(dto);
+                returnItems[index] = dto;
+                index++;
             }
 
             if (programTuples.Count > 0)
@@ -157,18 +165,14 @@ namespace Emby.Server.Implementations.Dto
                 await _livetvManager().AddChannelInfo(channelTuples, options, user).ConfigureAwait(false);
             }
 
-            return list;
+            return returnItems;
         }
 
         public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
         {
             var syncDictionary = GetSyncedItemProgress(options);
 
-            var refreshQueue = options.Fields.Contains(ItemFields.RefreshState)
-                ? _providerManager.GetRefreshQueue()
-                : null;
-
-            var dto = GetBaseItemDtoInternal(item, options, refreshQueue, user, owner);
+            var dto = GetBaseItemDtoInternal(item, options, user, owner);
             var tvChannel = item as LiveTvChannel;
             if (tvChannel != null)
             {
@@ -300,7 +304,7 @@ namespace Emby.Server.Implementations.Dto
             }
         }
 
-        private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Dictionary<Guid, Guid> currentRefreshQueue, User user = null, BaseItem owner = null)
+        private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
         {
             var fields = options.Fields;
 
@@ -365,6 +369,8 @@ namespace Emby.Server.Implementations.Dto
                     {
                         dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true, user);
                     }
+
+                    NormalizeMediaSourceContainers(dto);
                 }
             }
 
@@ -400,25 +406,72 @@ namespace Emby.Server.Implementations.Dto
                 dto.Etag = item.GetEtag(user);
             }
 
-            if (currentRefreshQueue != null)
+            var liveTvManager = _livetvManager();
+            if (item is ILiveTvRecording)
             {
-                //dto.RefreshState = item.GetRefreshState(currentRefreshQueue);
+                liveTvManager.AddInfoToRecordingDto(item, dto, user);
             }
-
-            if (item is ILiveTvRecording)
+            else
             {
-                _livetvManager().AddInfoToRecordingDto(item, dto, user);
+                var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
+                if (activeRecording != null)
+                {
+                    dto.Type = "Recording";
+                    dto.CanDownload = false;
+                    if (!string.IsNullOrWhiteSpace(dto.SeriesName))
+                    {
+                        dto.EpisodeTitle = dto.Name;
+                        dto.Name = dto.SeriesName;
+                    }
+                    liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
+                }
             }
 
             return dto;
         }
 
+        private void NormalizeMediaSourceContainers(BaseItemDto dto)
+        {
+            foreach (var mediaSource in dto.MediaSources)
+            {
+                var container = mediaSource.Container;
+                if (string.IsNullOrWhiteSpace(container))
+                {
+                    continue;
+                }
+                var containers = container.Split(new[] { ',' });
+                if (containers.Length < 2)
+                {
+                    continue;
+                }
+
+                var path = mediaSource.Path;
+                string fileExtensionContainer = null;
+
+                if (!string.IsNullOrWhiteSpace(path))
+                {
+                    path = Path.GetExtension(path);
+                    if (!string.IsNullOrWhiteSpace(path))
+                    {
+                        path = Path.GetExtension(path);
+                        if (!string.IsNullOrWhiteSpace(path))
+                        {
+                            path = path.TrimStart('.');
+                        }
+                        if (!string.IsNullOrWhiteSpace(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
+                        {
+                            fileExtensionContainer = path;
+                        }
+                    }
+                }
+
+                mediaSource.Container = fileExtensionContainer ?? containers[0];
+            }
+        }
+
         public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null)
         {
-            var refreshQueue = options.Fields.Contains(ItemFields.RefreshState)
-                ? _providerManager.GetRefreshQueue()
-                : null;
-            var dto = GetBaseItemDtoInternal(item, options, refreshQueue, user);
+            var dto = GetBaseItemDtoInternal(item, options, user);
 
             if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts))
             {
@@ -992,7 +1045,7 @@ namespace Emby.Server.Implementations.Dto
             {
                 dto.RemoteTrailers = hasTrailers != null ?
                     hasTrailers.RemoteTrailers :
-                    new MediaUrl[] {};
+                    new MediaUrl[] { };
             }
 
             dto.Name = item.Name;
@@ -1053,7 +1106,7 @@ namespace Emby.Server.Implementations.Dto
 
                 if (dto.Taglines == null)
                 {
-                    dto.Taglines = new string[]{};
+                    dto.Taglines = new string[] { };
                 }
             }
 
@@ -1243,17 +1296,17 @@ namespace Emby.Server.Implementations.Dto
 
                 if (iHasMediaSources != null)
                 {
-                    List<MediaStream> mediaStreams;
+                    MediaStream[] mediaStreams;
 
                     if (dto.MediaSources != null && dto.MediaSources.Count > 0)
                     {
                         mediaStreams = dto.MediaSources.Where(i => new Guid(i.Id) == item.Id)
                             .SelectMany(i => i.MediaStreams)
-                            .ToList();
+                            .ToArray();
                     }
                     else
                     {
-                        mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams;
+                        mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams.ToArray();
                     }
 
                     dto.MediaStreams = mediaStreams;
@@ -1564,7 +1617,7 @@ namespace Emby.Server.Implementations.Dto
                 return null;
             }
 
-            var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToList();
+            var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary);
 
             ImageSize size;
 

+ 3 - 4
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -133,7 +133,6 @@
     <Compile Include="IO\FileRefresher.cs" />
     <Compile Include="IO\IsoManager.cs" />
     <Compile Include="IO\LibraryMonitor.cs" />
-    <Compile Include="IO\LnkShortcutHandler.cs" />
     <Compile Include="IO\ManagedFileSystem.cs" />
     <Compile Include="IO\MbLinkShortcutHandler.cs" />
     <Compile Include="IO\MemoryStreamProvider.cs" />
@@ -662,8 +661,9 @@
     <Reference Include="Emby.XmlTv, Version=1.0.6387.29335, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\packages\Emby.XmlTv.1.0.10\lib\portable-net45+netstandard2.0+win8\Emby.XmlTv.dll</HintPath>
     </Reference>
-    <Reference Include="MediaBrowser.Naming, Version=1.0.6437.24226, Culture=neutral, processorArchitecture=MSIL">
-      <HintPath>..\packages\MediaBrowser.Naming.1.0.6\lib\portable-net45+netstandard2.0+win8\MediaBrowser.Naming.dll</HintPath>
+    <Reference Include="MediaBrowser.Naming, Version=1.0.6447.2217, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MediaBrowser.Naming.1.0.7\lib\portable-net45+netstandard2.0+win8\MediaBrowser.Naming.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath>
@@ -699,7 +699,6 @@
     <EmbeddedResource Include="Localization\Core\ar.json" />
     <EmbeddedResource Include="Localization\Core\bg-BG.json" />
     <EmbeddedResource Include="Localization\Core\ca.json" />
-    <EmbeddedResource Include="Localization\Core\core.json" />
     <EmbeddedResource Include="Localization\Core\cs.json" />
     <EmbeddedResource Include="Localization\Core\da.json" />
     <EmbeddedResource Include="Localization\Core\de.json" />

+ 5 - 5
Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -367,15 +367,15 @@ namespace Emby.Server.Implementations.EntryPoints
 
             return new LibraryUpdateInfo
             {
-                ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(),
+                ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
 
-                ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(),
+                ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
 
-                ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToList(),
+                ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
 
-                FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList(),
+                FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(),
 
-                FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToList()
+                FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray()
             };
         }
 

+ 1 - 1
Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs

@@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.EntryPoints
                             dto.ItemId = i.Id.ToString("N");
                             return dto;
                         })
-                        .ToList();
+                        .ToArray();
 
                     var info = new UserDataChangeInfo
                     {

+ 1 - 1
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -162,7 +162,7 @@ namespace Emby.Server.Implementations.HttpServer
             return serviceType;
         }
 
-        public void AddServiceInfo(Type serviceType, Type requestType, Type responseType)
+        public void AddServiceInfo(Type serviceType, Type requestType)
         {
             ServiceOperationsMap[requestType] = serviceType;
         }

+ 5 - 3
Emby.Server.Implementations/IO/LibraryMonitor.cs

@@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.IO
         /// <summary>
         /// Any file name ending in any of these will be ignored by the watchers
         /// </summary>
-        private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string>
+        private readonly string[] _alwaysIgnoreFiles = new string[]
         {
             "small.jpg",
             "albumart.jpg",
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.IO
             "TempSBE"
         };
 
-        private readonly IReadOnlyList<string> _alwaysIgnoreSubstrings = new List<string>
+        private readonly string[] _alwaysIgnoreSubstrings = new string[]
         {
             // Synology
             "eaDir",
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.IO
             ".actors"
         };
 
-        private readonly IReadOnlyList<string> _alwaysIgnoreExtensions = new List<string>
+        private readonly string[] _alwaysIgnoreExtensions = new string[]
         {
             // thumbs.db
             ".db",
@@ -85,6 +85,8 @@ namespace Emby.Server.Implementations.IO
 
         public bool IsPathLocked(string path)
         {
+            // This method is not used by the core but it used by auto-organize
+
             var lockedPaths = _tempIgnoredPaths.Keys.ToList();
             return lockedPaths.Any(i => _fileSystem.AreEqual(i, path) || _fileSystem.ContainsSubPath(i, path));
         }

+ 27 - 30
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -353,7 +353,7 @@ namespace Emby.Server.Implementations.Library
             }
             else
             {
-                if (item is Photo)
+                if (!(item is Video))
                 {
                     return;
                 }
@@ -461,10 +461,10 @@ namespace Emby.Server.Implementations.Library
                 parent.RemoveChild(item);
             }
 
-            await ItemRepository.DeleteItem(item.Id, CancellationToken.None).ConfigureAwait(false);
+            ItemRepository.DeleteItem(item.Id, CancellationToken.None);
             foreach (var child in children)
             {
-                await ItemRepository.DeleteItem(child.Id, CancellationToken.None).ConfigureAwait(false);
+                ItemRepository.DeleteItem(child.Id, CancellationToken.None);
             }
 
             BaseItem removed;
@@ -599,18 +599,16 @@ namespace Emby.Server.Implementations.Library
                 // When resolving the root, we need it's grandchildren (children of user views)
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
 
-                var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+                var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
 
                 // Need to remove subpaths that may have been resolved from shortcuts
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
                 if (isPhysicalRoot)
                 {
-                    var paths = NormalizeRootPathList(fileSystemDictionary.Values);
-
-                    fileSystemDictionary = paths.ToDictionary(i => i.FullName);
+                    files = NormalizeRootPathList(files).ToArray();
                 }
 
-                args.FileSystemDictionary = fileSystemDictionary;
+                args.FileSystemChildren = files;
             }
 
             // Check to see if we should resolve based on our contents
@@ -656,7 +654,7 @@ namespace Emby.Server.Implementations.Library
             return false;
         }
 
-        public IEnumerable<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
+        public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
         {
             var originalList = paths.ToList();
 
@@ -999,8 +997,7 @@ namespace Emby.Server.Implementations.Library
                     Path = path
                 };
 
-                var task = CreateItem(item, CancellationToken.None);
-                Task.WaitAll(task);
+                CreateItem(item, CancellationToken.None);
             }
 
             return item;
@@ -1172,7 +1169,7 @@ namespace Emby.Server.Implementations.Library
                 progress.Report(percent * 100);
             }
 
-            await ItemRepository.UpdateInheritedValues(cancellationToken).ConfigureAwait(false);
+            ItemRepository.UpdateInheritedValues(cancellationToken);
 
             progress.Report(100);
         }
@@ -1208,7 +1205,7 @@ namespace Emby.Server.Implementations.Library
                 .Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase))
                     .Select(_fileSystem.ResolveShortcut)
                     .OrderBy(i => i)
-                    .ToList(),
+                    .ToArray(),
 
                 CollectionType = GetCollectionType(dir)
             };
@@ -1554,7 +1551,7 @@ namespace Emby.Server.Implementations.Library
                     IncludeHidden = true,
                     IncludeExternalContent = allowExternalContent
 
-                }, CancellationToken.None).Result.ToList();
+                }, CancellationToken.None).Result;
 
                 query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).Select(i => i.ToString("N")).ToArray();
             }
@@ -1814,9 +1811,9 @@ namespace Emby.Server.Implementations.Library
         /// <param name="item">The item.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        public Task CreateItem(BaseItem item, CancellationToken cancellationToken)
+        public void CreateItem(BaseItem item, CancellationToken cancellationToken)
         {
-            return CreateItems(new[] { item }, cancellationToken);
+            CreateItems(new[] { item }, cancellationToken);
         }
 
         /// <summary>
@@ -1825,11 +1822,11 @@ namespace Emby.Server.Implementations.Library
         /// <param name="items">The items.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        public async Task CreateItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
+        public void CreateItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
         {
             var list = items.ToList();
 
-            await ItemRepository.SaveItems(list, cancellationToken).ConfigureAwait(false);
+            ItemRepository.SaveItems(list, cancellationToken);
 
             foreach (var item in list)
             {
@@ -1872,7 +1869,7 @@ namespace Emby.Server.Implementations.Library
             var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name;
             _logger.Debug("Saving {0} to database.", logName);
 
-            await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
+            ItemRepository.SaveItem(item, cancellationToken);
 
             RegisterItem(item);
 
@@ -2069,7 +2066,7 @@ namespace Emby.Server.Implementations.Library
         private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
         //private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1);
 
-        public Task<UserView> GetNamedView(User user,
+        public UserView GetNamedView(User user,
             string name,
             string viewType,
             string sortName,
@@ -2107,7 +2104,7 @@ namespace Emby.Server.Implementations.Library
                     ForcedSortName = sortName
                 };
 
-                await CreateItem(item, cancellationToken).ConfigureAwait(false);
+                CreateItem(item, cancellationToken);
 
                 refresh = true;
             }
@@ -2138,7 +2135,7 @@ namespace Emby.Server.Implementations.Library
             return item;
         }
 
-        public async Task<UserView> GetNamedView(User user,
+        public UserView GetNamedView(User user,
             string name,
             string parentId,
             string viewType,
@@ -2175,7 +2172,7 @@ namespace Emby.Server.Implementations.Library
                     item.DisplayParentId = new Guid(parentId);
                 }
 
-                await CreateItem(item, cancellationToken).ConfigureAwait(false);
+                CreateItem(item, cancellationToken);
 
                 isNew = true;
             }
@@ -2201,7 +2198,7 @@ namespace Emby.Server.Implementations.Library
             return item;
         }
 
-        public async Task<UserView> GetShadowView(BaseItem parent,
+        public UserView GetShadowView(BaseItem parent,
         string viewType,
         string sortName,
         CancellationToken cancellationToken)
@@ -2240,7 +2237,7 @@ namespace Emby.Server.Implementations.Library
 
                 item.DisplayParentId = parentId;
 
-                await CreateItem(item, cancellationToken).ConfigureAwait(false);
+                CreateItem(item, cancellationToken);
 
                 isNew = true;
             }
@@ -2311,7 +2308,7 @@ namespace Emby.Server.Implementations.Library
                     item.DisplayParentId = new Guid(parentId);
                 }
 
-                await CreateItem(item, cancellationToken).ConfigureAwait(false);
+                CreateItem(item, cancellationToken);
 
                 isNew = true;
             }
@@ -2825,14 +2822,14 @@ namespace Emby.Server.Implementations.Library
             return ItemRepository.GetPeopleNames(query);
         }
 
-        public Task UpdatePeople(BaseItem item, List<PersonInfo> people)
+        public void UpdatePeople(BaseItem item, List<PersonInfo> people)
         {
             if (!item.SupportsPeople)
             {
-                return Task.FromResult(true);
+                return;
             }
 
-            return ItemRepository.UpdatePeople(item.Id, people);
+            ItemRepository.UpdatePeople(item.Id, people);
         }
 
         public async Task<ItemImageInfo> ConvertImageToLocal(IHasMetadata item, ItemImageInfo image, int imageIndex)
@@ -3061,7 +3058,7 @@ namespace Emby.Server.Implementations.Library
             var topLibraryFolders = GetUserRootFolder().Children.ToList();
             var info = GetVirtualFolderInfo(virtualFolderPath, topLibraryFolders, null);
 
-            if (info.Locations.Count > 0 && info.Locations.Count != options.PathInfos.Length)
+            if (info.Locations.Length > 0 && info.Locations.Length != options.PathInfos.Length)
             {
                 var list = options.PathInfos.ToList();
 

+ 1 - 1
Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs

@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
                     return new CollectionFolder
                     {
                         CollectionType = GetCollectionType(args),
-                        PhysicalLocationsList = args.PhysicalLocations.ToList()
+                        PhysicalLocationsList = args.PhysicalLocations
                     };
                 }
             }

+ 13 - 4
Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs

@@ -1,6 +1,8 @@
-using MediaBrowser.Controller.Configuration;
+using System.Globalization;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Globalization;
 using MediaBrowser.Naming.Common;
 using MediaBrowser.Naming.TV;
 
@@ -17,15 +19,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
         private readonly IServerConfigurationManager _config;
 
         private readonly ILibraryManager _libraryManager;
+        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+        private readonly ILocalizationManager _localization;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SeasonResolver"/> class.
         /// </summary>
         /// <param name="config">The config.</param>
-        public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager)
+        public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization)
         {
             _config = config;
             _libraryManager = libraryManager;
+            _localization = localization;
         }
 
         /// <summary>
@@ -47,9 +52,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
                     SeriesName = series.Name
                 };
 
-                if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
+                if (season.IndexNumber.HasValue)
                 {
-                    season.Name = _config.Configuration.SeasonZeroDisplayName;
+                    var seasonNumber = season.IndexNumber.Value;
+
+                    season.Name = seasonNumber == 0 ?
+                        _config.Configuration.SeasonZeroDisplayName :
+                        string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture));
                 }
 
                 return season;

+ 1 - 1
Emby.Server.Implementations/Library/SearchEngine.cs

@@ -181,7 +181,7 @@ namespace Emby.Server.Implementations.Library
 
                 DtoOptions = new DtoOptions
                 {
-                    Fields = new List<ItemFields>
+                    Fields = new ItemFields[]
                     {
                          ItemFields.AirTime,
                          ItemFields.DateCreated,

+ 7 - 7
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Library
         /// <value>The repository.</value>
         public IUserDataRepository Repository { get; set; }
 
-        public async Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
+        public void SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
         {
             if (userData == null)
             {
@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Library
 
             foreach (var key in keys)
             {
-                await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
+                Repository.SaveUserData(userId, key, userData, cancellationToken);
             }
 
             var cacheKey = GetCacheKey(userId, item.Id);
@@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.Library
         /// <param name="userData"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task SaveAllUserData(Guid userId, IEnumerable<UserItemData> userData, CancellationToken cancellationToken)
+        public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
         {
             if (userData == null)
             {
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.Library
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
+            Repository.SaveAllUserData(userId, userData, cancellationToken);
         }
 
         /// <summary>
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library
         /// </summary>
         /// <param name="userId"></param>
         /// <returns></returns>
-        public IEnumerable<UserItemData> GetAllUserData(Guid userId)
+        public List<UserItemData> GetAllUserData(Guid userId)
         {
             if (userId == Guid.Empty)
             {
@@ -187,11 +187,11 @@ namespace Emby.Server.Implementations.Library
             var userData = GetUserData(user.Id, item);
             var dto = GetUserItemDataDto(userData);
 
-            item.FillUserDataDtoValues(dto, userData, null, user, new List<ItemFields>());
+            item.FillUserDataDtoValues(dto, userData, null, user, new ItemFields[] { });
             return dto;
         }
 
-        public UserItemDataDto GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields)
+        public UserItemDataDto GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, ItemFields[] fields)
         {
             var userData = GetUserData(user.Id, item);
             var dto = GetUserItemDataDto(userData);

+ 34 - 34
Emby.Server.Implementations/Library/UserManager.cs

@@ -160,9 +160,9 @@ namespace Emby.Server.Implementations.Library
             return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase));
         }
 
-        public async Task Initialize()
+        public void Initialize()
         {
-            Users = await LoadUsers().ConfigureAwait(false);
+            Users = LoadUsers();
 
             var users = Users.ToList();
 
@@ -174,7 +174,7 @@ namespace Emby.Server.Implementations.Library
                     if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value == UserLinkType.LinkedUser)
                     {
                         user.Policy.IsAdministrator = true;
-                        await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
+                        UpdateUserPolicy(user, user.Policy, false);
                     }
                 }
             }
@@ -294,12 +294,12 @@ namespace Emby.Server.Implementations.Library
             if (success)
             {
                 user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
-                await UpdateUser(user).ConfigureAwait(false);
-                await UpdateInvalidLoginAttemptCount(user, 0).ConfigureAwait(false);
+                UpdateUser(user);
+                UpdateInvalidLoginAttemptCount(user, 0);
             }
             else
             {
-                await UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1).ConfigureAwait(false);
+                UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1);
             }
 
             _logger.Info("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
@@ -307,7 +307,7 @@ namespace Emby.Server.Implementations.Library
             return success ? user : null;
         }
 
-        private async Task UpdateInvalidLoginAttemptCount(User user, int newValue)
+        private void UpdateInvalidLoginAttemptCount(User user, int newValue)
         {
             if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0)
             {
@@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.Library
                     //fireLockout = true;
                 }
 
-                await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
+                UpdateUserPolicy(user, user.Policy, false);
 
                 if (fireLockout)
                 {
@@ -372,7 +372,7 @@ namespace Emby.Server.Implementations.Library
         /// Loads the users from the repository
         /// </summary>
         /// <returns>IEnumerable{User}.</returns>
-        private async Task<IEnumerable<User>> LoadUsers()
+        private List<User> LoadUsers()
         {
             var users = UserRepository.RetrieveAllUsers().ToList();
 
@@ -385,14 +385,14 @@ namespace Emby.Server.Implementations.Library
 
                 user.DateLastSaved = DateTime.UtcNow;
 
-                await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
+                UserRepository.SaveUser(user, CancellationToken.None);
 
                 users.Add(user);
 
                 user.Policy.IsAdministrator = true;
                 user.Policy.EnableContentDeletion = true;
                 user.Policy.EnableRemoteControlOfOtherUsers = true;
-                await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
+                UpdateUserPolicy(user, user.Policy, false);
             }
 
             return users;
@@ -539,7 +539,7 @@ namespace Emby.Server.Implementations.Library
         /// <param name="user">The user.</param>
         /// <exception cref="System.ArgumentNullException">user</exception>
         /// <exception cref="System.ArgumentException"></exception>
-        public async Task UpdateUser(User user)
+        public void UpdateUser(User user)
         {
             if (user == null)
             {
@@ -554,7 +554,7 @@ namespace Emby.Server.Implementations.Library
             user.DateModified = DateTime.UtcNow;
             user.DateLastSaved = DateTime.UtcNow;
 
-            await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
+            UserRepository.SaveUser(user, CancellationToken.None);
 
             OnUserUpdated(user);
         }
@@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Library
 
                 user.DateLastSaved = DateTime.UtcNow;
 
-                await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
+                UserRepository.SaveUser(user, CancellationToken.None);
 
                 EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
 
@@ -653,7 +653,7 @@ namespace Emby.Server.Implementations.Library
             {
                 var configPath = GetConfigurationFilePath(user);
 
-                await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false);
+                UserRepository.DeleteUser(user, CancellationToken.None);
 
                 try
                 {
@@ -667,7 +667,7 @@ namespace Emby.Server.Implementations.Library
                 DeleteUserPolicy(user);
 
                 // Force this to be lazy loaded again
-                Users = await LoadUsers().ConfigureAwait(false);
+                Users = LoadUsers();
 
                 OnUserDeleted(user);
             }
@@ -681,17 +681,17 @@ namespace Emby.Server.Implementations.Library
         /// Resets the password by clearing it.
         /// </summary>
         /// <returns>Task.</returns>
-        public Task ResetPassword(User user)
+        public void ResetPassword(User user)
         {
-            return ChangePassword(user, GetSha1String(string.Empty));
+            ChangePassword(user, GetSha1String(string.Empty));
         }
 
-        public Task ResetEasyPassword(User user)
+        public void ResetEasyPassword(User user)
         {
-            return ChangeEasyPassword(user, GetSha1String(string.Empty));
+            ChangeEasyPassword(user, GetSha1String(string.Empty));
         }
 
-        public async Task ChangePassword(User user, string newPasswordSha1)
+        public void ChangePassword(User user, string newPasswordSha1)
         {
             if (user == null)
             {
@@ -709,12 +709,12 @@ namespace Emby.Server.Implementations.Library
 
             user.Password = newPasswordSha1;
 
-            await UpdateUser(user).ConfigureAwait(false);
+            UpdateUser(user);
 
             EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger);
         }
 
-        public async Task ChangeEasyPassword(User user, string newPasswordSha1)
+        public void ChangeEasyPassword(User user, string newPasswordSha1)
         {
             if (user == null)
             {
@@ -727,7 +727,7 @@ namespace Emby.Server.Implementations.Library
 
             user.EasyPassword = newPasswordSha1;
 
-            await UpdateUser(user).ConfigureAwait(false);
+            UpdateUser(user);
 
             EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger);
         }
@@ -842,7 +842,7 @@ namespace Emby.Server.Implementations.Library
             };
         }
 
-        public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+        public PinRedeemResult RedeemPasswordResetPin(string pin)
         {
             DeletePinFile();
 
@@ -863,12 +863,12 @@ namespace Emby.Server.Implementations.Library
 
                 foreach (var user in users)
                 {
-                    await ResetPassword(user).ConfigureAwait(false);
+                    ResetPassword(user);
 
                     if (user.Policy.IsDisabled)
                     {
                         user.Policy.IsDisabled = false;
-                        await UpdateUserPolicy(user, user.Policy, true).ConfigureAwait(false);
+                        UpdateUserPolicy(user, user.Policy, true);
                     }
                     usersReset.Add(user.Name);
                 }
@@ -945,13 +945,13 @@ namespace Emby.Server.Implementations.Library
         }
 
         private readonly object _policySyncLock = new object();
-        public Task UpdateUserPolicy(string userId, UserPolicy userPolicy)
+        public void UpdateUserPolicy(string userId, UserPolicy userPolicy)
         {
             var user = GetUserById(userId);
-            return UpdateUserPolicy(user, userPolicy, true);
+            UpdateUserPolicy(user, userPolicy, true);
         }
 
-        private async Task UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent)
+        private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent)
         {
             // The xml serializer will output differently if the type is not exact
             if (userPolicy.GetType() != typeof(UserPolicy))
@@ -970,7 +970,7 @@ namespace Emby.Server.Implementations.Library
                 user.Policy = userPolicy;
             }
 
-            await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
+            UpdateConfiguration(user, user.Configuration, true);
         }
 
         private void DeleteUserPolicy(User user)
@@ -1032,13 +1032,13 @@ namespace Emby.Server.Implementations.Library
         }
 
         private readonly object _configSyncLock = new object();
-        public Task UpdateConfiguration(string userId, UserConfiguration config)
+        public void UpdateConfiguration(string userId, UserConfiguration config)
         {
             var user = GetUserById(userId);
-            return UpdateConfiguration(user, config, true);
+            UpdateConfiguration(user, config, true);
         }
 
-        private async Task UpdateConfiguration(User user, UserConfiguration config, bool fireEvent)
+        private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent)
         {
             var path = GetConfigurationFilePath(user);
 

+ 11 - 10
Emby.Server.Implementations/Library/UserViewManager.cs

@@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Library
             _config = config;
         }
 
-        public async Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken)
+        public async Task<Folder[]> GetUserViews(UserViewQuery query, CancellationToken cancellationToken)
         {
             var user = _userManager.GetUserById(query.UserId);
 
@@ -68,7 +68,7 @@ namespace Emby.Server.Implementations.Library
 
                 if (UserView.IsUserSpecific(folder))
                 {
-                    list.Add(await _libraryManager.GetNamedView(user, folder.Name, folder.Id.ToString("N"), folderViewType, null, cancellationToken).ConfigureAwait(false));
+                    list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id.ToString("N"), folderViewType, null, cancellationToken));
                     continue;
                 }
 
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library
 
                 if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                 {
-                    list.Add(await GetUserView(folder, folderViewType, string.Empty, cancellationToken).ConfigureAwait(false));
+                    list.Add(GetUserView(folder, folderViewType, string.Empty, cancellationToken));
                 }
                 else
                 {
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library
 
                 if (parents.Count > 0)
                 {
-                    list.Add(await GetUserView(parents, viewType, string.Empty, user, query.PresetViews, cancellationToken).ConfigureAwait(false));
+                    list.Add(GetUserView(parents, viewType, string.Empty, user, query.PresetViews, cancellationToken));
                 }
             }
 
@@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.Library
                 }, cancellationToken).ConfigureAwait(false);
 
                 var channels = channelResult.Items;
-                
+
                 if (_config.Configuration.EnableChannelView && channels.Length > 0)
                 {
                     list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false));
@@ -154,7 +154,8 @@ namespace Emby.Server.Implementations.Library
                     return index == -1 ? int.MaxValue : index;
                 })
                 .ThenBy(sorted.IndexOf)
-                .ThenBy(i => i.SortName);
+                .ThenBy(i => i.SortName)
+                .ToArray();
         }
 
         public Task<UserView> GetUserSubView(string name, string parentId, string type, string sortName, CancellationToken cancellationToken)
@@ -171,7 +172,7 @@ namespace Emby.Server.Implementations.Library
             return GetUserSubView(name, parentId, type, sortName, cancellationToken);
         }
 
-        private async Task<Folder> GetUserView(List<ICollectionFolder> parents, string viewType, string sortName, User user, string[] presetViews, CancellationToken cancellationToken)
+        private Folder GetUserView(List<ICollectionFolder> parents, string viewType, string sortName, User user, string[] presetViews, CancellationToken cancellationToken)
         {
             if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
             {
@@ -180,14 +181,14 @@ namespace Emby.Server.Implementations.Library
                     return (Folder)parents[0];
                 }
 
-                return await GetUserView((Folder)parents[0], viewType, string.Empty, cancellationToken).ConfigureAwait(false);
+                return GetUserView((Folder)parents[0], viewType, string.Empty, cancellationToken);
             }
 
             var name = _localizationManager.GetLocalizedString("ViewType" + viewType);
-            return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
+            return _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken);
         }
 
-        public Task<UserView> GetUserView(Folder parent, string viewType, string sortName, CancellationToken cancellationToken)
+        public UserView GetUserView(Folder parent, string viewType, string sortName, CancellationToken cancellationToken)
         {
             return _libraryManager.GetShadowView(parent, viewType, sortName, cancellationToken);
         }

+ 105 - 60
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -208,7 +208,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     continue;
                 }
 
-                if (virtualFolder.Locations.Count == 1)
+                if (virtualFolder.Locations.Length == 1)
                 {
                     // remove entire virtual folder
                     try
@@ -458,7 +458,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
         }
 
-        private string GetMappedChannel(string channelId, List<NameValuePair> mappings)
+        private string GetMappedChannel(string channelId, NameValuePair[] mappings)
         {
             foreach (NameValuePair mapping in mappings)
             {
@@ -472,10 +472,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
         {
-            return GetEpgChannelFromTunerChannel(info.ChannelMappings.ToList(), tunerChannel, epgChannels);
+            return GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
         }
 
-        public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
+        public ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
         {
             if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
             {
@@ -607,20 +607,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             var timer = _timerProvider.GetTimer(timerId);
             if (timer != null)
             {
+                timer.Status = RecordingStatus.Cancelled;
+
                 if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled)
                 {
                     _timerProvider.Delete(timer);
                 }
                 else
                 {
-                    timer.Status = RecordingStatus.Cancelled;
                     _timerProvider.AddOrUpdate(timer, false);
                 }
             }
             ActiveRecordingInfo activeRecordingInfo;
 
             if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo))
-            {
+            { 
+                activeRecordingInfo.Timer = timer;
                 activeRecordingInfo.CancellationTokenSource.Cancel();
             }
         }
@@ -830,6 +832,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             existingTimer.IsKids = updatedTimer.IsKids;
             existingTimer.IsNews = updatedTimer.IsNews;
             existingTimer.IsMovie = updatedTimer.IsMovie;
+            existingTimer.IsSeries = updatedTimer.IsSeries;
+            existingTimer.IsLive = updatedTimer.IsLive;
+            existingTimer.IsPremiere = updatedTimer.IsPremiere;
             existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries;
             existingTimer.IsRepeat = updatedTimer.IsRepeat;
             existingTimer.IsSports = updatedTimer.IsSports;
@@ -861,7 +866,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
         {
-            return _activeRecordings.Values.ToList().Select(GetRecordingInfo).ToList();
+            return new List<RecordingInfo>();
         }
 
         public string GetActiveRecordingPath(string id)
@@ -875,49 +880,31 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return null;
         }
 
-        private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info)
-        {
-            var timer = info.Timer;
-            var program = info.Program;
-
-            var result = new RecordingInfo
-            {
-                ChannelId = timer.ChannelId,
-                CommunityRating = timer.CommunityRating,
-                DateLastUpdated = DateTime.UtcNow,
-                EndDate = timer.EndDate,
-                EpisodeTitle = timer.EpisodeTitle,
-                Genres = timer.Genres,
-                Id = "recording" + timer.Id,
-                IsKids = timer.IsKids,
-                IsMovie = timer.IsMovie,
-                IsNews = timer.IsNews,
-                IsRepeat = timer.IsRepeat,
-                IsSeries = timer.IsProgramSeries,
-                IsSports = timer.IsSports,
-                Name = timer.Name,
-                OfficialRating = timer.OfficialRating,
-                OriginalAirDate = timer.OriginalAirDate,
-                Overview = timer.Overview,
-                ProgramId = timer.ProgramId,
-                SeriesTimerId = timer.SeriesTimerId,
-                StartDate = timer.StartDate,
-                Status = RecordingStatus.InProgress,
-                TimerId = timer.Id
-            };
+        public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
+        {
+            return _activeRecordings.Values.Where(i => i.Timer.Status == RecordingStatus.InProgress && !i.CancellationTokenSource.IsCancellationRequested);
+        }
 
-            if (program != null)
+        public ActiveRecordingInfo GetActiveRecordingInfo(string path)
+        {
+            if (string.IsNullOrWhiteSpace(path))
             {
-                result.Audio = program.Audio;
-                result.ImagePath = program.ImagePath;
-                result.ImageUrl = program.ImageUrl;
-                result.IsHD = program.IsHD;
-                result.IsLive = program.IsLive;
-                result.IsPremiere = program.IsPremiere;
-                result.ShowId = program.ShowId;
+                return null;
             }
 
-            return result;
+            foreach (var recording in _activeRecordings.Values)
+            {
+                if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested)
+                {
+                    var timer = recording.Timer;
+                    if (timer.Status != RecordingStatus.InProgress)
+                    {
+                        return null;
+                    }
+                    return recording;
+                }
+            }
+            return null;
         }
 
         public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@@ -1245,6 +1232,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             throw new FileNotFoundException();
         }
 
+        public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
+        {
+            var stream = new MediaSourceInfo
+            {
+                Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+                Id = info.Id,
+                SupportsDirectPlay = false,
+                SupportsDirectStream = true,
+                SupportsTranscoding = true,
+                IsInfiniteStream = true,
+                RequiresOpening = false,
+                RequiresClosing = false,
+                Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
+                BufferMs = 0,
+                IgnoreDts = true,
+                IgnoreIndex = true
+            };
+
+            var isAudio = false;
+            await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
+
+            return new List<MediaSourceInfo>
+            {
+                stream
+            };
+        }
+
         public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
         {
             // Ignore the consumer id
@@ -1327,7 +1341,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 var activeRecordingInfo = new ActiveRecordingInfo
                 {
                     CancellationTokenSource = new CancellationTokenSource(),
-                    Timer = timer
+                    Timer = timer,
+                    Id = timer.Id
                 };
 
                 if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo))
@@ -1493,7 +1508,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
                 recordPath = EnsureFileUnique(recordPath, timer.Id);
 
-                _libraryManager.RegisterIgnoredPath(recordPath);
                 _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
                 _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath));
                 activeRecordingInfo.Path = recordPath;
@@ -1512,6 +1526,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     _timerProvider.AddOrUpdate(timer, false);
 
                     SaveRecordingMetadata(timer, recordPath, seriesPath);
+                    TriggerRefresh(recordPath);
                     EnforceKeepUpTo(timer, seriesPath);
                 };
 
@@ -1543,7 +1558,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 }
             }
 
-            _libraryManager.UnRegisterIgnoredPath(recordPath);
+            TriggerRefresh(recordPath);
             _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
 
             ActiveRecordingInfo removed;
@@ -1574,6 +1589,44 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             OnRecordingStatusChanged();
         }
 
+        private void TriggerRefresh(string path)
+        {
+            var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path));
+
+            if (item != null)
+            {
+                item.ChangedExternally();
+            }
+        }
+
+        private BaseItem GetAffectedBaseItem(string path)
+        {
+            BaseItem item = null;
+
+            while (item == null && !string.IsNullOrEmpty(path))
+            {
+                item = _libraryManager.FindByPath(path, null);
+
+                path = _fileSystem.GetDirectoryName(path);
+            }
+
+            if (item != null)
+            {
+                // If the item has been deleted find the first valid parent that still exists
+                while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
+                {
+                    item = item.GetParent();
+
+                    if (item == null)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return item;
+        }
+
         private void OnRecordingStatusChanged()
         {
             EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs
@@ -2591,7 +2644,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
                 list.Add(new VirtualFolderInfo
                 {
-                    Locations = new List<string> { defaultFolder },
+                    Locations = new string[] { defaultFolder },
                     Name = defaultName
                 });
             }
@@ -2601,7 +2654,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
                 list.Add(new VirtualFolderInfo
                 {
-                    Locations = new List<string> { customPath },
+                    Locations = new string[] { customPath },
                     Name = "Recorded Movies",
                     CollectionType = CollectionType.Movies
                 });
@@ -2612,7 +2665,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
                 list.Add(new VirtualFolderInfo
                 {
-                    Locations = new List<string> { customPath },
+                    Locations = new string[] { customPath },
                     Name = "Recorded Shows",
                     CollectionType = CollectionType.TvShows
                 });
@@ -2621,14 +2674,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return list;
         }
 
-        class ActiveRecordingInfo
-        {
-            public string Path { get; set; }
-            public TimerInfo Timer { get; set; }
-            public ProgramInfo Program { get; set; }
-            public CancellationTokenSource CancellationTokenSource { get; set; }
-        }
-
         private const int TunerDiscoveryDurationMs = 3000;
 
         public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)

+ 4 - 0
Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs

@@ -58,6 +58,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             timerInfo.OriginalAirDate = programInfo.OriginalAirDate;
             timerInfo.IsProgramSeries = programInfo.IsSeries;
 
+            timerInfo.IsSeries = programInfo.IsSeries;
+            timerInfo.IsLive = programInfo.IsLive;
+            timerInfo.IsPremiere = programInfo.IsPremiere;
+
             timerInfo.HomePageUrl = programInfo.HomePageUrl;
             timerInfo.CommunityRating = programInfo.CommunityRating;
             timerInfo.Overview = programInfo.Overview;

+ 21 - 10
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -247,7 +247,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             ProgramAudio audioType = ProgramAudio.Stereo;
 
             bool repeat = programInfo.@new == null;
-            string newID = programInfo.programID + "T" + startAt.Ticks + "C" + channelId;
+
+            var programId = programInfo.programID ?? string.Empty;
+
+            string newID = programId + "T" + startAt.Ticks + "C" + channelId;
 
             if (programInfo.audioProperties != null)
             {
@@ -300,7 +303,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 Etag = programInfo.md5
             };
 
-            var showId = programInfo.programID ?? string.Empty;
+            var showId = programId;
 
             if (!info.IsSeries)
             {
@@ -339,11 +342,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
             if (details.descriptions != null)
             {
-                if (details.descriptions.description1000 != null)
+                if (details.descriptions.description1000 != null && details.descriptions.description1000.Count > 0)
                 {
                     info.Overview = details.descriptions.description1000[0].description;
                 }
-                else if (details.descriptions.description100 != null)
+                else if (details.descriptions.description100 != null && details.descriptions.description100.Count > 0)
                 {
                     info.Overview = details.descriptions.description100[0].description;
                 }
@@ -351,16 +354,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
             if (info.IsSeries)
             {
-                info.SeriesId = programInfo.programID.Substring(0, 10);
+                info.SeriesId = programId.Substring(0, 10);
 
                 if (details.metadata != null)
                 {
-                    var gracenote = details.metadata.Find(x => x.Gracenote != null).Gracenote;
-                    info.SeasonNumber = gracenote.season;
-
-                    if (gracenote.episode > 0)
+                    foreach (var metadataProgram in details.metadata)
                     {
-                        info.EpisodeNumber = gracenote.episode;
+                        var gracenote = metadataProgram.Gracenote;
+                        if (gracenote != null)
+                        {
+                            info.SeasonNumber = gracenote.season;
+
+                            if (gracenote.episode > 0)
+                            {
+                                info.EpisodeNumber = gracenote.episode;
+                            }
+
+                            break;
+                        }
                     }
                 }
             }

+ 19 - 32
Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -15,6 +15,8 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Querying;
 
 namespace Emby.Server.Implementations.LiveTv
 {
@@ -110,7 +112,7 @@ namespace Emby.Server.Implementations.LiveTv
                 PostPaddingSeconds = info.PostPaddingSeconds,
                 IsPostPaddingRequired = info.IsPostPaddingRequired,
                 IsPrePaddingRequired = info.IsPrePaddingRequired,
-                Days = info.Days,
+                Days = info.Days.ToArray(),
                 Priority = info.Priority,
                 RecordAnyChannel = info.RecordAnyChannel,
                 RecordAnyTime = info.RecordAnyTime,
@@ -135,7 +137,7 @@ namespace Emby.Server.Implementations.LiveTv
                 dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
             }
 
-            dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
+            dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray(info.Days.Count));
 
             FillImages(dto, info.Name, info.SeriesId);
 
@@ -150,10 +152,7 @@ namespace Emby.Server.Implementations.LiveTv
                 Name = seriesName,
                 Limit = 1,
                 ImageTypes = new ImageType[] { ImageType.Thumb },
-                DtoOptions = new DtoOptions
-                {
-                    Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
-                }
+                DtoOptions = new DtoOptions(false)
 
             }).FirstOrDefault();
 
@@ -196,10 +195,7 @@ namespace Emby.Server.Implementations.LiveTv
                     ExternalSeriesId = programSeriesId,
                     Limit = 1,
                     ImageTypes = new ImageType[] { ImageType.Primary },
-                    DtoOptions = new DtoOptions
-                    {
-                        Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
-                    }
+                    DtoOptions = new DtoOptions(false)
 
                 }).FirstOrDefault();
 
@@ -248,10 +244,7 @@ namespace Emby.Server.Implementations.LiveTv
                 Name = seriesName,
                 Limit = 1,
                 ImageTypes = new ImageType[] { ImageType.Thumb },
-                DtoOptions = new DtoOptions
-                {
-                    Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
-                }
+                DtoOptions = new DtoOptions(false)
 
             }).FirstOrDefault();
 
@@ -274,7 +267,7 @@ namespace Emby.Server.Implementations.LiveTv
                 {
                     try
                     {
-                        dto.ParentBackdropImageTags = new List<string>
+                        dto.ParentBackdropImageTags = new string[]
                             {
                                 _imageProcessor.GetImageCacheTag(librarySeries, image)
                             };
@@ -294,10 +287,7 @@ namespace Emby.Server.Implementations.LiveTv
                     Name = seriesName,
                     Limit = 1,
                     ImageTypes = new ImageType[] { ImageType.Primary },
-                    DtoOptions = new DtoOptions
-                    {
-                        Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
-                    }
+                    DtoOptions = new DtoOptions(false)
 
                 }).FirstOrDefault() ?? _libraryManager.GetItemList(new InternalItemsQuery
                 {
@@ -305,10 +295,7 @@ namespace Emby.Server.Implementations.LiveTv
                     ExternalSeriesId = programSeriesId,
                     Limit = 1,
                     ImageTypes = new ImageType[] { ImageType.Primary },
-                    DtoOptions = new DtoOptions
-                    {
-                        Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
-                    }
+                    DtoOptions = new DtoOptions(false)
 
                 }).FirstOrDefault();
 
@@ -327,14 +314,14 @@ namespace Emby.Server.Implementations.LiveTv
                         }
                     }
 
-                    if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Count == 0)
+                    if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
                     {
                         image = program.GetImageInfo(ImageType.Backdrop, 0);
                         if (image != null)
                         {
                             try
                             {
-                                dto.ParentBackdropImageTags = new List<string>
+                                dto.ParentBackdropImageTags = new string[]
                             {
                                 _imageProcessor.GetImageCacheTag(program, image)
                             };
@@ -349,24 +336,24 @@ namespace Emby.Server.Implementations.LiveTv
             }
         }
 
-        public DayPattern? GetDayPattern(List<DayOfWeek> days)
+        public DayPattern? GetDayPattern(DayOfWeek[] days)
         {
             DayPattern? pattern = null;
 
-            if (days.Count > 0)
+            if (days.Length > 0)
             {
-                if (days.Count == 7)
+                if (days.Length == 7)
                 {
                     pattern = DayPattern.Daily;
                 }
-                else if (days.Count == 2)
+                else if (days.Length == 2)
                 {
                     if (days.Contains(DayOfWeek.Saturday) && days.Contains(DayOfWeek.Sunday))
                     {
                         pattern = DayPattern.Weekends;
                     }
                 }
-                else if (days.Count == 5)
+                else if (days.Length == 5)
                 {
                     if (days.Contains(DayOfWeek.Monday) && days.Contains(DayOfWeek.Tuesday) && days.Contains(DayOfWeek.Wednesday) && days.Contains(DayOfWeek.Thursday) && days.Contains(DayOfWeek.Friday))
                     {
@@ -384,7 +371,7 @@ namespace Emby.Server.Implementations.LiveTv
             {
                 Name = info.Name,
                 Id = info.Id,
-                Clients = info.Clients,
+                Clients = info.Clients.ToArray(),
                 ProgramName = info.ProgramName,
                 SourceType = info.SourceType,
                 Status = info.Status,
@@ -543,7 +530,7 @@ namespace Emby.Server.Implementations.LiveTv
                 PostPaddingSeconds = dto.PostPaddingSeconds,
                 IsPostPaddingRequired = dto.IsPostPaddingRequired,
                 IsPrePaddingRequired = dto.IsPrePaddingRequired,
-                Days = dto.Days,
+                Days = dto.Days.ToList(),
                 Priority = dto.Priority,
                 RecordAnyChannel = dto.RecordAnyChannel,
                 RecordAnyTime = dto.RecordAnyTime,

+ 135 - 93
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.LiveTv
 
         private readonly LiveTvDtoService _tvDtoService;
 
-        private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
+        private ILiveTvService[] _services = new ILiveTvService[] { };
 
         private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
 
@@ -124,7 +124,7 @@ namespace Emby.Server.Implementations.LiveTv
         /// <param name="listingProviders">The listing providers.</param>
         public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
         {
-            _services.AddRange(services);
+            _services = services.ToArray();
             _tunerHosts.AddRange(tunerHosts);
             _listingProviders.AddRange(listingProviders);
 
@@ -558,7 +558,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (isNew)
             {
-                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+                _libraryManager.CreateItem(item, cancellationToken);
             }
             else if (forceUpdate)
             {
@@ -875,7 +875,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (isNew)
             {
-                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+                _libraryManager.CreateItem(item, cancellationToken);
             }
             else if (dataChanged || info.DateLastUpdated > recording.DateLastSaved || statusChanged)
             {
@@ -985,9 +985,8 @@ namespace Emby.Server.Implementations.LiveTv
 
             var queryResult = _libraryManager.QueryItems(internalQuery);
 
-            var returnList = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user)
+            var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user)
                 .ConfigureAwait(false));
-            var returnArray = returnList.ToArray(returnList.Count);
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -998,7 +997,7 @@ namespace Emby.Server.Implementations.LiveTv
             return result;
         }
 
-        public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
+        public async Task<QueryResult<BaseItem>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
         {
             var user = _userManager.GetUserById(query.UserId);
 
@@ -1036,10 +1035,10 @@ namespace Emby.Server.Implementations.LiveTv
                 }
             }
 
-            var programList = _libraryManager.QueryItems(internalQuery).Items.Cast<LiveTvProgram>().ToList();
-            var totalCount = programList.Count;
+            var programList = _libraryManager.QueryItems(internalQuery).Items;
+            var totalCount = programList.Length;
 
-            IOrderedEnumerable<LiveTvProgram> orderedPrograms = programList.OrderBy(i => i.StartDate.Date);
+            IOrderedEnumerable<LiveTvProgram> orderedPrograms = programList.Cast<LiveTvProgram>().OrderBy(i => i.StartDate.Date);
 
             if (query.IsAiring ?? false)
             {
@@ -1047,14 +1046,14 @@ namespace Emby.Server.Implementations.LiveTv
                     .ThenByDescending(i => GetRecommendationScore(i, user.Id, true));
             }
 
-            IEnumerable<LiveTvProgram> programs = orderedPrograms;
+            IEnumerable<BaseItem> programs = orderedPrograms;
 
             if (query.Limit.HasValue)
             {
                 programs = programs.Take(query.Limit.Value);
             }
 
-            var result = new QueryResult<LiveTvProgram>
+            var result = new QueryResult<BaseItem>
             {
                 Items = programs.ToArray(),
                 TotalRecordCount = totalCount
@@ -1071,9 +1070,8 @@ namespace Emby.Server.Implementations.LiveTv
 
             var user = _userManager.GetUserById(query.UserId);
 
-            var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+            var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
                 .ConfigureAwait(false));
-            var returnArray = returnList.ToArray(returnList.Count);
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -1223,9 +1221,9 @@ namespace Emby.Server.Implementations.LiveTv
             await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
 
             var numComplete = 0;
-            double progressPerService = _services.Count == 0
+            double progressPerService = _services.Length == 0
                 ? 0
-                : 1 / _services.Count;
+                : 1 / _services.Length;
 
             var newChannelIdList = new List<Guid>();
             var newProgramIdList = new List<Guid>();
@@ -1257,13 +1255,13 @@ namespace Emby.Server.Implementations.LiveTv
 
                 numComplete++;
                 double percent = numComplete;
-                percent /= _services.Count;
+                percent /= _services.Length;
 
                 progress.Report(100 * percent);
             }
 
-            await CleanDatabaseInternal(newChannelIdList, new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
-            await CleanDatabaseInternal(newProgramIdList, new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
+            await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
+            await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
 
             var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
 
@@ -1275,8 +1273,11 @@ namespace Emby.Server.Implementations.LiveTv
 
             // Load these now which will prefetch metadata
             var dtoOptions = new DtoOptions();
-            dtoOptions.Fields.Remove(ItemFields.SyncInfo);
-            dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo);
+            var fields = dtoOptions.Fields.ToList();
+            fields.Remove(ItemFields.SyncInfo);
+            fields.Remove(ItemFields.BasicSyncInfo);
+            dtoOptions.Fields = fields.ToArray(fields.Count);
+
             await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false);
 
             progress.Report(100);
@@ -1409,7 +1410,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                     if (newPrograms.Count > 0)
                     {
-                        await _libraryManager.CreateItems(newPrograms, cancellationToken).ConfigureAwait(false);
+                        _libraryManager.CreateItems(newPrograms, cancellationToken);
                     }
 
                     // TODO: Do this in bulk
@@ -1446,14 +1447,14 @@ namespace Emby.Server.Implementations.LiveTv
             return new Tuple<List<Guid>, List<Guid>>(channels, programs);
         }
 
-        private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
+        private async Task CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
         {
             var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
             {
                 IncludeItemTypes = validTypes,
                 DtoOptions = new DtoOptions(false)
 
-            }).ToList();
+            });
 
             var numComplete = 0;
 
@@ -1543,7 +1544,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                 var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
 
-                await CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
+                await CleanDatabaseInternal(idList, new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
 
                 _lastRecordingRefreshTime = DateTime.UtcNow;
             }
@@ -1560,11 +1561,6 @@ namespace Emby.Server.Implementations.LiveTv
                 return new QueryResult<BaseItem>();
             }
 
-            if ((query.IsInProgress ?? false))
-            {
-                return new QueryResult<BaseItem>();
-            }
-
             var folderIds = EmbyTV.EmbyTV.Current.GetRecordingFolders()
                 .SelectMany(i => i.Locations)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -1576,13 +1572,10 @@ namespace Emby.Server.Implementations.LiveTv
 
             var excludeItemTypes = new List<string>();
 
-            if (!query.IsInProgress.HasValue)
-            {
-                folderIds.Add(internalLiveTvFolderId);
+            folderIds.Add(internalLiveTvFolderId);
 
-                excludeItemTypes.Add(typeof(LiveTvChannel).Name);
-                excludeItemTypes.Add(typeof(LiveTvProgram).Name);
-            }
+            excludeItemTypes.Add(typeof(LiveTvChannel).Name);
+            excludeItemTypes.Add(typeof(LiveTvProgram).Name);
 
             if (folderIds.Count == 0)
             {
@@ -1631,6 +1624,19 @@ namespace Emby.Server.Implementations.LiveTv
                 }
             }
 
+            if ((query.IsInProgress ?? false))
+            {
+                // TODO: filter
+                var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
+                var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
+
+                return new QueryResult<BaseItem>
+                {
+                    Items = items,
+                    TotalRecordCount = items.Length
+                };
+            }
+
             return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
                 MediaTypes = new[] { MediaType.Video },
@@ -1658,11 +1664,6 @@ namespace Emby.Server.Implementations.LiveTv
                 return new QueryResult<BaseItemDto>();
             }
 
-            if (_services.Count > 1)
-            {
-                return new QueryResult<BaseItemDto>();
-            }
-
             if (user == null || (query.IsInProgress ?? false))
             {
                 return new QueryResult<BaseItemDto>();
@@ -1701,11 +1702,9 @@ namespace Emby.Server.Implementations.LiveTv
                 DtoOptions = options
             });
 
-            var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+            var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
                 .ConfigureAwait(false));
 
-            var returnArray = returnList.ToArray(returnList.Count);
-
             return new QueryResult<BaseItemDto>
             {
                 Items = returnArray,
@@ -1723,13 +1722,9 @@ namespace Emby.Server.Implementations.LiveTv
 
             var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
 
-            if (_services.Count == 1 && (!query.IsInProgress.HasValue || !query.IsInProgress.Value) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value))
+            // TODO: Figure out how to merge emby recordings + service recordings
+            if (_services.Length == 1)
             {
-                if (!query.IsInProgress.HasValue)
-                {
-                    await RefreshRecordings(folder.Id, cancellationToken).ConfigureAwait(false);
-                }
-
                 return GetEmbyRecordings(query, options, folder.Id, user);
             }
 
@@ -1841,7 +1836,7 @@ namespace Emby.Server.Implementations.LiveTv
             };
         }
 
-        public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
+        public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, ItemFields[] fields, User user = null)
         {
             var programTuples = new List<Tuple<BaseItemDto, string, string, string>>();
             var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
@@ -1921,6 +1916,11 @@ namespace Emby.Server.Implementations.LiveTv
             await AddRecordingInfo(programTuples, CancellationToken.None).ConfigureAwait(false);
         }
 
+        public ActiveRecordingInfo GetActiveRecordingInfo(string path)
+        {
+            return EmbyTV.EmbyTV.Current.GetActiveRecordingInfo(path);
+        }
+
         public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, User user = null)
         {
             var recording = (ILiveTvRecording)item;
@@ -1950,27 +1950,72 @@ namespace Emby.Server.Implementations.LiveTv
             dto.IsKids = info.IsKids;
             dto.IsPremiere = info.IsPremiere;
 
-            dto.CanDelete = user == null
-                ? recording.CanDelete()
-                : recording.CanDelete(user);
-
-            if (dto.MediaSources == null)
+            if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
             {
-                dto.MediaSources = recording.GetMediaSources(true);
+                var now = DateTime.UtcNow.Ticks;
+                var start = info.StartDate.Ticks;
+                var end = info.EndDate.Value.Ticks;
+
+                var pct = now - start;
+                pct /= end;
+                pct *= 100;
+                dto.CompletionPercentage = pct;
             }
 
-            if (dto.MediaStreams == null)
+            if (channel != null)
             {
-                dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
+                dto.ChannelName = channel.Name;
+
+                if (channel.HasImage(ImageType.Primary))
+                {
+                    dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
+                }
             }
+        }
 
-            if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
+        public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null)
+        {
+            var service = EmbyTV.EmbyTV.Current;
+
+            var info = activeRecordingInfo.Timer;
+
+            var channel = string.IsNullOrWhiteSpace(info.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, info.ChannelId));
+
+            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
+                ? null
+                : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
+
+            dto.TimerId = string.IsNullOrEmpty(info.Id)
+                ? null
+                : _tvDtoService.GetInternalTimerId(service.Name, info.Id).ToString("N");
+
+            var startDate = info.StartDate;
+            var endDate = info.EndDate;
+
+            dto.StartDate = startDate;
+            dto.EndDate = endDate;
+            dto.Status = info.Status.ToString();
+            dto.IsRepeat = info.IsRepeat;
+            dto.EpisodeTitle = info.EpisodeTitle;
+            dto.IsMovie = info.IsMovie;
+            dto.IsSeries = info.IsSeries;
+            dto.IsSports = info.IsSports;
+            dto.IsLive = info.IsLive;
+            dto.IsNews = info.IsNews;
+            dto.IsKids = info.IsKids;
+            dto.IsPremiere = info.IsPremiere;
+
+            if (info.Status == RecordingStatus.InProgress)
             {
+                startDate = info.StartDate.AddSeconds(0 - info.PrePaddingSeconds);
+                endDate = info.EndDate.AddSeconds(info.PostPaddingSeconds);
+
                 var now = DateTime.UtcNow.Ticks;
-                var start = info.StartDate.Ticks;
-                var end = info.EndDate.Value.Ticks;
+                var start = startDate.Ticks;
+                var end = endDate.Ticks;
 
                 var pct = now - start;
+
                 pct /= end;
                 pct *= 100;
                 dto.CompletionPercentage = pct;
@@ -1995,9 +2040,8 @@ namespace Emby.Server.Implementations.LiveTv
 
             var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
 
-            var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
+            var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user)
                 .ConfigureAwait(false));
-            var returnArray = returnList.ToArray(returnList.Count);
 
             return new QueryResult<BaseItemDto>
             {
@@ -2100,7 +2144,6 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (service is EmbyTV.EmbyTV)
             {
-                // We can't trust that we'll be able to direct stream it through emby server,  no matter what the provider says
                 return service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None);
             }
 
@@ -2350,7 +2393,6 @@ namespace Emby.Server.Implementations.LiveTv
             var currentChannelsDict = new Dictionary<string, BaseItemDto>();
 
             var addCurrentProgram = options.AddCurrentProgram;
-            var addMediaSources = options.Fields.Contains(ItemFields.MediaSources);
             var addServiceName = options.Fields.Contains(ItemFields.ServiceName);
 
             foreach (var tuple in tuples)
@@ -2369,11 +2411,6 @@ namespace Emby.Server.Implementations.LiveTv
 
                 currentChannelsDict[dto.Id] = dto;
 
-                if (addMediaSources)
-                {
-                    dto.MediaSources = channel.GetMediaSources(true);
-                }
-
                 if (addCurrentProgram)
                 {
                     var channelIdString = channel.Id.ToString("N");
@@ -2479,7 +2516,7 @@ namespace Emby.Server.Implementations.LiveTv
             var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
             var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
 
-            info.Days = defaults.Item1.Days;
+            info.Days = defaults.Item1.Days.ToArray();
 
             info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
 
@@ -2656,8 +2693,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             var series = recordings
                 .Where(i => i.IsSeries)
-                .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
-                .ToList();
+                .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase);
 
             groups.AddRange(series.OrderByString(i => i.Key).Select(i => new BaseItemDto
             {
@@ -2762,7 +2798,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
         }
 
-        private async Task<IEnumerable<LiveTvServiceInfo>> GetServiceInfos(CancellationToken cancellationToken)
+        private async Task<LiveTvServiceInfo[]> GetServiceInfos(CancellationToken cancellationToken)
         {
             var tasks = Services.Select(i => GetServiceInfo(i, cancellationToken));
 
@@ -2806,7 +2842,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                     return dto;
 
-                }).ToList();
+                }).ToArray();
             }
             catch (Exception ex)
             {
@@ -2822,25 +2858,24 @@ namespace Emby.Server.Implementations.LiveTv
         public async Task<LiveTvInfo> GetLiveTvInfo(CancellationToken cancellationToken)
         {
             var services = await GetServiceInfos(CancellationToken.None).ConfigureAwait(false);
-            var servicesList = services.ToList();
 
             var info = new LiveTvInfo
             {
-                Services = servicesList.ToList(),
-                IsEnabled = servicesList.Count > 0
+                Services = services,
+                IsEnabled = services.Length > 0
             };
 
             info.EnabledUsers = _userManager.Users
                 .Where(IsLiveTvEnabled)
                 .Select(i => i.Id.ToString("N"))
-                .ToList();
+                .ToArray();
 
             return info;
         }
 
         private bool IsLiveTvEnabled(User user)
         {
-            return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0);
+            return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
         }
 
         public IEnumerable<User> GetEnabledUsers()
@@ -2880,10 +2915,13 @@ namespace Emby.Server.Implementations.LiveTv
 
         private void RemoveFields(DtoOptions options)
         {
-            options.Fields.Remove(ItemFields.CanDelete);
-            options.Fields.Remove(ItemFields.CanDownload);
-            options.Fields.Remove(ItemFields.DisplayPreferencesId);
-            options.Fields.Remove(ItemFields.Etag);
+            var fields = options.Fields.ToList();
+
+            fields.Remove(ItemFields.CanDelete);
+            fields.Remove(ItemFields.CanDownload);
+            fields.Remove(ItemFields.DisplayPreferencesId);
+            fields.Remove(ItemFields.Etag);
+            options.Fields = fields.ToArray(fields.Count);
         }
 
         public async Task<Folder> GetInternalLiveTvFolder(CancellationToken cancellationToken)
@@ -2911,12 +2949,14 @@ namespace Emby.Server.Implementations.LiveTv
 
             var config = GetConfiguration();
 
-            var index = config.TunerHosts.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+            var list = config.TunerHosts.ToList();
+            var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
 
             if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
             {
                 info.Id = Guid.NewGuid().ToString("N");
-                config.TunerHosts.Add(info);
+                list.Add(info);
+                config.TunerHosts = list.ToArray(list.Count);
             }
             else
             {
@@ -2948,12 +2988,14 @@ namespace Emby.Server.Implementations.LiveTv
 
             var config = GetConfiguration();
 
-            var index = config.ListingProviders.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+            var list = config.ListingProviders.ToList();
+            var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
 
             if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
             {
                 info.Id = Guid.NewGuid().ToString("N");
-                config.ListingProviders.Add(info);
+                list.Add(info);
+                config.ListingProviders = list.ToArray(list.Count);
                 info.EnableNewProgramIds = true;
             }
             else
@@ -2972,7 +3014,7 @@ namespace Emby.Server.Implementations.LiveTv
         {
             var config = GetConfiguration();
 
-            config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+            config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
 
             _config.SaveConfiguration("livetv", config);
             _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
@@ -3004,7 +3046,7 @@ namespace Emby.Server.Implementations.LiveTv
             var providerChannels = await GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
                      .ConfigureAwait(false);
 
-            var mappings = listingsProviderInfo.ChannelMappings.ToList();
+            var mappings = listingsProviderInfo.ChannelMappings;
 
             var tunerChannelMappings =
                 tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
@@ -3014,7 +3056,7 @@ namespace Emby.Server.Implementations.LiveTv
             return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
         }
 
-        public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, List<NameValuePair> mappings, List<ChannelInfo> epgChannels)
+        public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> epgChannels)
         {
             var result = new TunerChannelMapping
             {
@@ -3078,7 +3120,7 @@ namespace Emby.Server.Implementations.LiveTv
             if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
             {
                 var config = GetConfiguration();
-                if (config.TunerHosts.Count > 0 &&
+                if (config.TunerHosts.Length > 0 &&
                     config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
                 {
                     return Task.FromResult(new MBRegistrationRecord

+ 16 - 6
Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs

@@ -43,9 +43,11 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (baseItem.SourceType == SourceType.LiveTV)
             {
-                if (string.IsNullOrWhiteSpace(baseItem.Path))
+                var activeRecordingInfo = _liveTvManager.GetActiveRecordingInfo(item.Path);
+
+                if (string.IsNullOrWhiteSpace(baseItem.Path) || activeRecordingInfo != null)
                 {
-                    return GetMediaSourcesInternal(item, cancellationToken);
+                    return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
                 }
             }
 
@@ -56,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv
         private const char StreamIdDelimeter = '_';
         private const string StreamIdDelimeterString = "_";
 
-        private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(IHasMediaSources item, CancellationToken cancellationToken)
+        private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(IHasMediaSources item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
         {
             IEnumerable<MediaSourceInfo> sources;
 
@@ -67,12 +69,20 @@ namespace Emby.Server.Implementations.LiveTv
                 if (item is ILiveTvRecording)
                 {
                     sources = await _liveTvManager.GetRecordingMediaSources(item, cancellationToken)
-                                .ConfigureAwait(false);
+                        .ConfigureAwait(false);
                 }
                 else
                 {
-                    sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
-                                .ConfigureAwait(false);
+                    if (activeRecordingInfo != null)
+                    {
+                        sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken)
+                            .ConfigureAwait(false);
+                    }
+                    else
+                    {
+                        sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
+                            .ConfigureAwait(false);
+                    }
                 }
             }
             catch (NotImplementedException)

+ 1 - 1
Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs

@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv
 
         public bool IsHidden
         {
-            get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; }
+            get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0; }
         }
 
         public bool IsEnabled

+ 71 - 57
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -25,12 +25,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         private readonly IHttpClient _httpClient;
         private readonly IServerApplicationHost _appHost;
         private readonly IEnvironmentInfo _environment;
+        private readonly INetworkManager _networkManager;
 
-        public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+        public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
         {
             _httpClient = httpClient;
             _appHost = appHost;
             _environment = environment;
+            _networkManager = networkManager;
         }
 
         public override string Type
@@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             get { return "m3u"; }
         }
 
-        public string Name
+        public virtual string Name
         {
             get { return "M3U Tuner"; }
         }
@@ -99,72 +101,84 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             }
 
             var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
-            var m3uchannels = channels.Cast<M3UChannel>();
-            var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
+            var channel = channels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
             if (channel != null)
             {
-                var path = channel.Path;
-                MediaProtocol protocol = MediaProtocol.File;
-                if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
-                {
-                    protocol = MediaProtocol.Http;
-                }
-                else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
-                {
-                    protocol = MediaProtocol.Rtmp;
-                }
-                else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
-                {
-                    protocol = MediaProtocol.Rtsp;
-                }
-                else if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
-                {
-                    protocol = MediaProtocol.Udp;
-                }
-                else if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
-                {
-                    protocol = MediaProtocol.Rtmp;
-                }
+                return new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channel) };
+            }
+            return new List<MediaSourceInfo>();
+        }
+
+        protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)
+        {
+            var path = channel.Path;
+            MediaProtocol protocol = MediaProtocol.File;
+            if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+            {
+                protocol = MediaProtocol.Http;
+            }
+            else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
+            {
+                protocol = MediaProtocol.Rtmp;
+            }
+            else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
+            {
+                protocol = MediaProtocol.Rtsp;
+            }
+            else if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
+            {
+                protocol = MediaProtocol.Udp;
+            }
+            else if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
+            {
+                protocol = MediaProtocol.Rtmp;
+            }
+
+            Uri uri;
+            var isRemote = true;
+            if (Uri.TryCreate(path, UriKind.Absolute, out uri))
+            {
+                isRemote = !_networkManager.IsInLocalNetwork(uri.Host);
+            }
 
-                var mediaSource = new MediaSourceInfo
+            var mediaSource = new MediaSourceInfo
+            {
+                Path = path,
+                Protocol = protocol,
+                MediaStreams = new List<MediaStream>
                 {
-                    Path = channel.Path,
-                    Protocol = protocol,
-                    MediaStreams = new List<MediaStream>
+                    new MediaStream
                     {
-                        new MediaStream
-                        {
-                            Type = MediaStreamType.Video,
-                            // Set the index to -1 because we don't know the exact index of the video stream within the container
-                            Index = -1,
-                            IsInterlaced = true
-                        },
-                        new MediaStream
-                        {
-                            Type = MediaStreamType.Audio,
-                            // Set the index to -1 because we don't know the exact index of the audio stream within the container
-                            Index = -1
-
-                        }
+                        Type = MediaStreamType.Video,
+                        // Set the index to -1 because we don't know the exact index of the video stream within the container
+                        Index = -1,
+                        IsInterlaced = true
                     },
-                    RequiresOpening = true,
-                    RequiresClosing = true,
-                    RequiresLooping = info.EnableStreamLooping,
+                    new MediaStream
+                    {
+                        Type = MediaStreamType.Audio,
+                        // Set the index to -1 because we don't know the exact index of the audio stream within the container
+                        Index = -1
 
-                    ReadAtNativeFramerate = false,
+                    }
+                },
+                RequiresOpening = true,
+                RequiresClosing = true,
+                RequiresLooping = info.EnableStreamLooping,
+                EnableMpDecimate = info.EnableMpDecimate,
 
-                    Id = channel.Path.GetMD5().ToString("N"),
-                    IsInfiniteStream = true,
-                    IsRemote = true,
+                ReadAtNativeFramerate = false,
 
-                    IgnoreDts = true
-                };
+                Id = channel.Path.GetMD5().ToString("N"),
+                IsInfiniteStream = true,
+                IsRemote = isRemote,
 
-                mediaSource.InferTotalBitrate();
+                IgnoreDts = true
+            };
 
-                return new List<MediaSourceInfo> { mediaSource };
-            }
-            return new List<MediaSourceInfo>();
+            mediaSource.InferTotalBitrate();
+
+            return mediaSource;
         }
 
         protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)

+ 6 - 12
Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs

@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             _appHost = appHost;
         }
 
-        public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
+        public async Task<List<ChannelInfo>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
         {
             // Read the file and display it line by line.
             using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             }
         }
 
-        public List<M3UChannel> ParseString(string text, string channelIdPrefix, string tunerHostId)
+        public List<ChannelInfo> ParseString(string text, string channelIdPrefix, string tunerHostId)
         {
             // Read the file and display it line by line.
             using (var reader = new StringReader(text))
@@ -66,9 +66,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         }
 
         const string ExtInfPrefix = "#EXTINF:";
-        private List<M3UChannel> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
+        private List<ChannelInfo> GetChannels(TextReader reader, string channelIdPrefix, string tunerHostId)
         {
-            var channels = new List<M3UChannel>();
+            var channels = new List<ChannelInfo>();
             string line;
             string extInf = "";
 
@@ -111,9 +111,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return channels;
         }
 
-        private M3UChannel GetChannelnfo(string extInf, string tunerHostId, string mediaUrl)
+        private ChannelInfo GetChannelnfo(string extInf, string tunerHostId, string mediaUrl)
         {
-            var channel = new M3UChannel();
+            var channel = new ChannelInfo();
             channel.TunerHostId = tunerHostId;
 
             extInf = extInf.Trim();
@@ -335,10 +335,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return dict;
         }
     }
-
-
-    public class M3UChannel : ChannelInfo
-    {
-        public string Path { get; set; }
-    }
 }

+ 16 - 34
Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs

@@ -24,8 +24,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
         public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
         {
-            byte[] buffer = new byte[BufferSize];
-
             if (source == null)
             {
                 throw new ArgumentNullException("source");
@@ -35,25 +33,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             {
                 cancellationToken.ThrowIfCancellationRequested();
 
+                byte[] buffer = new byte[BufferSize];
+
                 var bytesRead = source.Read(buffer, 0, buffer.Length);
 
                 if (bytesRead > 0)
                 {
-                    var allStreams = _outputStreams.ToList();
-
-                    //if (allStreams.Count == 1)
-                    //{
-                    //    allStreams[0].Value.Write(buffer, 0, bytesRead);
-                    //}
-                    //else
+                    foreach (var stream in _outputStreams)
                     {
-                        byte[] copy = new byte[bytesRead];
-                        Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
-
-                        foreach (var stream in allStreams)
-                        {
-                            stream.Value.Queue(copy, 0, copy.Length);
-                        }
+                        stream.Value.Queue(buffer, 0, bytesRead);
                     }
 
                     if (onStarted != null)
@@ -73,27 +61,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
         public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
         {
-            var result = new QueueStream(stream, _logger)
-            {
-                OnFinished = OnFinished
-            };
-
-            _outputStreams.TryAdd(result.Id, result);
+            var queueStream = new QueueStream(stream, _logger);
 
-            result.Start(cancellationToken);
-
-            return result.TaskCompletion.Task;
-        }
+            _outputStreams.TryAdd(queueStream.Id, queueStream);
 
-        public void RemoveOutputStream(QueueStream stream)
-        {
-            QueueStream removed;
-            _outputStreams.TryRemove(stream.Id, out removed);
-        }
+            try
+            {
+                queueStream.Start(cancellationToken);
+            }
+            finally
+            {
+                _outputStreams.TryRemove(queueStream.Id, out queueStream);
+                GC.Collect();
+            }
 
-        private void OnFinished(QueueStream queueStream)
-        {
-            RemoveOutputStream(queueStream);
+            return Task.FromResult(true);
         }
     }
 }

+ 5 - 77
Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs

@@ -13,10 +13,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
     public class QueueStream
     {
         private readonly Stream _outputStream;
-        private readonly ConcurrentQueue<Tuple<byte[], int, int>> _queue = new ConcurrentQueue<Tuple<byte[], int, int>>();
-        public TaskCompletionSource<bool> TaskCompletion { get; private set; }
+        private readonly BlockingCollection<Tuple<byte[], int, int>> _queue = new BlockingCollection<Tuple<byte[], int, int>>();
 
-        public Action<QueueStream> OnFinished { get; set; }
         private readonly ILogger _logger;
         public Guid Id = Guid.NewGuid();
 
@@ -24,94 +22,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         {
             _outputStream = outputStream;
             _logger = logger;
-            TaskCompletion = new TaskCompletionSource<bool>();
         }
 
         public void Queue(byte[] bytes, int offset, int count)
         {
-            _queue.Enqueue(new Tuple<byte[], int, int>(bytes, offset, count));
+            _queue.Add(new Tuple<byte[], int, int>(bytes, offset, count));
         }
 
         public void Start(CancellationToken cancellationToken)
         {
-            Task.Run(() => StartInternal(cancellationToken));
-        }
-
-        private Tuple<byte[], int, int> Dequeue()
-        {
-            Tuple<byte[], int, int> result;
-            if (_queue.TryDequeue(out result))
-            {
-                return result;
-            }
-
-            return null;
-        }
-
-        private void OnClosed()
-        {
-            GC.Collect();
-            if (OnFinished != null)
-            {
-                OnFinished(this);
-            }
-        }
-
-        public void Write(byte[] bytes, int offset, int count)
-        {
-            //return _outputStream.WriteAsync(bytes, offset, count, cancellationToken);
-
-            try
+            while (true)
             {
-                _outputStream.Write(bytes, offset, count);
-            }
-            catch (OperationCanceledException)
-            {
-                _logger.Debug("QueueStream cancelled");
-                TaskCompletion.TrySetCanceled();
-                OnClosed();
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error in QueueStream", ex);
-                TaskCompletion.TrySetException(ex);
-                OnClosed();
-            }
-        }
-
-        private async Task StartInternal(CancellationToken cancellationToken)
-        {
-            try
-            {
-                while (true)
+                foreach (var result in _queue.GetConsumingEnumerable())
                 {
                     cancellationToken.ThrowIfCancellationRequested();
 
-                    var result = Dequeue();
-                    if (result != null)
-                    {
-                        _outputStream.Write(result.Item1, result.Item2, result.Item3);
-                    }
-                    else
-                    {
-                        await Task.Delay(50, cancellationToken).ConfigureAwait(false);
-                    }
+                    _outputStream.Write(result.Item1, result.Item2, result.Item3);
                 }
             }
-            catch (OperationCanceledException)
-            {
-                _logger.Debug("QueueStream cancelled");
-                TaskCompletion.TrySetCanceled();
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error in QueueStream", ex);
-                TaskCompletion.TrySetException(ex);
-            }
-            finally
-            {
-                OnClosed();
-            }
         }
     }
 }

+ 0 - 179
Emby.Server.Implementations/Localization/Core/core.json

@@ -1,179 +0,0 @@
-{
-    "AppDeviceValues": "App: {0}, Device: {1}",
-    "UserDownloadingItemWithValues": "{0} is downloading {1}",
-    "FolderTypeMixed": "Mixed content",
-    "FolderTypeMovies": "Movies",
-    "FolderTypeMusic": "Music",
-    "FolderTypeAdultVideos": "Adult videos",
-    "FolderTypePhotos": "Photos",
-    "FolderTypeMusicVideos": "Music videos",
-    "FolderTypeHomeVideos": "Home videos",
-    "FolderTypeGames": "Games",
-    "FolderTypeBooks": "Books",
-    "FolderTypeTvShows": "TV",
-    "FolderTypeInherit": "Inherit",
-    "HeaderCastCrew": "Cast & Crew",
-    "HeaderPeople": "People",
-    "ValueSpecialEpisodeName": "Special - {0}",
-    "LabelChapterName": "Chapter {0}",
-    "NameSeasonNumber": "Season {0}",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelGithub": "Github",
-    "LabelApiDocumentation": "Api Documentation",
-    "LabelDeveloperResources": "Developer Resources",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server",
-    "CategorySync": "Sync",
-    "CategoryUser": "User",
-    "CategorySystem": "System",
-    "CategoryApplication": "Application",
-    "CategoryPlugin": "Plugin",
-    "NotificationOptionPluginError": "Plugin failure",
-    "NotificationOptionApplicationUpdateAvailable": "Application update available",
-    "NotificationOptionApplicationUpdateInstalled": "Application update installed",
-    "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
-    "NotificationOptionPluginInstalled": "Plugin installed",
-    "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback started",
-    "NotificationOptionAudioPlayback": "Audio playback started",
-    "NotificationOptionGamePlayback": "Game playback started",
-    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
-    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
-    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
-    "NotificationOptionTaskFailed": "Scheduled task failure",
-    "NotificationOptionInstallationFailed": "Installation failure",
-    "NotificationOptionNewLibraryContent": "New content added",
-    "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
-    "NotificationOptionCameraImageUploaded": "Camera image uploaded",
-    "NotificationOptionUserLockedOut": "User locked out",
-    "NotificationOptionServerRestartRequired": "Server restart required",
-    "ViewTypePlaylists": "Playlists",
-    "ViewTypeMovies": "Movies",
-    "ViewTypeTvShows": "TV",
-    "ViewTypeGames": "Games",
-    "ViewTypeMusic": "Music",
-    "ViewTypeMusicGenres": "Genres",
-    "ViewTypeMusicArtists": "Artists",
-    "ViewTypeBoxSets": "Collections",
-    "ViewTypeChannels": "Channels",
-    "ViewTypeLiveTV": "Live TV",
-    "ViewTypeLiveTvNowPlaying": "Now Airing",
-    "ViewTypeLatestGames": "Latest Games",
-    "ViewTypeRecentlyPlayedGames": "Recently Played",
-    "ViewTypeGameFavorites": "Favorites",
-    "ViewTypeGameSystems": "Game Systems",
-    "ViewTypeGameGenres": "Genres",
-    "ViewTypeTvResume": "Resume",
-    "ViewTypeTvNextUp": "Next Up",
-    "ViewTypeTvLatest": "Latest",
-    "ViewTypeTvShowSeries": "Series",
-    "ViewTypeTvGenres": "Genres",
-    "ViewTypeTvFavoriteSeries": "Favorite Series",
-    "ViewTypeTvFavoriteEpisodes": "Favorite Episodes",
-    "ViewTypeMovieResume": "Resume",
-    "ViewTypeMovieLatest": "Latest",
-    "ViewTypeMovieMovies": "Movies",
-    "ViewTypeMovieCollections": "Collections",
-    "ViewTypeMovieFavorites": "Favorites",
-    "ViewTypeMovieGenres": "Genres",
-    "ViewTypeMusicLatest": "Latest",
-    "ViewTypeMusicPlaylists": "Playlists",
-    "ViewTypeMusicAlbums": "Albums",
-    "ViewTypeMusicAlbumArtists": "Album Artists",
-    "HeaderOtherDisplaySettings": "Display Settings",
-    "ViewTypeMusicSongs": "Songs",
-    "ViewTypeMusicFavorites": "Favorites",
-    "ViewTypeMusicFavoriteAlbums": "Favorite Albums",
-    "ViewTypeMusicFavoriteArtists": "Favorite Artists",
-    "ViewTypeMusicFavoriteSongs": "Favorite Songs",
-    "ViewTypeFolders": "Folders",
-    "ViewTypeLiveTvRecordingGroups": "Recordings",
-    "ViewTypeLiveTvChannels": "Channels",
-    "ScheduledTaskFailedWithName": "{0} failed",
-    "LabelRunningTimeValue": "Running time: {0}",
-    "ScheduledTaskStartedWithName": "{0} started",
-    "VersionNumber": "Version {0}",
-    "PluginInstalledWithName": "{0} was installed",
-    "PluginUpdatedWithName": "{0} was updated",
-    "PluginUninstalledWithName": "{0} was uninstalled",
-    "ItemAddedWithName": "{0} was added to the library",
-    "ItemRemovedWithName": "{0} was removed from the library",
-    "LabelIpAddressValue": "Ip address: {0}",
-    "DeviceOnlineWithName": "{0} is connected",
-    "UserOnlineFromDevice": "{0} is online from {1}",
-    "ProviderValue": "Provider: {0}",
-    "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
-    "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
-    "UserCreatedWithName": "User {0} has been created",
-    "UserPasswordChangedWithName": "Password has been changed for user {0}",
-    "UserDeletedWithName": "User {0} has been deleted",
-    "MessageServerConfigurationUpdated": "Server configuration has been updated",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
-    "MessageApplicationUpdated": "Emby Server has been updated",
-    "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
-    "DeviceOfflineWithName": "{0} has disconnected",
-    "UserLockedOutWithName": "User {0} has been locked out",
-    "UserOfflineFromDevice": "{0} has disconnected from {1}",
-    "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
-    "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
-    "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
-    "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
-    "HeaderUnidentified": "Unidentified",
-    "HeaderImagePrimary": "Primary",
-    "HeaderImageBackdrop": "Backdrop",
-    "HeaderImageLogo": "Logo",
-    "HeaderUserPrimaryImage": "User Image",
-    "HeaderOverview": "Overview",
-    "HeaderShortOverview": "Short Overview",
-    "HeaderType": "Type",
-    "HeaderSeverity": "Severity",
-    "HeaderUser": "User",
-    "HeaderName": "Name",
-    "HeaderDate": "Date",
-    "HeaderPremiereDate": "Premiere Date",
-    "HeaderDateAdded": "Date Added",
-    "HeaderReleaseDate": "Release date",
-    "HeaderRuntime": "Runtime",
-    "HeaderPlayCount": "Play Count",
-    "HeaderSeason": "Season",
-    "HeaderSeasonNumber": "Season number",
-    "HeaderSeries": "Series:",
-    "HeaderNetwork": "Network",
-    "HeaderYear": "Year:",
-    "HeaderYears": "Years:",
-    "HeaderParentalRating": "Parental Rating",
-    "HeaderCommunityRating": "Community rating",
-    "HeaderTrailers": "Trailers",
-    "HeaderSpecials": "Specials",
-    "HeaderGameSystems": "Game Systems",
-    "HeaderPlayers": "Players:",
-    "HeaderAlbumArtists": "Album Artists",
-    "HeaderAlbums": "Albums",
-    "HeaderDisc": "Disc",
-    "HeaderTrack": "Track",
-    "HeaderAudio": "Audio",
-    "HeaderVideo": "Video",
-    "HeaderEmbeddedImage": "Embedded image",
-    "HeaderResolution": "Resolution",
-    "HeaderSubtitles": "Subtitles",
-    "HeaderGenres": "Genres",
-    "HeaderCountries": "Countries",
-    "HeaderStatus": "Status",
-    "HeaderTracks": "Tracks",
-    "HeaderMusicArtist": "Music artist",
-    "HeaderLocked": "Locked",
-    "HeaderStudios": "Studios",
-    "HeaderActor": "Actors",
-    "HeaderComposer": "Composers",
-    "HeaderDirector": "Directors",
-    "HeaderGuestStar": "Guest star",
-    "HeaderProducer": "Producers",
-    "HeaderWriter": "Writers",
-    "HeaderParentalRatings": "Parental Ratings",
-    "HeaderCommunityRatings": "Community ratings",
-    "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly.",
-    "DbUpgradeMessage":  "Please wait while your Emby Server database is upgraded. {0}% complete."
-}

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

@@ -1,5 +1,4 @@
 {
-  "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
   "AppDeviceValues": "App: {0}, Device: {1}",
   "UserDownloadingItemWithValues": "{0} is downloading {1}",
   "FolderTypeMixed": "Mixed content",
@@ -13,15 +12,12 @@
   "FolderTypeBooks": "Books",
   "FolderTypeTvShows": "TV",
   "FolderTypeInherit": "Inherit",
-  "HeaderCastCrew": "Cast & Crew",
-  "HeaderPeople": "People",
   "ValueSpecialEpisodeName": "Special - {0}",
   "LabelChapterName": "Chapter {0}",
   "NameSeasonUnknown": "Season Unknown",
   "NameSeasonNumber": "Season {0}",
   "LabelExit": "Exit",
   "LabelVisitCommunity": "Visit Community",
-  "LabelGithub": "Github",
   "LabelApiDocumentation": "Api Documentation",
   "LabelDeveloperResources": "Developer Resources",
   "LabelBrowseLibrary": "Browse Library",
@@ -164,15 +160,5 @@
   "HeaderStatus": "Status",
   "HeaderTracks": "Tracks",
   "HeaderMusicArtist": "Music artist",
-  "HeaderLocked": "Locked",
-  "HeaderStudios": "Studios",
-  "HeaderActor": "Actors",
-  "HeaderComposer": "Composers",
-  "HeaderDirector": "Directors",
-  "HeaderGuestStar": "Guest star",
-  "HeaderProducer": "Producers",
-  "HeaderWriter": "Writers",
-  "HeaderParentalRatings": "Parental Ratings",
-  "HeaderCommunityRatings": "Community ratings",
   "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
 }

+ 10 - 10
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Localization
         /// Gets the cultures.
         /// </summary>
         /// <returns>IEnumerable{CultureDto}.</returns>
-        public List<CultureDto> GetCultures()
+        public CultureDto[] GetCultures()
         {
             var type = GetType();
             var path = type.Namespace + ".iso6392.txt";
@@ -169,21 +169,21 @@ namespace Emby.Server.Implementations.Localization
             return list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
                 !string.IsNullOrWhiteSpace(i.DisplayName) &&
                 !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
-                !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToList();
+                !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray();
         }
 
         /// <summary>
         /// Gets the countries.
         /// </summary>
         /// <returns>IEnumerable{CountryInfo}.</returns>
-        public List<CountryInfo> GetCountries()
+        public CountryInfo[] GetCountries()
         {
             var type = GetType();
             var path = type.Namespace + ".countries.json";
 
             using (var stream = _assemblyInfo.GetManifestResourceStream(type, path))
             {
-                return _jsonSerializer.DeserializeFromStream<List<CountryInfo>>(stream);
+                return _jsonSerializer.DeserializeFromStream<CountryInfo[]>(stream);
             }
         }
 
@@ -191,9 +191,9 @@ namespace Emby.Server.Implementations.Localization
         /// Gets the parental ratings.
         /// </summary>
         /// <returns>IEnumerable{ParentalRating}.</returns>
-        public IEnumerable<ParentalRating> GetParentalRatings()
+        public ParentalRating[] GetParentalRatings()
         {
-            return GetParentalRatingsDictionary().Values.ToList();
+            return GetParentalRatingsDictionary().Values.ToArray();
         }
 
         /// <summary>
@@ -335,7 +335,7 @@ namespace Emby.Server.Implementations.Localization
             const string prefix = "Core";
             var key = prefix + culture;
 
-            return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "core.json"));
+            return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "en-US.json"));
         }
 
         private Dictionary<string, string> GetDictionary(string prefix, string culture, string baseFilename)
@@ -382,9 +382,9 @@ namespace Emby.Server.Implementations.Localization
             return culture + ".json";
         }
 
-        public IEnumerable<LocalizatonOption> GetLocalizationOptions()
+        public LocalizatonOption[] GetLocalizationOptions()
         {
-            return new List<LocalizatonOption>
+            return new LocalizatonOption[]
             {
                 new LocalizatonOption{ Name="Arabic", Value="ar"},
                 new LocalizatonOption{ Name="Bulgarian (Bulgaria)", Value="bg-BG"},
@@ -421,7 +421,7 @@ namespace Emby.Server.Implementations.Localization
                 new LocalizatonOption{ Name="Ukrainian", Value="uk"},
                 new LocalizatonOption{ Name="Vietnamese", Value="vi"}
 
-            }.OrderBy(i => i.Name);
+            };
         }
     }
 

+ 3 - 10
Emby.Server.Implementations/Logging/SimpleLogManager.cs

@@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Logging
 
     public class FileLogger : IDisposable
     {
-        private readonly Stream _fileStream;
+        private readonly FileStream _fileStream;
 
         private bool _disposed;
         private readonly CancellationTokenSource _cancellationTokenSource;
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Logging
         {
             Directory.CreateDirectory(Path.GetDirectoryName(path));
 
-            _fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
+            _fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 32768);
             _cancellationTokenSource = new CancellationTokenSource();
 
             Task.Factory.StartNew(LogInternal, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
@@ -134,19 +134,12 @@ namespace Emby.Server.Implementations.Logging
             {
                 try
                 {
-                    var any = false;
-
                     foreach (var message in _queue.GetConsumingEnumerable())
                     {
                         var bytes = Encoding.UTF8.GetBytes(message + Environment.NewLine);
                         _fileStream.Write(bytes, 0, bytes.Length);
 
-                        any = true;
-                    }
-
-                    if (any)
-                    {
-                        _fileStream.Flush();
+                        _fileStream.Flush(true);
                     }
                 }
                 catch

+ 7 - 2
Emby.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -75,6 +75,11 @@ namespace Emby.Server.Implementations.MediaEncoder
                 return false;
             }
 
+            if (!video.IsCompleteMedia)
+            {
+                return false;
+            }
+
             // Can't extract images if there are no video streams
             return video.DefaultVideoStreamIndex.HasValue;
         }
@@ -129,7 +134,7 @@ namespace Emby.Server.Implementations.MediaEncoder
 
                             var protocol = MediaProtocol.File;
 
-                            var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, new List<string>());
+                            var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, new string[] { });
 
                             _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
@@ -174,7 +179,7 @@ namespace Emby.Server.Implementations.MediaEncoder
 
             if (saveChapters && changesMade)
             {
-                await _chapterManager.SaveChapters(video.Id.ToString(), chapters).ConfigureAwait(false);
+                _chapterManager.SaveChapters(video.Id.ToString(), chapters);
             }
 
             DeleteDeadImages(currentImages, chapters);

+ 16 - 16
Emby.Server.Implementations/Notifications/CoreNotificationTypes.cs

@@ -28,21 +28,21 @@ namespace Emby.Server.Implementations.Notifications
                      Type = NotificationType.ApplicationUpdateInstalled.ToString(),
                      DefaultDescription = "{ReleaseNotes}",
                      DefaultTitle = "A new version of Emby Server has been installed.",
-                     Variables = new List<string>{"Version"}
+                     Variables = new string[]{"Version"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.InstallationFailed.ToString(),
                      DefaultTitle = "{Name} installation failed.",
-                     Variables = new List<string>{"Name", "Version"}
+                     Variables = new string[]{"Name", "Version"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.PluginInstalled.ToString(),
                      DefaultTitle = "{Name} was installed.",
-                     Variables = new List<string>{"Name", "Version"}
+                     Variables = new string[]{"Name", "Version"}
                 },
 
                 new NotificationTypeInfo
@@ -50,14 +50,14 @@ namespace Emby.Server.Implementations.Notifications
                      Type = NotificationType.PluginError.ToString(),
                      DefaultTitle = "{Name} has encountered an error.",
                      DefaultDescription = "{ErrorMessage}",
-                     Variables = new List<string>{"Name", "ErrorMessage"}
+                     Variables = new string[]{"Name", "ErrorMessage"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.PluginUninstalled.ToString(),
                      DefaultTitle = "{Name} was uninstalled.",
-                     Variables = new List<string>{"Name", "Version"}
+                     Variables = new string[]{"Name", "Version"}
                 },
 
                 new NotificationTypeInfo
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Notifications
                      Type = NotificationType.PluginUpdateInstalled.ToString(),
                      DefaultTitle = "{Name} was updated.",
                      DefaultDescription = "{ReleaseNotes}",
-                     Variables = new List<string>{"Name", "ReleaseNotes", "Version"}
+                     Variables = new string[]{"Name", "ReleaseNotes", "Version"}
                 },
 
                 new NotificationTypeInfo
@@ -79,70 +79,70 @@ namespace Emby.Server.Implementations.Notifications
                      Type = NotificationType.TaskFailed.ToString(),
                      DefaultTitle = "{Name} failed.",
                      DefaultDescription = "{ErrorMessage}",
-                     Variables = new List<string>{"Name", "ErrorMessage"}
+                     Variables = new string[]{"Name", "ErrorMessage"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.NewLibraryContent.ToString(),
                      DefaultTitle = "{Name} has been added to your media library.",
-                     Variables = new List<string>{"Name"}
+                     Variables = new string[]{"Name"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.AudioPlayback.ToString(),
                      DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
-                     Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
+                     Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.GamePlayback.ToString(),
                      DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
-                     Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
+                     Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.VideoPlayback.ToString(),
                      DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
-                     Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
+                     Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.AudioPlaybackStopped.ToString(),
                      DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.",
-                     Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
+                     Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.GamePlaybackStopped.ToString(),
                      DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.",
-                     Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
+                     Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.VideoPlaybackStopped.ToString(),
                      DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.",
-                     Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
+                     Variables = new string[]{"UserName", "ItemName", "DeviceName", "AppName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.CameraImageUploaded.ToString(),
                      DefaultTitle = "A new camera image has been uploaded from {DeviceName}.",
-                     Variables = new List<string>{"DeviceName"}
+                     Variables = new string[]{"DeviceName"}
                 },
 
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.UserLockedOut.ToString(),
                      DefaultTitle = "{UserName} has been locked out.",
-                     Variables = new List<string>{"UserName"}
+                     Variables = new string[]{"UserName"}
                 }
             };
 

+ 1 - 1
Emby.Server.Implementations/Notifications/NotificationManager.cs

@@ -251,7 +251,7 @@ namespace Emby.Server.Implementations.Notifications
             _typeFactories = notificationTypeFactories.ToArray();
         }
 
-        public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
+        public List<NotificationTypeInfo> GetNotificationTypes()
         {
             var list = _typeFactories.Select(i =>
             {

+ 2 - 2
Emby.Server.Implementations/Notifications/Notifications.cs

@@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.Notifications
             {
                 var artists = hasArtist.AllArtists;
 
-                if (artists.Count > 0)
+                if (artists.Length > 0)
                 {
                     name = hasArtist.AllArtists[0] + " - " + name;
                 }
@@ -440,7 +440,7 @@ namespace Emby.Server.Implementations.Notifications
                 name = item.SeriesName + " - " + name;
             }
 
-            if (item.Artists != null && item.Artists.Count > 0)
+            if (item.Artists != null && item.Artists.Length > 0)
             {
                 name = item.Artists[0] + " - " + name;
             }

+ 2 - 2
Emby.Server.Implementations/Playlists/PlaylistManager.cs

@@ -128,12 +128,12 @@ namespace Emby.Server.Implementations.Playlists
 
                 playlist.SetMediaType(options.MediaType);
 
-                await parentFolder.AddChild(playlist, CancellationToken.None).ConfigureAwait(false);
+                parentFolder.AddChild(playlist, CancellationToken.None);
 
                 await playlist.RefreshMetadata(new MetadataRefreshOptions(_fileSystem) { ForceSave = true }, CancellationToken.None)
                     .ConfigureAwait(false);
 
-                if (options.ItemIdList.Count > 0)
+                if (options.ItemIdList.Length > 0)
                 {
                     await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false)
                     {

+ 3 - 4
Emby.Server.Implementations/Security/AuthenticationRepository.cs

@@ -4,7 +4,6 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Threading;
-using System.Threading.Tasks;
 using Emby.Server.Implementations.Data;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Security;
@@ -51,14 +50,14 @@ namespace Emby.Server.Implementations.Security
             }
         }
 
-        public Task Create(AuthenticationInfo info, CancellationToken cancellationToken)
+        public void Create(AuthenticationInfo info, CancellationToken cancellationToken)
         {
             info.Id = Guid.NewGuid().ToString("N");
 
-            return Update(info, cancellationToken);
+            Update(info, cancellationToken);
         }
 
-        public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken)
+        public void Update(AuthenticationInfo info, CancellationToken cancellationToken)
         {
             if (info == null)
             {

+ 7 - 33
Emby.Server.Implementations/Services/ServiceController.cs

@@ -29,13 +29,6 @@ namespace Emby.Server.Implementations.Services
             }
         }
 
-        private Type[] GetGenericArguments(Type type)
-        {
-            return type.GetTypeInfo().IsGenericTypeDefinition
-                ? type.GetTypeInfo().GenericTypeParameters
-                : type.GetTypeInfo().GenericTypeArguments;
-        }
-
         public void RegisterService(HttpListenerHost appHost, Type serviceType)
         {
             var processedReqs = new HashSet<Type>();
@@ -50,38 +43,19 @@ namespace Emby.Server.Implementations.Services
 
                 ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions);
 
-                var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>));
-                var responseType = returnMarker != null ?
-                      GetGenericArguments(returnMarker)[0]
-                    : mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
-                      mi.ReturnType
-                    : Type.GetType(requestType.FullName + "Response");
+                //var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>));
+                //var responseType = returnMarker != null ?
+                //      GetGenericArguments(returnMarker)[0]
+                //    : mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ?
+                //      mi.ReturnType
+                //    : Type.GetType(requestType.FullName + "Response");
 
                 RegisterRestPaths(appHost, requestType);
 
-                appHost.AddServiceInfo(serviceType, requestType, responseType);
+                appHost.AddServiceInfo(serviceType, requestType);
             }
         }
 
-        private static Type GetTypeWithGenericTypeDefinitionOf(Type type, Type genericTypeDefinition)
-        {
-            foreach (var t in type.GetTypeInfo().ImplementedInterfaces)
-            {
-                if (t.GetTypeInfo().IsGenericType && t.GetGenericTypeDefinition() == genericTypeDefinition)
-                {
-                    return t;
-                }
-            }
-
-            var genericType = FirstGenericType(type);
-            if (genericType != null && genericType.GetGenericTypeDefinition() == genericTypeDefinition)
-            {
-                return genericType;
-            }
-
-            return null;
-        }
-
         public static Type FirstGenericType(Type type)
         {
             while (type != null)

+ 4 - 4
Emby.Server.Implementations/Services/ServiceHandler.cs

@@ -215,13 +215,13 @@ namespace Emby.Server.Implementations.Services
                 if (name == null) continue; //thank you ASP.NET
 
                 var values = request.QueryString.GetValues(name);
-                if (values.Length == 1)
+                if (values.Count == 1)
                 {
                     map[name] = values[0];
                 }
                 else
                 {
-                    for (var i = 0; i < values.Length; i++)
+                    for (var i = 0; i < values.Count; i++)
                     {
                         map[name + (i == 0 ? "" : "#" + i)] = values[i];
                     }
@@ -235,13 +235,13 @@ namespace Emby.Server.Implementations.Services
                     if (name == null) continue; //thank you ASP.NET
 
                     var values = request.FormData.GetValues(name);
-                    if (values.Length == 1)
+                    if (values.Count == 1)
                     {
                         map[name] = values[0];
                     }
                     else
                     {
-                        for (var i = 0; i < values.Length; i++)
+                        for (var i = 0; i < values.Count; i++)
                         {
                             map[name + (i == 0 ? "" : "#" + i)] = values[i];
                         }

+ 65 - 58
Emby.Server.Implementations/Session/SessionManager.cs

@@ -6,11 +6,8 @@ using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Devices;
@@ -253,7 +250,7 @@ namespace Emby.Server.Implementations.Session
                 {
                     try
                     {
-                        await _userManager.UpdateUser(user).ConfigureAwait(false);
+                        _userManager.UpdateUser(user);
                     }
                     catch (Exception ex)
                     {
@@ -468,7 +465,7 @@ namespace Emby.Server.Implementations.Session
 
                 if (!userId.HasValue)
                 {
-                    sessionInfo.AdditionalUsers.Clear();
+                    sessionInfo.AdditionalUsers = new SessionUserInfo[] { };
                 }
 
                 if (sessionInfo.SessionController == null)
@@ -622,7 +619,7 @@ namespace Emby.Server.Implementations.Session
             {
                 foreach (var user in users)
                 {
-                    await OnPlaybackStart(user.Id, libraryItem).ConfigureAwait(false);
+                    OnPlaybackStart(user.Id, libraryItem);
                 }
             }
 
@@ -650,8 +647,7 @@ namespace Emby.Server.Implementations.Session
         /// </summary>
         /// <param name="userId">The user identifier.</param>
         /// <param name="item">The item.</param>
-        /// <returns>Task.</returns>
-        private async Task OnPlaybackStart(Guid userId, IHasUserData item)
+        private void OnPlaybackStart(Guid userId, IHasUserData item)
         {
             var data = _userDataManager.GetUserData(userId, item);
 
@@ -670,7 +666,7 @@ namespace Emby.Server.Implementations.Session
                 data.Played = false;
             }
 
-            await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
+            _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None);
         }
 
         public Task OnPlaybackProgress(PlaybackProgressInfo info)
@@ -702,7 +698,7 @@ namespace Emby.Server.Implementations.Session
             {
                 foreach (var user in users)
                 {
-                    await OnPlaybackProgress(user, libraryItem, info).ConfigureAwait(false);
+                    OnPlaybackProgress(user, libraryItem, info);
                 }
             }
 
@@ -730,7 +726,7 @@ namespace Emby.Server.Implementations.Session
             StartIdleCheckTimer();
         }
 
-        private async Task OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
+        private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
         {
             var data = _userDataManager.GetUserData(user.Id, item);
 
@@ -742,7 +738,7 @@ namespace Emby.Server.Implementations.Session
 
                 UpdatePlaybackSettings(user, info, data);
 
-                await _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
+                _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None);
             }
         }
 
@@ -842,7 +838,7 @@ namespace Emby.Server.Implementations.Session
             {
                 foreach (var user in users)
                 {
-                    playedToCompletion = await OnPlaybackStopped(user.Id, libraryItem, info.PositionTicks, info.Failed).ConfigureAwait(false);
+                    playedToCompletion = OnPlaybackStopped(user.Id, libraryItem, info.PositionTicks, info.Failed);
                 }
             }
 
@@ -875,7 +871,7 @@ namespace Emby.Server.Implementations.Session
             await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false);
         }
 
-        private async Task<bool> OnPlaybackStopped(Guid userId, BaseItem item, long? positionTicks, bool playbackFailed)
+        private bool OnPlaybackStopped(Guid userId, BaseItem item, long? positionTicks, bool playbackFailed)
         {
             bool playedToCompletion = false;
 
@@ -896,7 +892,7 @@ namespace Emby.Server.Implementations.Session
                     playedToCompletion = true;
                 }
 
-                await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
+                _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None);
             }
 
             return playedToCompletion;
@@ -1074,7 +1070,7 @@ namespace Emby.Server.Implementations.Session
                     DtoOptions = new DtoOptions(false)
                     {
                         EnableImages = false,
-                        Fields = new List<ItemFields>
+                        Fields = new ItemFields[]
                         {
                             ItemFields.SortName
                         }
@@ -1097,7 +1093,7 @@ namespace Emby.Server.Implementations.Session
                     DtoOptions = new DtoOptions(false)
                     {
                         EnableImages = false,
-                        Fields = new List<ItemFields>
+                        Fields = new ItemFields[]
                         {
                             ItemFields.SortName
                         }
@@ -1340,11 +1336,15 @@ namespace Emby.Server.Implementations.Session
             {
                 var user = _userManager.GetUserById(userId);
 
-                session.AdditionalUsers.Add(new SessionUserInfo
+                var list = session.AdditionalUsers.ToList();
+
+                list.Add(new SessionUserInfo
                 {
                     UserId = userId,
                     UserName = user.Name
                 });
+
+                session.AdditionalUsers = list.ToArray(list.Count);
             }
         }
 
@@ -1368,7 +1368,10 @@ namespace Emby.Server.Implementations.Session
 
             if (user != null)
             {
-                session.AdditionalUsers.Remove(user);
+                var list = session.AdditionalUsers.ToList();
+                list.Remove(user);
+
+                session.AdditionalUsers = list.ToArray(list.Count);
             }
         }
 
@@ -1425,7 +1428,7 @@ namespace Emby.Server.Implementations.Session
                 user = result;
             }
 
-            var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
+            var token = GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName);
 
             EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
 
@@ -1447,7 +1450,7 @@ namespace Emby.Server.Implementations.Session
         }
 
 
-        private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
+        private string GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
         {
             var existing = _authRepo.Get(new AuthenticationInfoQuery
             {
@@ -1477,12 +1480,12 @@ namespace Emby.Server.Implementations.Session
             };
 
             _logger.Info("Creating new access token for user {0}", userId);
-            await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false);
+            _authRepo.Create(newToken, CancellationToken.None);
 
             return newToken.AccessToken;
         }
 
-        public async Task Logout(string accessToken)
+        public void Logout(string accessToken)
         {
             if (string.IsNullOrWhiteSpace(accessToken))
             {
@@ -1502,7 +1505,7 @@ namespace Emby.Server.Implementations.Session
             {
                 existing.IsActive = false;
 
-                await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false);
+                _authRepo.Update(existing, CancellationToken.None);
 
                 var sessions = Sessions
                     .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
@@ -1522,7 +1525,7 @@ namespace Emby.Server.Implementations.Session
             }
         }
 
-        public async Task RevokeUserTokens(string userId, string currentAccessToken)
+        public void RevokeUserTokens(string userId, string currentAccessToken)
         {
             var existing = _authRepo.Get(new AuthenticationInfoQuery
             {
@@ -1534,14 +1537,14 @@ namespace Emby.Server.Implementations.Session
             {
                 if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
                 {
-                    await Logout(info.AccessToken).ConfigureAwait(false);
+                    Logout(info.AccessToken);
                 }
             }
         }
 
-        public Task RevokeToken(string token)
+        public void RevokeToken(string token)
         {
-            return Logout(token);
+            Logout(token);
         }
 
         /// <summary>
@@ -1661,35 +1664,39 @@ namespace Emby.Server.Implementations.Session
                     AddProgramRecordingInfo = false
                 };
 
-                dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo);
-                dtoOptions.Fields.Remove(ItemFields.SyncInfo);
-                dtoOptions.Fields.Remove(ItemFields.CanDelete);
-                dtoOptions.Fields.Remove(ItemFields.CanDownload);
-                dtoOptions.Fields.Remove(ItemFields.ChildCount);
-                dtoOptions.Fields.Remove(ItemFields.CustomRating);
-                dtoOptions.Fields.Remove(ItemFields.DateLastMediaAdded);
-                dtoOptions.Fields.Remove(ItemFields.DateLastRefreshed);
-                dtoOptions.Fields.Remove(ItemFields.DateLastSaved);
-                dtoOptions.Fields.Remove(ItemFields.DisplayPreferencesId);
-                dtoOptions.Fields.Remove(ItemFields.Etag);
-                dtoOptions.Fields.Remove(ItemFields.ExternalEtag);
-                dtoOptions.Fields.Remove(ItemFields.InheritedParentalRatingValue);
-                dtoOptions.Fields.Remove(ItemFields.ItemCounts);
-                dtoOptions.Fields.Remove(ItemFields.MediaSourceCount);
-                dtoOptions.Fields.Remove(ItemFields.MediaStreams);
-                dtoOptions.Fields.Remove(ItemFields.MediaSources);
-                dtoOptions.Fields.Remove(ItemFields.People);
-                dtoOptions.Fields.Remove(ItemFields.PlayAccess);
-                dtoOptions.Fields.Remove(ItemFields.People);
-                dtoOptions.Fields.Remove(ItemFields.ProductionLocations);
-                dtoOptions.Fields.Remove(ItemFields.RecursiveItemCount);
-                dtoOptions.Fields.Remove(ItemFields.RemoteTrailers);
-                dtoOptions.Fields.Remove(ItemFields.SeasonUserData);
-                dtoOptions.Fields.Remove(ItemFields.Settings);
-                dtoOptions.Fields.Remove(ItemFields.SortName);
-                dtoOptions.Fields.Remove(ItemFields.Tags);
-                dtoOptions.Fields.Remove(ItemFields.ThemeSongIds);
-                dtoOptions.Fields.Remove(ItemFields.ThemeVideoIds);
+                var fields = dtoOptions.Fields.ToList();
+
+                fields.Remove(ItemFields.BasicSyncInfo);
+                fields.Remove(ItemFields.SyncInfo);
+                fields.Remove(ItemFields.CanDelete);
+                fields.Remove(ItemFields.CanDownload);
+                fields.Remove(ItemFields.ChildCount);
+                fields.Remove(ItemFields.CustomRating);
+                fields.Remove(ItemFields.DateLastMediaAdded);
+                fields.Remove(ItemFields.DateLastRefreshed);
+                fields.Remove(ItemFields.DateLastSaved);
+                fields.Remove(ItemFields.DisplayPreferencesId);
+                fields.Remove(ItemFields.Etag);
+                fields.Remove(ItemFields.ExternalEtag);
+                fields.Remove(ItemFields.InheritedParentalRatingValue);
+                fields.Remove(ItemFields.ItemCounts);
+                fields.Remove(ItemFields.MediaSourceCount);
+                fields.Remove(ItemFields.MediaStreams);
+                fields.Remove(ItemFields.MediaSources);
+                fields.Remove(ItemFields.People);
+                fields.Remove(ItemFields.PlayAccess);
+                fields.Remove(ItemFields.People);
+                fields.Remove(ItemFields.ProductionLocations);
+                fields.Remove(ItemFields.RecursiveItemCount);
+                fields.Remove(ItemFields.RemoteTrailers);
+                fields.Remove(ItemFields.SeasonUserData);
+                fields.Remove(ItemFields.Settings);
+                fields.Remove(ItemFields.SortName);
+                fields.Remove(ItemFields.Tags);
+                fields.Remove(ItemFields.ThemeSongIds);
+                fields.Remove(ItemFields.ThemeVideoIds);
+
+                dtoOptions.Fields = fields.ToArray(fields.Count);
 
                 _itemInfoDtoOptions = dtoOptions;
             }
@@ -1698,7 +1705,7 @@ namespace Emby.Server.Implementations.Session
 
             if (mediaSource != null)
             {
-                info.MediaStreams = mediaSource.MediaStreams;
+                info.MediaStreams = mediaSource.MediaStreams.ToArray();
             }
 
             return info;

+ 4 - 4
Emby.Server.Implementations/Social/SharingManager.cs

@@ -58,8 +58,8 @@ namespace Emby.Server.Implementations.Social
             };
 
             AddShareInfo(info, externalUrl);
-            
-            await _repository.CreateShare(info).ConfigureAwait(false);
+
+            _repository.CreateShare(info);
 
             return info;
         }
@@ -92,9 +92,9 @@ namespace Emby.Server.Implementations.Social
             }
         }
 
-        public Task DeleteShare(string id)
+        public void DeleteShare(string id)
         {
-            return _repository.DeleteShare(id);
+            _repository.DeleteShare(id);
         }
     }
 }

+ 2 - 4
Emby.Server.Implementations/Social/SharingRepository.cs

@@ -1,8 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
 using Emby.Server.Implementations.Data;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Model.Logging;
@@ -42,7 +40,7 @@ namespace Emby.Server.Implementations.Social
             }
         }
 
-        public async Task CreateShare(SocialShareInfo info)
+        public void CreateShare(SocialShareInfo info)
         {
             if (info == null)
             {
@@ -109,7 +107,7 @@ namespace Emby.Server.Implementations.Social
             return info;
         }
 
-        public async Task DeleteShare(string id)
+        public void DeleteShare(string id)
         {
 
         }

+ 1 - 1
Emby.Server.Implementations/Sorting/ArtistComparer.cs

@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Sorting
                 return string.Empty;
             }
 
-            return audio.Artists.Count == 0 ? null : audio.Artists[0];
+            return audio.Artists.Length == 0 ? null : audio.Artists[0];
         }
 
         /// <summary>

+ 3 - 3
Emby.Server.Implementations/TV/TVSeriesManager.cs

@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.TV
                 Recursive = true,
                 DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions
                 {
-                    Fields = new List<ItemFields>
+                    Fields = new ItemFields[]
                     {
                         ItemFields.SeriesPresentationUniqueKey
                     }
@@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV
                 Limit = limit,
                 DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions
                 {
-                    Fields = new List<ItemFields>
+                    Fields = new ItemFields[]
                     {
                         ItemFields.SeriesPresentationUniqueKey
                     },
@@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.TV
                 ParentIndexNumberNotEquals = 0,
                 DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions
                 {
-                    Fields = new List<ItemFields>
+                    Fields = new ItemFields[]
                     {
                         ItemFields.SortName
                     },

+ 74 - 56
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -122,7 +122,10 @@ namespace Emby.Server.Implementations.Updates
 
         private readonly ICryptoProvider _cryptographyProvider;
 
-        public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IConfigurationManager config, IFileSystem fileSystem, ICryptoProvider cryptographyProvider)
+        // netframework or netcore
+        private readonly string _packageRuntime;
+
+        public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IConfigurationManager config, IFileSystem fileSystem, ICryptoProvider cryptographyProvider, string packageRuntime)
         {
             if (logger == null)
             {
@@ -140,6 +143,7 @@ namespace Emby.Server.Implementations.Updates
             _config = config;
             _fileSystem = fileSystem;
             _cryptographyProvider = cryptographyProvider;
+            _packageRuntime = packageRuntime;
             _logger = logger;
         }
 
@@ -157,7 +161,7 @@ namespace Emby.Server.Implementations.Updates
         /// Gets all available packages.
         /// </summary>
         /// <returns>Task{List{PackageInfo}}.</returns>
-        public async Task<IEnumerable<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
+        public async Task<List<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
             bool withRegistration = true,
             string packageType = null,
             Version applicationVersion = null)
@@ -171,11 +175,11 @@ namespace Emby.Server.Implementations.Updates
                     { "systemid", _applicationHost.SystemId }
                 };
 
-                using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
+                using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall?includeAllRuntimes=true", data, cancellationToken).ConfigureAwait(false))
                 {
                     cancellationToken.ThrowIfCancellationRequested();
 
-                    var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(json).ToList();
+                    var packages = _jsonSerializer.DeserializeFromStream<PackageInfo[]>(json);
 
                     return FilterPackages(packages, packageType, applicationVersion);
                 }
@@ -184,7 +188,7 @@ namespace Emby.Server.Implementations.Updates
             {
                 var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
 
-                return FilterPackages(packages.ToList(), packageType, applicationVersion);
+                return FilterPackages(packages, packageType, applicationVersion);
             }
         }
 
@@ -195,21 +199,21 @@ namespace Emby.Server.Implementations.Updates
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{List{PackageInfo}}.</returns>
-        public async Task<IEnumerable<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
+        public async Task<List<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
         {
             _logger.Info("Opening {0}", PackageCachePath);
             try
             {
                 using (var stream = _fileSystem.OpenRead(PackageCachePath))
                 {
-                    var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(stream).ToList();
+                    var packages = _jsonSerializer.DeserializeFromStream<PackageInfo[]>(stream);
 
                     if (DateTime.UtcNow - _lastPackageUpdateTime > GetCacheLength())
                     {
                         UpdateCachedPackages(CancellationToken.None, false);
                     }
 
-                    return packages;
+                    return FilterPackages(packages);
                 }
             }
             catch (Exception)
@@ -221,7 +225,7 @@ namespace Emby.Server.Implementations.Updates
             await UpdateCachedPackages(cancellationToken, true).ConfigureAwait(false);
             using (var stream = _fileSystem.OpenRead(PackageCachePath))
             {
-                return _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(stream).ToList();
+                return FilterPackages(_jsonSerializer.DeserializeFromStream<PackageInfo[]>(stream));
             }
         }
 
@@ -244,7 +248,7 @@ namespace Emby.Server.Implementations.Updates
 
                 var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
                 {
-                    Url = "https://www.mb3admin.com/admin/service/MB3Packages.json",
+                    Url = "https://www.mb3admin.com/admin/service/EmbyPackages.json",
                     CancellationToken = cancellationToken,
                     Progress = new SimpleProgress<Double>()
 
@@ -277,57 +281,77 @@ namespace Emby.Server.Implementations.Updates
 
         private TimeSpan GetCacheLength()
         {
-            switch (GetSystemUpdateLevel())
-            {
-                case PackageVersionClass.Beta:
-                    return TimeSpan.FromMinutes(30);
-                case PackageVersionClass.Dev:
-                    return TimeSpan.FromMinutes(3);
-                default:
-                    return TimeSpan.FromHours(24);
-            }
+            return TimeSpan.FromMinutes(3);
         }
 
-        protected IEnumerable<PackageInfo> FilterPackages(List<PackageInfo> packages)
+        protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages)
         {
+            var list = new List<PackageInfo>();
+
             foreach (var package in packages)
             {
-                package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
-                    .OrderByDescending(GetPackageVersion).ToList();
+                var versions = new List<PackageVersionInfo>();
+                foreach (var version in package.versions)
+                {
+                    if (string.IsNullOrWhiteSpace(version.sourceUrl))
+                    {
+                        continue;
+                    }
+
+                    if (string.IsNullOrWhiteSpace(version.runtimes) || version.runtimes.IndexOf(_packageRuntime, StringComparison.OrdinalIgnoreCase) == -1)
+                    {
+                        continue;
+                    }
+
+                    versions.Add(version);
+                }
+
+                package.versions = versions
+                    .OrderByDescending(GetPackageVersion)
+                    .ToArray();
+
+                if (package.versions.Length == 0)
+                {
+                    continue;
+                }
+
+                list.Add(package);
             }
 
             // Remove packages with no versions
-            packages = packages.Where(p => p.versions.Any()).ToList();
-
-            return packages;
+            return list;
         }
 
-        protected IEnumerable<PackageInfo> FilterPackages(List<PackageInfo> packages, string packageType, Version applicationVersion)
+        protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages, string packageType, Version applicationVersion)
         {
-            foreach (var package in packages)
-            {
-                package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
-                    .OrderByDescending(GetPackageVersion).ToList();
-            }
+            var packagesList = FilterPackages(packages);
 
-            if (!string.IsNullOrWhiteSpace(packageType))
-            {
-                packages = packages.Where(p => string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase)).ToList();
-            }
+            var returnList = new List<PackageInfo>();
 
-            // If an app version was supplied, filter the versions for each package to only include supported versions
-            if (applicationVersion != null)
+            var filterOnPackageType = !string.IsNullOrWhiteSpace(packageType);
+
+            foreach (var p in packagesList)
             {
-                foreach (var package in packages)
+                if (filterOnPackageType && !string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase))
                 {
-                    package.versions = package.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToList();
+                    continue;
                 }
-            }
 
-            // Remove packages with no versions
-            packages = packages.Where(p => p.versions.Any()).ToList();
+                // If an app version was supplied, filter the versions for each package to only include supported versions
+                if (applicationVersion != null)
+                {
+                    p.versions = p.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToArray();
+                }
 
-            return packages;
+                if (p.versions.Length == 0)
+                {
+                    continue;
+                }
+
+                returnList.Add(p);
+            }
+
+            return returnList;
         }
 
         /// <summary>
@@ -418,30 +442,24 @@ namespace Emby.Server.Implementations.Updates
         /// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns>
         public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken)
         {
-            var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
-
-            var plugins = _applicationHost.Plugins.ToList();
-
-            if (withAutoUpdateEnabled)
+            if (!_config.CommonConfiguration.EnableAutoUpdate)
             {
-                plugins = plugins
-                    .Where(p => _config.CommonConfiguration.EnableAutoUpdate)
-                    .ToList();
+                return new PackageVersionInfo[] { };
             }
 
+            var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
+
             var systemUpdateLevel = GetSystemUpdateLevel();
 
             // Figure out what needs to be installed
-            var packages = plugins.Select(p =>
+            return _applicationHost.Plugins.Select(p =>
             {
                 var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel);
 
                 return latestPluginInfo != null && GetPackageVersion(latestPluginInfo) > p.Version ? latestPluginInfo : null;
 
-            }).Where(i => i != null).ToList();
-
-            return packages
-                .Where(p => !string.IsNullOrWhiteSpace(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase)));
+            }).Where(i => i != null)
+            .Where(p => !string.IsNullOrWhiteSpace(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase)));
         }
 
         /// <summary>

+ 1 - 1
Emby.Server.Implementations/packages.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="Emby.XmlTv" version="1.0.10" targetFramework="net46" />
-  <package id="MediaBrowser.Naming" version="1.0.6" targetFramework="net46" />
+  <package id="MediaBrowser.Naming" version="1.0.7" targetFramework="net46" />
   <package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" />
   <package id="SharpCompress" version="0.14.0" targetFramework="net46" />
   <package id="SimpleInjector" version="4.0.8" targetFramework="net46" />

+ 20 - 4
MediaBrowser.Api/BaseApiService.cs

@@ -11,6 +11,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Services;
+using MediaBrowser.Model.Extensions;
 
 namespace MediaBrowser.Api
 {
@@ -54,6 +55,17 @@ namespace MediaBrowser.Api
             return Request.Headers[name];
         }
 
+        private static readonly string[] EmptyStringArray = new string[] { };
+        public static string[] SplitValue(string value, char delim)
+        {
+            if (string.IsNullOrWhiteSpace(value))
+            {
+                return EmptyStringArray;
+            }
+
+            return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
+        }
+        
         /// <summary>
         /// To the optimized result.
         /// </summary>
@@ -128,7 +140,7 @@ namespace MediaBrowser.Api
             var hasFields = request as IHasItemFields;
             if (hasFields != null)
             {
-                options.Fields = hasFields.GetItemFields().ToList();
+                options.Fields = hasFields.GetItemFields();
             }
 
             var client = authInfo.Client ?? string.Empty;
@@ -137,7 +149,9 @@ namespace MediaBrowser.Api
                 client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 ||
                 client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1)
             {
-                options.Fields.Add(Model.Querying.ItemFields.RecursiveItemCount);
+                var list = options.Fields.ToList();
+                list.Add(Model.Querying.ItemFields.RecursiveItemCount);
+                options.Fields = list.ToArray(list.Count);
             }
 
             if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -148,7 +162,9 @@ namespace MediaBrowser.Api
                client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 ||
                client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1)
             {
-                options.Fields.Add(Model.Querying.ItemFields.ChildCount);
+                var list = options.Fields.ToList();
+                list.Add(Model.Querying.ItemFields.ChildCount);
+                options.Fields = list.ToArray(list.Count);
             }
 
             var hasDtoOptions = request as IHasDtoOptions;
@@ -167,7 +183,7 @@ namespace MediaBrowser.Api
 
                 if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
                 {
-                    options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToList();
+                    options.ImageTypes = (hasDtoOptions.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
                 }
             }
 

+ 4 - 4
MediaBrowser.Api/ChannelService.cs

@@ -55,7 +55,7 @@ namespace MediaBrowser.Api
     }
 
     [Route("/Channels/Features", "GET", Summary = "Gets features for a channel")]
-    public class GetAllChannelFeatures : IReturn<List<ChannelFeatures>>
+    public class GetAllChannelFeatures : IReturn<ChannelFeatures[]>
     {
     }
 
@@ -187,7 +187,7 @@ namespace MediaBrowser.Api
 
         public object Get(GetAllChannelFeatures request)
         {
-            var result = _channelManager.GetAllChannelFeatures().ToList();
+            var result = _channelManager.GetAllChannelFeatures();
 
             return ToOptimizedResult(result);
         }
@@ -231,7 +231,7 @@ namespace MediaBrowser.Api
                 SortOrder = request.SortOrder,
                 SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
                 Filters = request.GetFilters().ToArray(),
-                Fields = request.GetItemFields().ToArray()
+                Fields = request.GetItemFields()
 
             }, CancellationToken.None).ConfigureAwait(false);
 
@@ -247,7 +247,7 @@ namespace MediaBrowser.Api
                 ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
                 UserId = request.UserId,
                 Filters = request.GetFilters().ToArray(),
-                Fields = request.GetItemFields().ToList()
+                Fields = request.GetItemFields()
 
             }, CancellationToken.None).ConfigureAwait(false);
 

+ 2 - 3
MediaBrowser.Api/ConfigurationService.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Serialization;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 using System.Threading.Tasks;
 
 using MediaBrowser.Controller.IO;
@@ -62,7 +61,7 @@ namespace MediaBrowser.Api
 
     [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")]
     [Authenticated(Roles = "Admin")]
-    public class GetMetadataPlugins : IReturn<List<MetadataPluginSummary>>
+    public class GetMetadataPlugins : IReturn<MetadataPluginSummary[]>
     {
 
     }
@@ -170,7 +169,7 @@ namespace MediaBrowser.Api
 
         public object Get(GetMetadataPlugins request)
         {
-            return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins().ToList());
+            return ToOptimizedSerializedResultUsingCache(_providerManager.GetAllMetadataPlugins());
         }
     }
 }

+ 1 - 2
MediaBrowser.Api/Devices/DeviceService.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Linq;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Devices;
@@ -143,7 +142,7 @@ namespace MediaBrowser.Api.Devices
             }
             else
             {
-                var file = Request.Files.First();
+                var file = Request.Files.Length == 0 ? null : Request.Files[0];
 
                 var task = _deviceManager.AcceptCameraUpload(deviceId, file.InputStream, new LocalFileInfo
                 {

+ 1 - 3
MediaBrowser.Api/DisplayPreferencesService.cs

@@ -88,9 +88,7 @@ namespace MediaBrowser.Api
             // Serialize to json and then back so that the core doesn't see the request dto type
             var displayPreferences = _jsonSerializer.DeserializeFromString<DisplayPreferences>(_jsonSerializer.SerializeToString(request));
 
-            var task = _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None);
-
-            Task.WaitAll(task);
+            _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None);
         }
     }
 }

+ 0 - 1
MediaBrowser.Api/Dlna/DlnaServerService.cs

@@ -3,7 +3,6 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
-using System.Linq;
 using System.Threading.Tasks;
 
 using MediaBrowser.Controller.IO;

+ 4 - 5
MediaBrowser.Api/Dlna/DlnaService.cs

@@ -1,14 +1,13 @@
-using MediaBrowser.Controller.Dlna;
+using System.Linq;
+using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dlna;
-using System.Collections.Generic;
-using System.Linq;
 using MediaBrowser.Model.Services;
 
 namespace MediaBrowser.Api.Dlna
 {
     [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")]
-    public class GetProfileInfos : IReturn<List<DeviceProfileInfo>>
+    public class GetProfileInfos : IReturn<DeviceProfileInfo[]>
     {
     }
 
@@ -53,7 +52,7 @@ namespace MediaBrowser.Api.Dlna
 
         public object Get(GetProfileInfos request)
         {
-            var result = _dlnaManager.GetProfileInfos().ToList();
+            var result = _dlnaManager.GetProfileInfos().ToArray();
 
             return ToOptimizedResult(result);
         }

+ 3 - 6
MediaBrowser.Api/EnvironmentService.cs

@@ -226,7 +226,7 @@ namespace MediaBrowser.Api
                 return ToOptimizedSerializedResultUsingCache(GetNetworkShares(path).OrderBy(i => i.Path).ToList());
             }
 
-            return ToOptimizedSerializedResultUsingCache(GetFileSystemEntries(request).OrderBy(i => i.Path).ToList());
+            return ToOptimizedSerializedResultUsingCache(GetFileSystemEntries(request).ToList());
         }
 
         public object Get(GetNetworkShares request)
@@ -271,9 +271,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetNetworkDevices request)
         {
-            var result = _networkManager.GetNetworkDevices()
-                .OrderBy(i => i.Path)
-                .ToList();
+            var result = _networkManager.GetNetworkDevices().ToList();
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -300,7 +298,6 @@ namespace MediaBrowser.Api
         /// <returns>IEnumerable{FileSystemEntryInfo}.</returns>
         private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request)
         {
-            // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks)
             var entries = _fileSystem.GetFileSystemEntries(request.Path).Where(i =>
             {
                 if (!request.IncludeHidden && i.IsHidden)
@@ -329,7 +326,7 @@ namespace MediaBrowser.Api
                 Path = f.FullName,
                 Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File
 
-            }).ToList();
+            });
         }
 
         public object Get(GetParentPath request)

+ 1 - 1
MediaBrowser.Api/FilterService.cs

@@ -108,7 +108,7 @@ namespace MediaBrowser.Api
                 EnableTotalRecordCount = false,
                 DtoOptions = new Controller.Dto.DtoOptions
                 {
-                    Fields = new List<ItemFields> { ItemFields.Genres, ItemFields.Tags },
+                    Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
                     EnableImages = false,
                     EnableUserData = false
                 }

+ 8 - 52
MediaBrowser.Api/GamesService.cs

@@ -28,21 +28,7 @@ namespace MediaBrowser.Api
     /// Class GetGameSystemSummaries
     /// </summary>
     [Route("/Games/SystemSummaries", "GET", Summary = "Finds games similar to a given game.")]
-    public class GetGameSystemSummaries : IReturn<List<GameSystemSummary>>
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "Optional. Filter by user id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string UserId { get; set; }
-    }
-
-    /// <summary>
-    /// Class GetGameSystemSummaries
-    /// </summary>
-    [Route("/Games/PlayerIndex", "GET", Summary = "Gets an index of players (1-x) and the number of games listed under each")]
-    public class GetPlayerIndex : IReturn<List<ItemIndex>>
+    public class GetGameSystemSummaries : IReturn<GameSystemSummary[]>
     {
         /// <summary>
         /// Gets or sets the user id.
@@ -117,47 +103,17 @@ namespace MediaBrowser.Api
                     EnableImages = false
                 }
             };
-            var gameSystems = _libraryManager.GetItemList(query)
-                .Cast<GameSystem>()
-                .ToList();
 
-            var result = gameSystems
+            var result = _libraryManager.GetItemList(query)
+                .Cast<GameSystem>()
                 .Select(i => GetSummary(i, user))
-                .ToList();
+                .ToArray();
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
-        public object Get(GetPlayerIndex request)
-        {
-            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
-            var query = new InternalItemsQuery(user)
-            {
-                IncludeItemTypes = new[] { typeof(Game).Name },
-                DtoOptions = new DtoOptions(false)
-                {
-                    EnableImages = false
-                }
-            };
-            var games = _libraryManager.GetItemList(query)
-                .Cast<Game>()
-                .ToList();
-
-            var lookup = games
-                .ToLookup(i => i.PlayersSupported ?? -1)
-                .OrderBy(i => i.Key)
-                .Select(i => new ItemIndex
-                {
-                    ItemCount = i.Count(),
-                    Name = i.Key == -1 ? string.Empty : i.Key.ToString(UsCulture)
-                })
-                .ToList();
-
-            return ToOptimizedSerializedResultUsingCache(lookup);
-        }
-
         /// <summary>
         /// Gets the summary.
         /// </summary>
@@ -183,15 +139,15 @@ namespace MediaBrowser.Api
                     }
                 });
 
-            var games = items.Cast<Game>().ToList();
+            var games = items.Cast<Game>().ToArray();
 
             summary.ClientInstalledGameCount = games.Count(i => i.IsPlaceHolder);
 
-            summary.GameCount = games.Count;
+            summary.GameCount = games.Length;
 
             summary.GameFileExtensions = games.Where(i => !i.IsPlaceHolder).Select(i => Path.GetExtension(i.Path))
                 .Distinct(StringComparer.OrdinalIgnoreCase)
-                .ToList();
+                .ToArray();
 
             return summary;
         }
@@ -234,7 +190,7 @@ namespace MediaBrowser.Api
 
             var result = new QueryResult<BaseItemDto>
             {
-                Items = returnList.ToArray(returnList.Count),
+                Items = returnList,
 
                 TotalRecordCount = itemsResult.Count
             };

+ 2 - 2
MediaBrowser.Api/IHasItemFields.cs

@@ -27,7 +27,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>IEnumerable{ItemFields}.</returns>
-        public static IEnumerable<ItemFields> GetItemFields(this IHasItemFields request)
+        public static ItemFields[] GetItemFields(this IHasItemFields request)
         {
             var val = request.Fields;
 
@@ -46,7 +46,7 @@ namespace MediaBrowser.Api
                 }
                 return null;
 
-            }).Where(i => i.HasValue).Select(i => i.Value);
+            }).Where(i => i.HasValue).Select(i => i.Value).ToArray();
         }
     }
 }

+ 1 - 14
MediaBrowser.Api/Images/ImageService.cs

@@ -556,20 +556,7 @@ namespace MediaBrowser.Api.Images
                 throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type));
             }
 
-            var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.ImageEnhancers.Where(i =>
-            {
-                try
-                {
-                    return i.Supports(item, request.Type);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name);
-
-                    return false;
-                }
-
-            }).ToList() : new List<IImageEnhancer>();
+            var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type) : new List<IImageEnhancer>();
 
             var cropwhitespace = request.Type == ImageType.Logo || 
                 request.Type == ImageType.Art

+ 5 - 5
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -150,7 +150,7 @@ namespace MediaBrowser.Api.Images
 
             }, CancellationToken.None).ConfigureAwait(false);
 
-            var imagesList = images.ToList();
+            var imagesList = images.ToArray();
 
             var allProviders = _providerManager.GetRemoteImageProviderInfo(item);
 
@@ -161,22 +161,22 @@ namespace MediaBrowser.Api.Images
 
             var result = new RemoteImageResult
             {
-                TotalRecordCount = imagesList.Count,
+                TotalRecordCount = imagesList.Length,
                 Providers = allProviders.Select(i => i.Name)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
-                .ToList()
+                .ToArray()
             };
 
             if (request.StartIndex.HasValue)
             {
                 imagesList = imagesList.Skip(request.StartIndex.Value)
-                    .ToList();
+                    .ToArray();
             }
 
             if (request.Limit.HasValue)
             {
                 imagesList = imagesList.Take(request.Limit.Value)
-                    .ToList();
+                    .ToArray();
             }
 
             result.Images = imagesList;

+ 6 - 6
MediaBrowser.Api/ItemUpdateService.cs

@@ -64,8 +64,8 @@ namespace MediaBrowser.Api
 
             var info = new MetadataEditorInfo
             {
-                ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
-                ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToList(),
+                ParentalRatingOptions = _localizationManager.GetParentalRatings(),
+                ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
                 Countries = _localizationManager.GetCountries(),
                 Cultures = _localizationManager.GetCultures()
             };
@@ -78,14 +78,14 @@ namespace MediaBrowser.Api
 
                 if (string.IsNullOrWhiteSpace(inheritedContentType) || !string.IsNullOrWhiteSpace(configuredContentType))
                 {
-                    info.ContentTypeOptions = GetContentTypeOptions(true);
+                    info.ContentTypeOptions = GetContentTypeOptions(true).ToArray();
                     info.ContentType = configuredContentType;
 
                     if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
                     {
                         info.ContentTypeOptions = info.ContentTypeOptions
                             .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
-                            .ToList();
+                            .ToArray();
                     }
                 }
             }
@@ -209,7 +209,7 @@ namespace MediaBrowser.Api
             // Do this first so that metadata savers can pull the updates from the database.
             if (request.People != null)
             {
-                await _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList());
+                _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList());
             }
 
             UpdateItem(request, item);
@@ -352,7 +352,7 @@ namespace MediaBrowser.Api
                     hasArtists.Artists = request
                         .ArtistItems
                         .Select(i => i.Name)
-                        .ToList();
+                        .ToArray();
                 }
             }
 

+ 8 - 8
MediaBrowser.Api/Library/LibraryService.cs

@@ -227,7 +227,7 @@ namespace MediaBrowser.Api.Library
 
     [Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")]
     [Authenticated]
-    public class GetMediaFolders : IReturn<ItemsResult>
+    public class GetMediaFolders : IReturn<QueryResult<BaseItemDto>>
     {
         [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         public bool? IsHidden { get; set; }
@@ -400,7 +400,7 @@ namespace MediaBrowser.Api.Library
                 });
             }
 
-            return new ItemsResult();
+            return new QueryResult<BaseItemDto>();
         }
 
         public object Get(GetMediaFolders request)
@@ -416,7 +416,7 @@ namespace MediaBrowser.Api.Library
 
             var dtoOptions = GetDtoOptions(_authContext, request);
 
-            var result = new ItemsResult
+            var result = new QueryResult<BaseItemDto>
             {
                 TotalRecordCount = items.Count,
 
@@ -525,18 +525,18 @@ namespace MediaBrowser.Api.Library
             });
         }
 
-        private async void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
+        private void LogDownload(BaseItem item, User user, AuthorizationInfo auth)
         {
             try
             {
-                await _activityManager.Create(new ActivityLogEntry
+                _activityManager.Create(new ActivityLogEntry
                 {
                     Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name),
                     Type = "UserDownloadingContent",
                     ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device),
                     UserId = auth.UserId
 
-                }).ConfigureAwait(false);
+                });
             }
             catch
             {
@@ -615,7 +615,7 @@ namespace MediaBrowser.Api.Library
                 parent = parent.GetParent();
             }
 
-            return baseItemDtos.ToList();
+            return baseItemDtos;
         }
 
         private BaseItem TranslateParentItem(BaseItem item, User user)
@@ -915,7 +915,7 @@ namespace MediaBrowser.Api.Library
              : request.IncludeItemTypes.Split(',');
 
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-            
+
             var query = new InternalItemsQuery(user)
             {
                 IncludeItemTypes = includeTypes,

+ 11 - 9
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -648,7 +648,7 @@ namespace MediaBrowser.Api.LiveTv
     {
         public List<TunerChannelMapping> TunerChannels { get; set; }
         public List<NameIdPair> ProviderChannels { get; set; }
-        public List<NameValuePair> Mappings { get; set; }
+        public NameValuePair[] Mappings { get; set; }
         public string ProviderName { get; set; }
     }
 
@@ -789,7 +789,7 @@ namespace MediaBrowser.Api.LiveTv
             var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None)
                      .ConfigureAwait(false);
 
-            var mappings = listingsProviderInfo.ChannelMappings.ToList();
+            var mappings = listingsProviderInfo.ChannelMappings;
 
             var result = new ChannelMappingOptions
             {
@@ -862,7 +862,7 @@ namespace MediaBrowser.Api.LiveTv
         {
             var config = GetConfiguration();
 
-            config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+            config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
 
             _config.SaveConfiguration("livetv", config);
         }
@@ -922,9 +922,8 @@ namespace MediaBrowser.Api.LiveTv
 
             options.AddCurrentProgram = request.AddCurrentProgram;
 
-            var returnList = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user)
+            var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user)
                 .ConfigureAwait(false));
-            var returnArray = returnList.ToArray(returnList.Count);
 
             var result = new QueryResult<BaseItemDto>
             {
@@ -937,10 +936,13 @@ namespace MediaBrowser.Api.LiveTv
 
         private void RemoveFields(DtoOptions options)
         {
-            options.Fields.Remove(ItemFields.CanDelete);
-            options.Fields.Remove(ItemFields.CanDownload);
-            options.Fields.Remove(ItemFields.DisplayPreferencesId);
-            options.Fields.Remove(ItemFields.Etag);
+            var fields = options.Fields.ToList();
+
+            fields.Remove(ItemFields.CanDelete);
+            fields.Remove(ItemFields.CanDownload);
+            fields.Remove(ItemFields.DisplayPreferencesId);
+            fields.Remove(ItemFields.Etag);
+            options.Fields = fields.ToArray(fields.Count);
         }
 
         public object Get(GetChannel request)

+ 6 - 7
MediaBrowser.Api/LocalizationService.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using System.Collections.Generic;
-using System.Linq;
 using MediaBrowser.Model.Services;
 
 namespace MediaBrowser.Api
@@ -11,7 +10,7 @@ namespace MediaBrowser.Api
     /// Class GetCultures
     /// </summary>
     [Route("/Localization/Cultures", "GET", Summary = "Gets known cultures")]
-    public class GetCultures : IReturn<List<CultureDto>>
+    public class GetCultures : IReturn<CultureDto[]>
     {
     }
 
@@ -19,7 +18,7 @@ namespace MediaBrowser.Api
     /// Class GetCountries
     /// </summary>
     [Route("/Localization/Countries", "GET", Summary = "Gets known countries")]
-    public class GetCountries : IReturn<List<CountryInfo>>
+    public class GetCountries : IReturn<CountryInfo[]>
     {
     }
 
@@ -27,7 +26,7 @@ namespace MediaBrowser.Api
     /// Class ParentalRatings
     /// </summary>
     [Route("/Localization/ParentalRatings", "GET", Summary = "Gets known parental ratings")]
-    public class GetParentalRatings : IReturn<List<ParentalRating>>
+    public class GetParentalRatings : IReturn<ParentalRating[]>
     {
     }
 
@@ -35,7 +34,7 @@ namespace MediaBrowser.Api
     /// Class ParentalRatings
     /// </summary>
     [Route("/Localization/Options", "GET", Summary = "Gets localization options")]
-    public class GetLocalizationOptions : IReturn<List<LocalizatonOption>>
+    public class GetLocalizationOptions : IReturn<LocalizatonOption[]>
     {
     }
 
@@ -66,14 +65,14 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetParentalRatings request)
         {
-            var result = _localization.GetParentalRatings().ToList();
+            var result = _localization.GetParentalRatings();
 
             return ToOptimizedResult(result);
         }
 
         public object Get(GetLocalizationOptions request)
         {
-            var result = _localization.GetLocalizationOptions().ToList();
+            var result = _localization.GetLocalizationOptions();
 
             return ToOptimizedResult(result);
         }

+ 0 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -97,7 +97,6 @@
     <Compile Include="Movies\MoviesService.cs" />
     <Compile Include="NewsService.cs" />
     <Compile Include="NotificationsService.cs" />
-    <Compile Include="PackageReviewService.cs" />
     <Compile Include="PackageService.cs" />
     <Compile Include="PluginService.cs" />
     <Compile Include="Images\RemoteImageService.cs" />

+ 4 - 5
MediaBrowser.Api/Movies/CollectionService.cs

@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Collections;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Services;
 
@@ -71,8 +70,8 @@ namespace MediaBrowser.Api.Movies
                 IsLocked = request.IsLocked,
                 Name = request.Name,
                 ParentId = parentId,
-                ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList(),
-                UserIds = new List<Guid> { new Guid(userId) }
+                ItemIdList = SplitValue(request.Ids, ','),
+                UserIds = new string[] { userId }
 
             }).ConfigureAwait(false);
 
@@ -88,14 +87,14 @@ namespace MediaBrowser.Api.Movies
 
         public void Post(AddToCollection request)
         {
-            var task = _collectionManager.AddToCollection(new Guid(request.Id), request.Ids.Split(',').Select(i => new Guid(i)));
+            var task = _collectionManager.AddToCollection(new Guid(request.Id), SplitValue(request.Ids, ','));
 
             Task.WaitAll(task);
         }
 
         public void Delete(RemoveFromCollection request)
         {
-            var task = _collectionManager.RemoveFromCollection(new Guid(request.Id), request.Ids.Split(',').Select(i => new Guid(i)));
+            var task = _collectionManager.RemoveFromCollection(new Guid(request.Id), SplitValue(request.Ids, ','));
 
             Task.WaitAll(task);
         }

+ 5 - 5
MediaBrowser.Api/Movies/MoviesService.cs

@@ -170,7 +170,7 @@ namespace MediaBrowser.Api.Movies
 
             var result = new QueryResult<BaseItemDto>
             {
-                Items = returnList.ToArray(returnList.Count),
+                Items = returnList,
 
                 TotalRecordCount = itemsResult.Count
             };
@@ -320,7 +320,7 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = name,
                         CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
-                        Items = returnItems.ToArray(returnItems.Count)
+                        Items = returnItems
                     };
                 }
             }
@@ -360,7 +360,7 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = name,
                         CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
-                        Items = returnItems.ToArray(returnItems.Count)
+                        Items = returnItems
                     };
                 }
             }
@@ -397,7 +397,7 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = item.Name,
                         CategoryId = item.Id.ToString("N"),
                         RecommendationType = type,
-                        Items = returnItems.ToArray(returnItems.Count)
+                        Items = returnItems
                     };
                 }
             }
@@ -426,7 +426,7 @@ namespace MediaBrowser.Api.Movies
         {
             var people = _libraryManager.GetPeople(new InternalPeopleQuery
             {
-                PersonTypes = new List<string>
+                PersonTypes = new string[]
                 {
                     PersonType.Director
                 }

+ 2 - 1
MediaBrowser.Api/Movies/TrailersService.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Controller.Collections;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Services;
@@ -11,7 +12,7 @@ using MediaBrowser.Model.Services;
 namespace MediaBrowser.Api.Movies
 {
     [Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")]
-    public class Getrailers : BaseItemsRequest, IReturn<ItemsResult>
+    public class Getrailers : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>>
     {
     }
 

+ 9 - 3
MediaBrowser.Api/Music/InstantMixService.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Extensions;
 
@@ -185,15 +186,20 @@ namespace MediaBrowser.Api.Music
         {
             var list = items;
 
-            var result = new ItemsResult
+            var result = new QueryResult<BaseItemDto>
             {
                 TotalRecordCount = list.Count
             };
 
-            var returnList = (await _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user)
+            if (request.Limit.HasValue)
+            {
+                list = list.Take(request.Limit.Value).ToList();
+            }
+
+            var returnList = (await _dtoService.GetBaseItemDtos(list, dtoOptions, user)
                 .ConfigureAwait(false));
 
-            result.Items = returnList.ToArray(returnList.Count);
+            result.Items = returnList;
 
             return ToOptimizedResult(result);
         }

+ 1 - 1
MediaBrowser.Api/NotificationsService.cs

@@ -99,7 +99,7 @@ namespace MediaBrowser.Api
 
         public object Get(GetNotificationTypes request)
         {
-            var result = _notificationManager.GetNotificationTypes().ToList();
+            var result = _notificationManager.GetNotificationTypes();
 
             return ToOptimizedResult(result);
         }

+ 0 - 162
MediaBrowser.Api/PackageReviewService.cs

@@ -1,162 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Serialization;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
-
-namespace MediaBrowser.Api
-{
-    /// <summary>
-    /// Class InstallPackage
-    /// </summary>
-    [Route("/Packages/Reviews/{Id}", "POST", Summary = "Creates or updates a package review")]
-    public class CreateReviewRequest : IReturnVoid
-    {
-        /// <summary>
-        /// Gets or sets the Id.
-        /// </summary>
-        /// <value>The Id.</value>
-        [ApiMember(Name = "Id", Description = "Package Id", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "POST")]
-        public int Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the rating.
-        /// </summary>
-        /// <value>The review.</value>
-        [ApiMember(Name = "Rating", Description = "The rating value (1-5)", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "POST")]
-        public int Rating { get; set; }
-
-        /// <summary>
-        /// Gets or sets the recommend value.
-        /// </summary>
-        /// <value>Whether or not this review recommends this item.</value>
-        [ApiMember(Name = "Recommend", Description = "Whether or not this review recommends this item", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
-        public bool Recommend { get; set; }
-
-        /// <summary>
-        /// Gets or sets the title.
-        /// </summary>
-        /// <value>The title.</value>
-        [ApiMember(Name = "Title", Description = "Optional short description of review.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Title { get; set; }
-
-        /// <summary>
-        /// Gets or sets the full review.
-        /// </summary>
-        /// <value>The full review.</value>
-        [ApiMember(Name = "Review", Description = "Optional full review.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string Review { get; set; }
-    }
-
-    /// <summary>
-    /// Class InstallPackage
-    /// </summary>
-    [Route("/Packages/{Id}/Reviews", "GET", Summary = "Gets reviews for a package")]
-    public class ReviewRequest : IReturn<List<PackageReviewInfo>>
-    {
-        /// <summary>
-        /// Gets or sets the Id.
-        /// </summary>
-        /// <value>The Id.</value>
-        [ApiMember(Name = "Id", Description = "Package Id", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
-        public int Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the max rating.
-        /// </summary>
-        /// <value>The max rating.</value>
-        [ApiMember(Name = "MaxRating", Description = "Retrieve only reviews less than or equal to this", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int MaxRating { get; set; }
-
-        /// <summary>
-        /// Gets or sets the min rating.
-        /// </summary>
-        /// <value>The max rating.</value>
-        [ApiMember(Name = "MinRating", Description = "Retrieve only reviews greator than or equal to this", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int MinRating { get; set; }
-
-        /// <summary>
-        /// Only retrieve reviews with at least a short review.
-        /// </summary>
-        /// <value>True if should only get reviews with a title.</value>
-        [ApiMember(Name = "ForceTitle", Description = "Whether or not to restrict results to those with a title", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool ForceTitle { get; set; }
-
-        /// <summary>
-        /// Gets or sets the limit for the query.
-        /// </summary>
-        /// <value>The max rating.</value>
-        [ApiMember(Name = "Limit", Description = "Limit the result to this many reviews (ordered by latest)", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int Limit { get; set; }
-
-    }
-
-    [Authenticated]
-    public class PackageReviewService : BaseApiService
-    {
-        private readonly IHttpClient _httpClient;
-        private readonly IJsonSerializer _serializer;
-        private const string MbAdminUrl = "https://www.mb3admin.com/admin/";
-        private readonly IServerApplicationHost _appHost;
-
-        public PackageReviewService(IHttpClient httpClient, IJsonSerializer serializer, IServerApplicationHost appHost)
-        {
-            _httpClient = httpClient;
-            _serializer = serializer;
-            _appHost = appHost;
-        }
-
-        public async Task<object> Get(ReviewRequest request)
-        {
-            var parms = "?id=" + request.Id;
-
-            if (request.MaxRating > 0)
-            {
-                parms += "&max=" + request.MaxRating;
-            }
-            if (request.MinRating > 0)
-            {
-                parms += "&min=" + request.MinRating;
-            }
-            if (request.MinRating > 0)
-            {
-                parms += "&limit=" + request.Limit;
-            }
-            if (request.ForceTitle)
-            {
-                parms += "&title=true";
-            }
-
-            using (var result = await _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None)
-                            .ConfigureAwait(false))
-            {
-                var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
-
-                return ToOptimizedResult(reviews);
-            }
-        }
-
-        public void Post(CreateReviewRequest request)
-        {
-            var reviewText = WebUtility.HtmlEncode(request.Review ?? string.Empty);
-            var title = WebUtility.HtmlEncode(request.Title ?? string.Empty);
-
-            var review = new Dictionary<string, string>
-                             { { "id", request.Id.ToString(CultureInfo.InvariantCulture) },
-                               { "mac", _appHost.SystemId },
-                               { "rating", request.Rating.ToString(CultureInfo.InvariantCulture) },
-                               { "recommend", request.Recommend.ToString() },
-                               { "title", title },
-                               { "review", reviewText },
-                             };
-
-            Task.WaitAll(_httpClient.Post(MbAdminUrl + "/service/packageReview/update", review, CancellationToken.None));
-        }
-    }
-}

+ 14 - 13
MediaBrowser.Api/PackageService.cs

@@ -40,7 +40,7 @@ namespace MediaBrowser.Api
     /// </summary>
     [Route("/Packages", "GET", Summary = "Gets available packages")]
     [Authenticated]
-    public class GetPackages : IReturn<List<PackageInfo>>
+    public class GetPackages : IReturn<PackageInfo[]>
     {
         /// <summary>
         /// Gets or sets the name.
@@ -66,7 +66,7 @@ namespace MediaBrowser.Api
     /// </summary>
     [Route("/Packages/Updates", "GET", Summary = "Gets available package updates for currently installed packages")]
     [Authenticated(Roles = "Admin")]
-    public class GetPackageVersionUpdates : IReturn<List<PackageVersionInfo>>
+    public class GetPackageVersionUpdates : IReturn<PackageVersionInfo[]>
     {
         /// <summary>
         /// Gets or sets the name.
@@ -148,24 +148,26 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetPackageVersionUpdates request)
         {
-            var result = new List<PackageVersionInfo>();
+            PackageVersionInfo[] result = null;
 
             if (string.Equals(request.PackageType, "UserInstalled", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
             {
-                result.AddRange(_installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToList());
+                result = _installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToArray();
             }
 
-            else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
+            else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) ||
+                     string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase))
             {
-                var updateCheckResult = _appHost.CheckForApplicationUpdate(CancellationToken.None, new SimpleProgress<double>()).Result;
+                var updateCheckResult = _appHost
+                    .CheckForApplicationUpdate(CancellationToken.None, new SimpleProgress<double>()).Result;
 
                 if (updateCheckResult.IsUpdateAvailable)
                 {
-                    result.Add(updateCheckResult.Package);
+                    result = new PackageVersionInfo[] {updateCheckResult.Package};
                 }
             }
 
-            return ToOptimizedResult(result);
+            return ToOptimizedResult(result ?? new PackageVersionInfo[] { });
         }
 
         /// <summary>
@@ -176,10 +178,9 @@ namespace MediaBrowser.Api
         public object Get(GetPackage request)
         {
             var packages = _installationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: _appHost.ApplicationVersion).Result;
-            var list = packages.ToList();
 
-            var result = list.FirstOrDefault(p => string.Equals(p.guid, request.AssemblyGuid ?? "none", StringComparison.OrdinalIgnoreCase))
-                         ?? list.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
+            var result = packages.FirstOrDefault(p => string.Equals(p.guid, request.AssemblyGuid ?? "none", StringComparison.OrdinalIgnoreCase))
+                         ?? packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
 
             return ToOptimizedResult(result);
         }
@@ -191,7 +192,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetPackages request)
         {
-            var packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false);
+            IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false);
 
             if (!string.IsNullOrEmpty(request.TargetSystems))
             {
@@ -215,7 +216,7 @@ namespace MediaBrowser.Api
                 packages = packages.Where(p => p.enableInAppStore == request.IsAppStoreEnabled.Value);
             }
 
-            return ToOptimizedResult(packages.ToList());
+            return ToOptimizedResult(packages.ToArray());
         }
 
         /// <summary>

+ 5 - 7
MediaBrowser.Api/PlaylistService.cs

@@ -1,11 +1,11 @@
-using MediaBrowser.Controller.Dto;
+using System.Linq;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Playlists;
 using MediaBrowser.Model.Querying;
-using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Extensions;
@@ -149,7 +149,7 @@ namespace MediaBrowser.Api
             var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
             {
                 Name = request.Name,
-                ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(),
+                ItemIdList = SplitValue(request.Ids, ','),
                 UserId = request.UserId,
                 MediaType = request.MediaType
 
@@ -193,10 +193,8 @@ namespace MediaBrowser.Api
 
             var dtoOptions = GetDtoOptions(_authContext, request);
 
-            var returnList = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user)
+            var dtos = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user)
                 .ConfigureAwait(false));
-            var dtos = returnList
-                   .ToArray(returnList.Count);
 
             var index = 0;
             foreach (var item in dtos)
@@ -205,7 +203,7 @@ namespace MediaBrowser.Api
                 index++;
             }
 
-            var result = new ItemsResult
+            var result = new QueryResult<BaseItemDto>
             {
                 Items = dtos,
                 TotalRecordCount = count

+ 5 - 6
MediaBrowser.Api/PluginService.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.Api
     /// </summary>
     [Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")]
     [Authenticated]
-    public class GetPlugins : IReturn<List<PluginInfo>>
+    public class GetPlugins : IReturn<PluginInfo[]>
     {
         public bool? IsAppStoreEnabled { get; set; }
     }
@@ -195,14 +195,13 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetPlugins request)
         {
-            var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList();
+            var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToArray();
             var requireAppStoreEnabled = request.IsAppStoreEnabled.HasValue && request.IsAppStoreEnabled.Value;
 
             // Don't fail just on account of image url's
             try
             {
-                var packages = (await _installationManager.GetAvailablePackagesWithoutRegistrationInfo(CancellationToken.None))
-                    .ToList();
+                var packages = (await _installationManager.GetAvailablePackagesWithoutRegistrationInfo(CancellationToken.None));
 
                 foreach (var plugin in result)
                 {
@@ -223,7 +222,7 @@ namespace MediaBrowser.Api
                             return pkg != null && pkg.enableInAppStore;
                   
                         })
-                        .ToList();
+                        .ToArray();
                 }
             }
             catch
@@ -232,7 +231,7 @@ namespace MediaBrowser.Api
                 // Play it safe here
                 if (requireAppStoreEnabled)
                 {
-                    result = new List<PluginInfo>();
+                    result = new PluginInfo[] { };
                 }
             }
 

+ 1 - 1
MediaBrowser.Api/Reports/Data/ReportBuilder.cs

@@ -533,7 +533,7 @@ namespace MediaBrowser.Api.Reports
                     break;
 
                 case HeaderMetadata.Tracks:
-                    option.Column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.ToList(), new List<Audio>()).Count();
+                    option.Column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.Cast<Audio>().ToList(), new List<Audio>()).Count();
                     break;
 
                 case HeaderMetadata.Audio:

+ 2 - 2
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -27,7 +27,7 @@ namespace MediaBrowser.Api.ScheduledTasks
     /// Class GetScheduledTasks
     /// </summary>
     [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")]
-    public class GetScheduledTasks : IReturn<List<TaskInfo>>
+    public class GetScheduledTasks : IReturn<TaskInfo[]>
     {
         [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsHidden { get; set; }
@@ -158,7 +158,7 @@ namespace MediaBrowser.Api.ScheduledTasks
             
             var infos = result
                 .Select(ScheduledTaskHelpers.GetTaskInfo)
-                .ToList();
+                .ToArray();
 
             return ToOptimizedResult(infos);
         }

+ 4 - 4
MediaBrowser.Api/SearchService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Drawing;
+using System.Linq;
+using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -7,7 +8,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Search;
-using System.Linq;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Services;
@@ -240,7 +240,7 @@ namespace MediaBrowser.Api
 
             if (album != null)
             {
-                result.Artists = album.Artists.ToArray();
+                result.Artists = album.Artists;
                 result.AlbumArtist = album.AlbumArtist;
             }
 
@@ -250,7 +250,7 @@ namespace MediaBrowser.Api
             {
                 result.Album = song.Album;
                 result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
-                result.Artists = song.Artists.ToArray();
+                result.Artists = song.Artists;
             }
 
             if (!string.IsNullOrWhiteSpace(item.ChannelId))

+ 6 - 9
MediaBrowser.Api/Session/SessionsService.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.Api.Session
     /// </summary>
     [Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
     [Authenticated]
-    public class GetSessions : IReturn<List<SessionInfoDto>>
+    public class GetSessions : IReturn<SessionInfoDto[]>
     {
         [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string ControllableByUserId { get; set; }
@@ -313,14 +313,13 @@ namespace MediaBrowser.Api.Session
 
         public void Delete(RevokeKey request)
         {
-            var task = _sessionManager.RevokeToken(request.Key);
+            _sessionManager.RevokeToken(request.Key);
 
-            Task.WaitAll(task);
         }
 
         public void Post(CreateKey request)
         {
-            var task = _authRepo.Create(new AuthenticationInfo
+            _authRepo.Create(new AuthenticationInfo
             {
                 AppName = request.App,
                 IsActive = true,
@@ -328,8 +327,6 @@ namespace MediaBrowser.Api.Session
                 DateCreated = DateTime.UtcNow
 
             }, CancellationToken.None);
-
-            Task.WaitAll(task);
         }
 
         public void Post(ReportSessionEnded request)
@@ -396,7 +393,7 @@ namespace MediaBrowser.Api.Session
                 });
             }
 
-            return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList());
+            return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToArray());
         }
 
         public void Post(SendPlaystateCommand request)
@@ -532,9 +529,9 @@ namespace MediaBrowser.Api.Session
             }
             _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
             {
-                PlayableMediaTypes = (request.PlayableMediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+                PlayableMediaTypes = SplitValue(request.PlayableMediaTypes, ','),
 
-                SupportedCommands = (request.SupportedCommands ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
+                SupportedCommands = SplitValue(request.SupportedCommands, ','),
 
                 SupportsMediaControl = request.SupportsMediaControl,
 

+ 4 - 4
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -30,7 +30,7 @@ namespace MediaBrowser.Api
         public string ExcludeArtistIds { get; set; }
     }
 
-    public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasDtoOptions
+    public class BaseGetSimilarItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
     {
         [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         public bool? EnableImages { get; set; }
@@ -97,18 +97,18 @@ namespace MediaBrowser.Api
             var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
                 .ToList();
 
-            IEnumerable<BaseItem> returnItems = items;
+            List<BaseItem> returnItems = items;
 
             if (request.Limit.HasValue)
             {
-                returnItems = returnItems.Take(request.Limit.Value);
+                returnItems = returnItems.Take(request.Limit.Value).ToList();
             }
 
             var dtos = await dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ConfigureAwait(false);
 
             return new QueryResult<BaseItemDto>
             {
-                Items = dtos.ToArray(dtos.Count),
+                Items = dtos,
 
                 TotalRecordCount = items.Count
             };

+ 2 - 3
MediaBrowser.Api/Social/SharingService.cs

@@ -121,8 +121,7 @@ namespace MediaBrowser.Api.Social
 
         public void Delete(DeleteShare request)
         {
-            var task = _sharingManager.DeleteShare(request.Id);
-            Task.WaitAll(task);
+            _sharingManager.DeleteShare(request.Id);
         }
 
         public async Task<object> Get(GetShareImage request)
@@ -157,7 +156,7 @@ namespace MediaBrowser.Api.Social
                 }
                 catch
                 {
-                    
+
                 }
             }
 

Some files were not shown because too many files changed in this diff