فهرست منبع

Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser

Eric Reed 11 سال پیش
والد
کامیت
c02c0db35a
100فایلهای تغییر یافته به همراه2484 افزوده شده و 1309 حذف شده
  1. 1 0
      CONTRIBUTORS.md
  2. 45 5
      MediaBrowser.Api/ApiEntryPoint.cs
  3. 181 0
      MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
  4. 1 146
      MediaBrowser.Api/BaseApiService.cs
  5. 22 19
      MediaBrowser.Api/MediaBrowser.Api.csproj
  6. 1 1
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  7. 0 36
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  8. 0 24
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  9. 53 0
      MediaBrowser.Api/Playback/Hls/HlsSegmentResponseFilter.cs
  10. 147 0
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  11. 1 66
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  12. 31 227
      MediaBrowser.Api/SessionsService.cs
  13. 45 15
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  14. 87 0
      MediaBrowser.Common.Implementations/Archiving/ZipClient.cs
  15. 29 30
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  16. 7 11
      MediaBrowser.Common.Implementations/Logging/NlogManager.cs
  17. 16 13
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  18. 13 2
      MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
  19. 18 19
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  20. 5 1
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
  21. 1 0
      MediaBrowser.Common.Implementations/packages.config
  22. 9 14
      MediaBrowser.Common/MediaBrowser.Common.csproj
  23. 7 0
      MediaBrowser.Common/ScheduledTasks/ITaskManager.cs
  24. 26 3
      MediaBrowser.Controller/Entities/Folder.cs
  25. 12 6
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  26. 1 1
      MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
  27. 56 4
      MediaBrowser.Controller/Session/ISessionManager.cs
  28. 61 0
      MediaBrowser.Controller/Session/ISessionRemoteController.cs
  29. 38 0
      MediaBrowser.Controller/Session/PlaybackInfo.cs
  30. 16 3
      MediaBrowser.Controller/Session/SessionInfo.cs
  31. 3 1
      MediaBrowser.Model/ApiClient/IApiClient.cs
  32. 16 0
      MediaBrowser.Model/IO/IZipClient.cs
  33. 6 5
      MediaBrowser.Model/MediaBrowser.Model.csproj
  34. 15 2
      MediaBrowser.Model/Session/SessionInfoDto.cs
  35. 68 0
      MediaBrowser.Mono.sln
  36. 19 0
      MediaBrowser.Mono.userprefs
  37. 8 7
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  38. 1 0
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
  39. 0 1
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  40. 3 1
      MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs
  41. 3 2
      MediaBrowser.Providers/Savers/AlbumXmlSaver.cs
  42. 3 2
      MediaBrowser.Providers/Savers/ArtistXmlSaver.cs
  43. 3 2
      MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs
  44. 3 2
      MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
  45. 2 1
      MediaBrowser.Providers/Savers/FolderXmlSaver.cs
  46. 4 3
      MediaBrowser.Providers/Savers/GameXmlSaver.cs
  47. 3 2
      MediaBrowser.Providers/Savers/MovieXmlSaver.cs
  48. 3 2
      MediaBrowser.Providers/Savers/PersonXmlSaver.cs
  49. 2 1
      MediaBrowser.Providers/Savers/SeasonXmlSaver.cs
  50. 7 3
      MediaBrowser.Providers/Savers/SeriesXmlSaver.cs
  51. 40 13
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  52. 3 1
      MediaBrowser.Providers/TV/SeriesPostScanTask.cs
  53. 56 38
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  54. 3 1
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  55. 4 2
      MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
  56. 11 4
      MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
  57. 16 38
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  58. 4 2
      MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
  59. 4 6
      MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
  60. 4 6
      MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
  61. 3 1
      MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
  62. 4 4
      MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
  63. 39 47
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  64. 1 1
      MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
  65. 7 8
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  66. 12 0
      MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
  67. 4 0
      MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
  68. 140 11
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  69. 42 10
      MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
  70. 92 0
      MediaBrowser.Server.Implementations/Session/WebSocketController.cs
  71. 3 1
      MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs
  72. 36 0
      MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloader.cs
  73. 16 0
      MediaBrowser.Server.Mono/MainWindow.cs
  74. 119 0
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  75. 22 0
      MediaBrowser.Server.Mono/Native/Assemblies.cs
  76. 20 0
      MediaBrowser.Server.Mono/Native/Autorun.cs
  77. 25 0
      MediaBrowser.Server.Mono/Native/HttpMessageHandlerFactory.cs
  78. 25 0
      MediaBrowser.Server.Mono/Native/NativeApp.cs
  79. 26 0
      MediaBrowser.Server.Mono/Native/ServerAuthorization.cs
  80. 36 0
      MediaBrowser.Server.Mono/Native/Sqlite.cs
  81. 16 0
      MediaBrowser.Server.Mono/Program.cs
  82. 22 0
      MediaBrowser.Server.Mono/Properties/AssemblyInfo.cs
  83. 20 0
      MediaBrowser.Server.Mono/gtk-gui/MainWindow.cs
  84. 29 0
      MediaBrowser.Server.Mono/gtk-gui/generated.cs
  85. 20 0
      MediaBrowser.Server.Mono/gtk-gui/gui.stetic
  86. 0 53
      MediaBrowser.ServerApplication/App.xaml.cs
  87. 46 97
      MediaBrowser.ServerApplication/ApplicationHost.cs
  88. 8 6
      MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs
  89. 302 0
      MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
  90. 24 0
      MediaBrowser.ServerApplication/FFMpeg/FFMpegInfo.cs
  91. 1 0
      MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id
  92. 0 43
      MediaBrowser.ServerApplication/Implementations/DotNetZipClient.cs
  93. 0 205
      MediaBrowser.ServerApplication/Implementations/FFMpegDownloader.cs
  94. 0 1
      MediaBrowser.ServerApplication/Implementations/ffmpeg20130904.zip.REMOVED.git-id
  95. 0 5
      MediaBrowser.ServerApplication/Implementations/readme.txt
  96. 0 1
      MediaBrowser.ServerApplication/MainStartup.cs
  97. 9 8
      MediaBrowser.ServerApplication/MainWindow.xaml.cs
  98. 12 13
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  99. 25 0
      MediaBrowser.ServerApplication/Native/Assemblies.cs
  100. 31 0
      MediaBrowser.ServerApplication/Native/Autorun.cs

+ 1 - 0
CONTRIBUTORS.md

@@ -57,3 +57,4 @@
  - [Detector1](https://github.com/Detector1)
  - [BlackIce013](https://github.com/blackice013)
  - [mporcas] (https://github.com/mporcas)
+ - [tikuf] (https://github.com/tikuf/)

+ 45 - 5
MediaBrowser.Api/ApiEntryPoint.cs

@@ -62,7 +62,7 @@ namespace MediaBrowser.Api
         {
             var jobCount = _activeTranscodingJobs.Count;
 
-            Parallel.ForEach(_activeTranscodingJobs, OnTranscodeKillTimerStopped);
+            Parallel.ForEach(_activeTranscodingJobs, KillTranscodingJob);
 
             // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
             if (jobCount > 0)
@@ -84,7 +84,8 @@ namespace MediaBrowser.Api
         /// <param name="process">The process.</param>
         /// <param name="isVideo">if set to <c>true</c> [is video].</param>
         /// <param name="startTimeTicks">The start time ticks.</param>
-        public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, bool isVideo, long? startTimeTicks)
+        /// <param name="sourcePath">The source path.</param>
+        public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, bool isVideo, long? startTimeTicks, string sourcePath)
         {
             lock (_activeTranscodingJobs)
             {
@@ -95,7 +96,8 @@ namespace MediaBrowser.Api
                     Process = process,
                     ActiveRequestCount = 1,
                     IsVideo = isVideo,
-                    StartTimeTicks = startTimeTicks
+                    StartTimeTicks = startTimeTicks,
+                    SourcePath = sourcePath
                 });
             }
         }
@@ -178,7 +180,7 @@ namespace MediaBrowser.Api
 
                 if (job.ActiveRequestCount == 0)
                 {
-                    var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 60000;
+                    var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 180000;
 
                     if (job.KillTimer == null)
                     {
@@ -196,10 +198,47 @@ namespace MediaBrowser.Api
         /// Called when [transcode kill timer stopped].
         /// </summary>
         /// <param name="state">The state.</param>
-        private async void OnTranscodeKillTimerStopped(object state)
+        private void OnTranscodeKillTimerStopped(object state)
         {
             var job = (TranscodingJob)state;
 
+            KillTranscodingJob(job);
+        }
+
+        /// <summary>
+        /// Kills the single transcoding job.
+        /// </summary>
+        /// <param name="sourcePath">The source path.</param>
+        internal void KillSingleTranscodingJob(string sourcePath)
+        {
+            if (string.IsNullOrEmpty(sourcePath))
+            {
+                throw new ArgumentNullException("sourcePath");
+            }
+
+            var jobs = new List<TranscodingJob>();
+
+            lock (_activeTranscodingJobs)
+            {
+                // This is really only needed for HLS. 
+                // Progressive streams can stop on their own reliably
+                jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(sourcePath, i.SourcePath) && i.Type == TranscodingJobType.Hls));
+            }
+
+            // This method of killing is a bit of a shortcut, but it saves clients from having to send a request just for that
+            // But we can only kill if there's one active job. If there are more we won't know which one to stop
+            if (jobs.Count == 1)
+            {
+                KillTranscodingJob(jobs.First());
+            }
+        }
+
+        /// <summary>
+        /// Kills the transcoding job.
+        /// </summary>
+        /// <param name="job">The job.</param>
+        private async void KillTranscodingJob(TranscodingJob job)
+        {
             lock (_activeTranscodingJobs)
             {
                 _activeTranscodingJobs.Remove(job);
@@ -373,6 +412,7 @@ namespace MediaBrowser.Api
 
         public bool IsVideo { get; set; }
         public long? StartTimeTicks { get; set; }
+        public string SourcePath { get; set; }
     }
 
     /// <summary>

+ 181 - 0
MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs

@@ -0,0 +1,181 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+using ServiceStack.Common.Web;
+using ServiceStack.ServiceHost;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Api
+{
+    public class AuthorizationRequestFilterAttribute : Attribute, IHasRequestFilter
+    {
+        //This property will be resolved by the IoC container
+        /// <summary>
+        /// Gets or sets the user manager.
+        /// </summary>
+        /// <value>The user manager.</value>
+        public IUserManager UserManager { get; set; }
+
+        public ISessionManager SessionManager { get; set; }
+
+        /// <summary>
+        /// Gets or sets the logger.
+        /// </summary>
+        /// <value>The logger.</value>
+        public ILogger Logger { get; set; }
+
+        /// <summary>
+        /// The request filter is executed before the service.
+        /// </summary>
+        /// <param name="request">The http request wrapper</param>
+        /// <param name="response">The http response wrapper</param>
+        /// <param name="requestDto">The request DTO</param>
+        public void RequestFilter(IHttpRequest request, IHttpResponse response, object requestDto)
+        {
+            //This code is executed before the service
+
+            var auth = GetAuthorization(request);
+
+            if (auth != null)
+            {
+                User user = null;
+
+                if (auth.ContainsKey("UserId"))
+                {
+                    var userId = auth["UserId"];
+
+                    if (!string.IsNullOrEmpty(userId))
+                    {
+                        user = UserManager.GetUserById(new Guid(userId));
+                    }
+                }
+
+                string deviceId;
+                string device;
+                string client;
+                string version;
+
+                auth.TryGetValue("DeviceId", out deviceId);
+                auth.TryGetValue("Device", out device);
+                auth.TryGetValue("Client", out client);
+                auth.TryGetValue("Version", out version);
+
+                if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version))
+                {
+                    SessionManager.LogConnectionActivity(client, version, deviceId, device, user);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the auth.
+        /// </summary>
+        /// <param name="httpReq">The HTTP req.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        public static Dictionary<string, string> GetAuthorization(IHttpRequest httpReq)
+        {
+            var auth = httpReq.Headers[HttpHeaders.Authorization];
+
+            return GetAuthorization(auth);
+        }
+
+        /// <summary>
+        /// Gets the authorization.
+        /// </summary>
+        /// <param name="httpReq">The HTTP req.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        public static AuthorizationInfo GetAuthorization(IRequestContext httpReq)
+        {
+            var header = httpReq.GetHeader("Authorization");
+
+            var auth = GetAuthorization(header);
+
+            string userId;
+            string deviceId;
+            string device;
+            string client;
+            string version;
+
+            auth.TryGetValue("UserId", out userId);
+            auth.TryGetValue("DeviceId", out deviceId);
+            auth.TryGetValue("Device", out device);
+            auth.TryGetValue("Client", out client);
+            auth.TryGetValue("Version", out version);
+
+            return new AuthorizationInfo
+            {
+                Client = client,
+                Device = device,
+                DeviceId = deviceId,
+                UserId = userId,
+                Version = version
+            };
+        }
+
+        /// <summary>
+        /// Gets the authorization.
+        /// </summary>
+        /// <param name="authorizationHeader">The authorization header.</param>
+        /// <returns>Dictionary{System.StringSystem.String}.</returns>
+        private static Dictionary<string, string> GetAuthorization(string authorizationHeader)
+        {
+            if (authorizationHeader == null) return null;
+
+            var parts = authorizationHeader.Split(' ');
+
+            // There should be at least to parts
+            if (parts.Length < 2) return null;
+
+            // It has to be a digest request
+            if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase))
+            {
+                return null;
+            }
+
+            // Remove uptil the first space
+            authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' '));
+            parts = authorizationHeader.Split(',');
+
+            var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+            foreach (var item in parts)
+            {
+                var param = item.Trim().Split(new[] { '=' }, 2);
+                result.Add(param[0], param[1].Trim(new[] { '"' }));
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// A new shallow copy of this filter is used on every request.
+        /// </summary>
+        /// <returns>IHasRequestFilter.</returns>
+        public IHasRequestFilter Copy()
+        {
+            return this;
+        }
+
+        /// <summary>
+        /// Order in which Request Filters are executed.
+        /// &lt;0 Executed before global request filters
+        /// &gt;0 Executed after global request filters
+        /// </summary>
+        /// <value>The priority.</value>
+        public int Priority
+        {
+            get { return 0; }
+        }
+    }
+
+    public class AuthorizationInfo
+    {
+        public string UserId;
+        public string DeviceId;
+        public string Device;
+        public string Client;
+        public string Version;
+    }
+}

+ 1 - 146
MediaBrowser.Api/BaseApiService.cs

@@ -2,9 +2,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Logging;
-using ServiceStack.Common.Web;
 using ServiceStack.ServiceHost;
 using System;
 using System.Collections.Generic;
@@ -15,7 +13,7 @@ namespace MediaBrowser.Api
     /// <summary>
     /// Class BaseApiService
     /// </summary>
-    [RequestFilter]
+    [AuthorizationRequestFilter]
     public class BaseApiService : IHasResultFactory, IRestfulService
     {
         /// <summary>
@@ -308,147 +306,4 @@ namespace MediaBrowser.Api
             return item;
         }
     }
-
-    /// <summary>
-    /// Class RequestFilterAttribute
-    /// </summary>
-    public class RequestFilterAttribute : Attribute, IHasRequestFilter
-    {
-        //This property will be resolved by the IoC container
-        /// <summary>
-        /// Gets or sets the user manager.
-        /// </summary>
-        /// <value>The user manager.</value>
-        public IUserManager UserManager { get; set; }
-
-        public ISessionManager SessionManager { get; set; }
-
-        /// <summary>
-        /// Gets or sets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        public ILogger Logger { get; set; }
-
-        /// <summary>
-        /// The request filter is executed before the service.
-        /// </summary>
-        /// <param name="request">The http request wrapper</param>
-        /// <param name="response">The http response wrapper</param>
-        /// <param name="requestDto">The request DTO</param>
-        public void RequestFilter(IHttpRequest request, IHttpResponse response, object requestDto)
-        {
-            //This code is executed before the service
-
-            var auth = GetAuthorization(request);
-
-            if (auth != null)
-            {
-                User user = null;
-
-                if (auth.ContainsKey("UserId"))
-                {
-                    var userId = auth["UserId"];
-
-                    if (!string.IsNullOrEmpty(userId))
-                    {
-                        user = UserManager.GetUserById(new Guid(userId));
-                    }
-                }
-
-                string deviceId;
-                string device;
-                string client;
-                string version;
-
-                auth.TryGetValue("DeviceId", out deviceId);
-                auth.TryGetValue("Device", out device);
-                auth.TryGetValue("Client", out client);
-                auth.TryGetValue("Version", out version);
-
-                if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version))
-                {
-                    SessionManager.LogConnectionActivity(client, version, deviceId, device, user);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the auth.
-        /// </summary>
-        /// <param name="httpReq">The HTTP req.</param>
-        /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        public static Dictionary<string, string> GetAuthorization(IHttpRequest httpReq)
-        {
-            var auth = httpReq.Headers[HttpHeaders.Authorization];
-
-            return GetAuthorization(auth);
-        }
-
-        /// <summary>
-        /// Gets the authorization.
-        /// </summary>
-        /// <param name="httpReq">The HTTP req.</param>
-        /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        public static Dictionary<string, string> GetAuthorization(IRequestContext httpReq)
-        {
-            var auth = httpReq.GetHeader("Authorization");
-
-            return GetAuthorization(auth);
-        }
-
-        /// <summary>
-        /// Gets the authorization.
-        /// </summary>
-        /// <param name="authorizationHeader">The authorization header.</param>
-        /// <returns>Dictionary{System.StringSystem.String}.</returns>
-        private static Dictionary<string, string> GetAuthorization(string authorizationHeader)
-        {
-            if (authorizationHeader == null) return null;
-
-            var parts = authorizationHeader.Split(' ');
-
-            // There should be at least to parts
-            if (parts.Length < 2) return null;
-
-            // It has to be a digest request
-            if (!string.Equals(parts[0], "MediaBrowser", StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-
-            // Remove uptil the first space
-            authorizationHeader = authorizationHeader.Substring(authorizationHeader.IndexOf(' '));
-            parts = authorizationHeader.Split(',');
-
-            var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
-            foreach (var item in parts)
-            {
-                var param = item.Trim().Split(new[] { '=' }, 2);
-                result.Add(param[0], param[1].Trim(new[] { '"' }));
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// A new shallow copy of this filter is used on every request.
-        /// </summary>
-        /// <returns>IHasRequestFilter.</returns>
-        public IHasRequestFilter Copy()
-        {
-            return this;
-        }
-
-        /// <summary>
-        /// Order in which Request Filters are executed.
-        /// &lt;0 Executed before global request filters
-        /// &gt;0 Executed after global request filters
-        /// </summary>
-        /// <value>The priority.</value>
-        public int Priority
-        {
-            get { return 0; }
-        }
-    }
 }

+ 22 - 19
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -13,6 +13,8 @@
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -36,29 +38,25 @@
     <RunPostBuildEvent>Always</RunPostBuildEvent>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Common, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Common">
       <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Interfaces, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Interfaces">
       <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Text">
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
     </Reference>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Drawing" />
-    <Reference Include="System.Net.Http" />
-    <Reference Include="System.XML" />
+    <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
@@ -70,6 +68,7 @@
     <Compile Include="DefaultTheme\Models.cs" />
     <Compile Include="DisplayPreferencesService.cs" />
     <Compile Include="EnvironmentService.cs" />
+    <Compile Include="AuthorizationRequestFilterAttribute.cs" />
     <Compile Include="GamesService.cs" />
     <Compile Include="Images\ImageByNameService.cs" />
     <Compile Include="Images\ImageRequest.cs" />
@@ -88,6 +87,8 @@
     <Compile Include="PackageService.cs" />
     <Compile Include="Playback\Hls\AudioHlsService.cs" />
     <Compile Include="Playback\Hls\BaseHlsService.cs" />
+    <Compile Include="Playback\Hls\HlsSegmentResponseFilter.cs" />
+    <Compile Include="Playback\Hls\HlsSegmentService.cs" />
     <Compile Include="Playback\Hls\VideoHlsService.cs" />
     <Compile Include="Playback\Progressive\AudioService.cs" />
     <Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
@@ -128,22 +129,24 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
-      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
       <Name>MediaBrowser.Common</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
-      <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+      <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
       <Name>MediaBrowser.Controller</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
-      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
   </ItemGroup>
-  <ItemGroup />
+  <ItemGroup>
+    <Folder Include="Filters\" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>

+ 1 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -613,7 +613,7 @@ namespace MediaBrowser.Api.Playback
                 EnableRaisingEvents = true
             };
 
-            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks);
+            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks, state.Item.Path);
 
             Logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
 

+ 0 - 36
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using ServiceStack.ServiceHost;
 using System;
-using System.IO;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -20,27 +19,6 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
-    /// <summary>
-    /// Class GetHlsAudioSegment
-    /// </summary>
-    [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")]
-    [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")]
-    [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
-    public class GetHlsAudioSegment
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public string Id { get; set; }
-
-        /// <summary>
-        /// Gets or sets the segment id.
-        /// </summary>
-        /// <value>The segment id.</value>
-        public string SegmentId { get; set; }
-    }
-
     /// <summary>
     /// Class AudioHlsService
     /// </summary>
@@ -59,20 +37,6 @@ namespace MediaBrowser.Api.Playback.Hls
         {
         }
 
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetHlsAudioSegment request)
-        {
-            var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
-
-            file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
-
-            return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite);
-        }
-
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 0 - 24
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -10,7 +10,6 @@ using MediaBrowser.Model.IO;
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -213,29 +212,6 @@ namespace MediaBrowser.Api.Playback.Hls
             return count;
         }
 
-        protected void ExtendHlsTimer(string itemId, string playlistId)
-        {
-            var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
-
-            foreach (var playlist in Directory.EnumerateFiles(ApplicationPaths.EncodedMediaCachePath, "*.m3u8")
-                .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
-                .ToList())
-            {
-                ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
-
-                // Avoid implicitly captured closure
-                var playlist1 = playlist;
-
-                Task.Run(async () =>
-                {
-                    // This is an arbitrary time period corresponding to when the request completes.
-                    await Task.Delay(30000).ConfigureAwait(false);
-
-                    ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist1, TranscodingJobType.Hls);
-                });
-            }
-        }
-
         /// <summary>
         /// Gets the command line arguments.
         /// </summary>

+ 53 - 0
MediaBrowser.Api/Playback/Hls/HlsSegmentResponseFilter.cs

@@ -0,0 +1,53 @@
+using MediaBrowser.Controller;
+using MediaBrowser.Model.Logging;
+using ServiceStack.ServiceHost;
+using ServiceStack.Text.Controller;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Api.Playback.Hls
+{
+    public class HlsSegmentResponseFilter : Attribute, IHasResponseFilter
+    {
+        public ILogger Logger { get; set; }
+        public IServerApplicationPaths ApplicationPaths { get; set; }
+
+        public void ResponseFilter(IHttpRequest req, IHttpResponse res, object response)
+        {
+            var pathInfo = PathInfo.Parse(req.PathInfo);
+            var itemId = pathInfo.GetArgumentValue<string>(1);
+            var playlistId = pathInfo.GetArgumentValue<string>(3);
+
+            OnEndRequest(itemId, playlistId);
+        }
+
+        public IHasResponseFilter Copy()
+        {
+            return this;
+        }
+
+        public int Priority
+        {
+            get { return -1; }
+        }
+
+        /// <summary>
+        /// Called when [end request].
+        /// </summary>
+        /// <param name="itemId">The item id.</param>
+        /// <param name="playlistId">The playlist id.</param>
+        protected void OnEndRequest(string itemId, string playlistId)
+        {
+            Logger.Info("OnEndRequest " + playlistId);
+            var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
+
+            foreach (var playlist in Directory.EnumerateFiles(ApplicationPaths.EncodedMediaCachePath, "*.m3u8")
+                .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+                .ToList())
+            {
+                ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
+            }
+        }
+    }
+}

+ 147 - 0
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -0,0 +1,147 @@
+using MediaBrowser.Controller;
+using ServiceStack.ServiceHost;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Playback.Hls
+{
+    /// <summary>
+    /// Class GetHlsAudioSegment
+    /// </summary>
+    [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")]
+    [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")]
+    [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
+    public class GetHlsAudioSegment
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets the segment id.
+        /// </summary>
+        /// <value>The segment id.</value>
+        public string SegmentId { get; set; }
+    }
+
+    /// <summary>
+    /// Class GetHlsVideoSegment
+    /// </summary>
+    [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")]
+    [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
+    public class GetHlsVideoSegment
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        public string Id { get; set; }
+
+        public string PlaylistId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the segment id.
+        /// </summary>
+        /// <value>The segment id.</value>
+        public string SegmentId { get; set; }
+    }
+
+    /// <summary>
+    /// Class GetHlsVideoSegment
+    /// </summary>
+    [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
+    [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
+    public class GetHlsPlaylist
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        public string Id { get; set; }
+
+        public string PlaylistId { get; set; }
+    }
+
+    public class HlsSegmentService : BaseApiService
+    {
+        private readonly IServerApplicationPaths _appPaths;
+
+        public HlsSegmentService(IServerApplicationPaths appPaths)
+        {
+            _appPaths = appPaths;
+        }
+
+        public object Get(GetHlsPlaylist request)
+        {
+            OnBeginRequest(request.PlaylistId);
+
+            var file = request.PlaylistId + Path.GetExtension(RequestContext.PathInfo);
+
+            file = Path.Combine(_appPaths.EncodedMediaCachePath, file);
+
+            return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite);
+        }
+
+        /// <summary>
+        /// Gets the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
+        public object Get(GetHlsVideoSegment request)
+        {
+            var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
+
+            file = Path.Combine(_appPaths.EncodedMediaCachePath, file);
+
+            OnBeginRequest(request.PlaylistId);
+
+            return ResultFactory.GetStaticFileResult(RequestContext, file);
+        }
+
+        /// <summary>
+        /// Gets the specified request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>System.Object.</returns>
+        public object Get(GetHlsAudioSegment request)
+        {
+            var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
+
+            file = Path.Combine(_appPaths.EncodedMediaCachePath, file);
+
+            return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite);
+        }
+
+        /// <summary>
+        /// Called when [begin request].
+        /// </summary>
+        /// <param name="playlistId">The playlist id.</param>
+        protected void OnBeginRequest(string playlistId)
+        {
+            var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
+
+            foreach (var playlist in Directory.EnumerateFiles(_appPaths.EncodedMediaCachePath, "*.m3u8")
+                .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+                .ToList())
+            {
+                ExtendPlaylistTimer(playlist);
+            }
+        }
+
+        private void ExtendPlaylistTimer(string playlist)
+        {
+            ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
+
+            Task.Run(async () =>
+            {
+                await Task.Delay(20000).ConfigureAwait(false);
+
+                ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
+            });
+        }
+    }
+}

+ 1 - 66
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.IO;
 using ServiceStack.ServiceHost;
 using System;
-using System.IO;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -31,44 +30,6 @@ namespace MediaBrowser.Api.Playback.Hls
         }
     }
 
-    /// <summary>
-    /// Class GetHlsVideoSegment
-    /// </summary>
-    [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")]
-    [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
-    public class GetHlsVideoSegment
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public string Id { get; set; }
-
-        public string PlaylistId { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the segment id.
-        /// </summary>
-        /// <value>The segment id.</value>
-        public string SegmentId { get; set; }
-    }
-
-    /// <summary>
-    /// Class GetHlsVideoSegment
-    /// </summary>
-    [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
-    [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
-    public class GetHlsPlaylist
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        public string Id { get; set; }
-
-        public string PlaylistId { get; set; }
-    }
-    
     /// <summary>
     /// Class VideoHlsService
     /// </summary>
@@ -82,38 +43,12 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
+        /// <param name="dtoService">The dto service.</param>
         public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService)
             : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService)
         {
         }
 
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetHlsVideoSegment request)
-        {
-            ExtendHlsTimer(request.Id, request.PlaylistId);
-            
-            var file = request.SegmentId + Path.GetExtension(RequestContext.PathInfo);
-
-            file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
-
-            return ResultFactory.GetStaticFileResult(RequestContext, file);
-        }
-
-        public object Get(GetHlsPlaylist request)
-        {
-            ExtendHlsTimer(request.Id, request.PlaylistId);
-
-            var file = request.PlaylistId + Path.GetExtension(RequestContext.PathInfo);
-
-            file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
-
-            return ResultFactory.GetStaticFileResult(RequestContext, file, FileShare.ReadWrite);
-        }
-
         /// <summary>
         /// Gets the specified request.
         /// </summary>

+ 31 - 227
MediaBrowser.Api/SessionsService.cs

@@ -1,7 +1,5 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Session;
 using ServiceStack.ServiceHost;
 using System;
@@ -189,6 +187,7 @@ namespace MediaBrowser.Api
         /// Initializes a new instance of the <see cref="SessionsService" /> class.
         /// </summary>
         /// <param name="sessionManager">The session manager.</param>
+        /// <param name="dtoService">The dto service.</param>
         public SessionsService(ISessionManager sessionManager, IDtoService dtoService)
         {
             _sessionManager = sessionManager;
@@ -214,52 +213,15 @@ namespace MediaBrowser.Api
 
         public void Post(SendPlaystateCommand request)
         {
-            var task = SendPlaystateCommand(request);
-
-            Task.WaitAll(task);
-        }
-
-        private async Task SendPlaystateCommand(SendPlaystateCommand request)
-        {
-            var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
-
-            if (session == null)
+            var command = new PlaystateRequest
             {
-                throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
-            }
-
-            if (!session.SupportsRemoteControl)
-            {
-                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
-            }
+                Command = request.Command,
+                SeekPositionTicks = request.SeekPositionTicks
+            };
 
-            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+            var task = _sessionManager.SendPlaystateCommand(request.Id, command, CancellationToken.None);
 
-            if (socket != null)
-            {
-                try
-                {
-                    await socket.SendAsync(new WebSocketMessage<PlaystateRequest>
-                    {
-                        MessageType = "Playstate",
-
-                        Data = new PlaystateRequest
-                        {
-                            Command = request.Command,
-                            SeekPositionTicks = request.SeekPositionTicks
-                        }
-
-                    }, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error sending web socket message", ex);
-                }
-            }
-            else
-            {
-                throw new InvalidOperationException("The requested session does not have an open web socket.");
-            }
+            Task.WaitAll(task);
         }
 
         /// <summary>
@@ -268,55 +230,17 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Post(BrowseTo request)
         {
-            var task = BrowseTo(request);
-
-            Task.WaitAll(task);
-        }
-
-        /// <summary>
-        /// Browses to.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="ResourceNotFoundException"></exception>
-        /// <exception cref="System.ArgumentException"></exception>
-        /// <exception cref="System.InvalidOperationException">The requested session does not have an open web socket.</exception>
-        private async Task BrowseTo(BrowseTo request)
-        {
-            var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
-
-            if (session == null)
-            {
-                throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
-            }
-
-            if (!session.SupportsRemoteControl)
+            var command = new BrowseRequest
             {
-                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
-            }
+                Context = request.Context,
+                ItemId = request.ItemId,
+                ItemName = request.ItemName,
+                ItemType = request.ItemType
+            };
 
-            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+            var task = _sessionManager.SendBrowseCommand(request.Id, command, CancellationToken.None);
 
-            if (socket != null)
-            {
-                try
-                {
-                    await socket.SendAsync(new WebSocketMessage<BrowseTo>
-                    {
-                        MessageType = "Browse",
-                        Data = request
-
-                    }, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error sending web socket message", ex);
-                }
-            }
-            else
-            {
-                throw new InvalidOperationException("The requested session does not have an open web socket.");
-            }
+            Task.WaitAll(task);
         }
 
         /// <summary>
@@ -325,102 +249,27 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Post(SendSystemCommand request)
         {
-            var task = SendSystemCommand(request);
+            var task = _sessionManager.SendSystemCommand(request.Id, request.Command, CancellationToken.None);
 
             Task.WaitAll(task);
         }
 
-        private async Task SendSystemCommand(SendSystemCommand request)
-        {
-            var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
-
-            if (session == null)
-            {
-                throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
-            }
-
-            if (!session.SupportsRemoteControl)
-            {
-                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
-            }
-
-            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
-
-            if (socket != null)
-            {
-                try
-                {
-                    await socket.SendAsync(new WebSocketMessage<string>
-                    {
-                        MessageType = "SystemCommand",
-                        Data = request.Command.ToString()
-
-                    }, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error sending web socket message", ex);
-                }
-            }
-            else
-            {
-                throw new InvalidOperationException("The requested session does not have an open web socket.");
-            }
-        }
-
         /// <summary>
         /// Posts the specified request.
         /// </summary>
         /// <param name="request">The request.</param>
         public void Post(SendMessageCommand request)
         {
-            var task = SendMessageCommand(request);
-
-            Task.WaitAll(task);
-        }
-
-        private async Task SendMessageCommand(SendMessageCommand request)
-        {
-            var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
-
-            if (session == null)
-            {
-                throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
-            }
-
-            if (!session.SupportsRemoteControl)
+            var command = new MessageCommand
             {
-                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
-            }
+                Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header,
+                TimeoutMs = request.TimeoutMs,
+                Text = request.Text
+            };
 
-            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+            var task = _sessionManager.SendMessageCommand(request.Id, command, CancellationToken.None);
 
-            if (socket != null)
-            {
-                try
-                {
-                    await socket.SendAsync(new WebSocketMessage<MessageCommand>
-                    {
-                        MessageType = "MessageCommand",
-
-                        Data = new MessageCommand
-                        {
-                            Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header,
-                            TimeoutMs = request.TimeoutMs,
-                            Text = request.Text
-                        }
-
-                    }, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error sending web socket message", ex);
-                }
-            }
-            else
-            {
-                throw new InvalidOperationException("The requested session does not have an open web socket.");
-            }
+            Task.WaitAll(task);
         }
 
         /// <summary>
@@ -429,62 +278,17 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public void Post(Play request)
         {
-            var task = Play(request);
-
-            Task.WaitAll(task);
-        }
-
-        /// <summary>
-        /// Plays the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="ResourceNotFoundException"></exception>
-        /// <exception cref="System.ArgumentException"></exception>
-        /// <exception cref="System.InvalidOperationException">The requested session does not have an open web socket.</exception>
-        private async Task Play(Play request)
-        {
-            var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
-
-            if (session == null)
+            var command = new PlayRequest
             {
-                throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
-            }
+                ItemIds = request.ItemIds.Split(',').ToArray(),
 
-            if (!session.SupportsRemoteControl)
-            {
-                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
-            }
+                PlayCommand = request.PlayCommand,
+                StartPositionTicks = request.StartPositionTicks
+            };
 
-            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+            var task = _sessionManager.SendPlayCommand(request.Id, command, CancellationToken.None);
 
-            if (socket != null)
-            {
-                try
-                {
-                    await socket.SendAsync(new WebSocketMessage<PlayRequest>
-                    {
-                        MessageType = "Play",
-
-                        Data = new PlayRequest
-                        {
-                            ItemIds = request.ItemIds.Split(',').ToArray(),
-
-                            PlayCommand = request.PlayCommand,
-                            StartPositionTicks = request.StartPositionTicks
-                        }
-
-                    }, CancellationToken.None).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error sending web socket message", ex);
-                }
-            }
-            else
-            {
-                throw new InvalidOperationException("The requested session does not have an open web socket.");
-            }
+            Task.WaitAll(task);
         }
     }
 }

+ 45 - 15
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -186,7 +186,7 @@ namespace MediaBrowser.Api.UserLibrary
 
         [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public DateTime? DatePlayed { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the id.
         /// </summary>
@@ -224,6 +224,13 @@ namespace MediaBrowser.Api.UserLibrary
     [Api(Description = "Reports that a user has begun playing an item")]
     public class OnPlaybackStart : IReturnVoid
     {
+        public OnPlaybackStart()
+        {
+            // Have to default these until all clients have a chance to incorporate them
+            CanSeek = true;
+            QueueableMediaTypes = "Audio,Video,Book,Game";
+        }
+
         /// <summary>
         /// Gets or sets the user id.
         /// </summary>
@@ -237,6 +244,20 @@ namespace MediaBrowser.Api.UserLibrary
         /// <value>The id.</value>
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
+        /// </summary>
+        /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value>
+        [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
+        public string QueueableMediaTypes { get; set; }
     }
 
     /// <summary>
@@ -378,6 +399,8 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="itemRepo">The item repo.</param>
+        /// <param name="sessionManager">The session manager.</param>
+        /// <param name="dtoService">The dto service.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
         public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager, IDtoService dtoService)
         {
@@ -640,19 +663,11 @@ namespace MediaBrowser.Api.UserLibrary
 
         private SessionInfo GetSession()
         {
-            var auth = RequestFilterAttribute.GetAuthorization(RequestContext);
-
-            string deviceId;
-            string client;
-            string version;
-
-            auth.TryGetValue("DeviceId", out deviceId);
-            auth.TryGetValue("Client", out client);
-            auth.TryGetValue("Version", out version);
+            var auth = AuthorizationRequestFilterAttribute.GetAuthorization(RequestContext);
 
-            return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, deviceId) &&
-                string.Equals(i.Client, client) &&
-                string.Equals(i.ApplicationVersion, version));
+            return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) &&
+                string.Equals(i.Client, auth.Client) &&
+                string.Equals(i.ApplicationVersion, auth.Version));
         }
 
         /// <summary>
@@ -665,7 +680,17 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = _dtoService.GetItemByDtoId(request.Id, user.Id);
 
-            _sessionManager.OnPlaybackStart(item, GetSession().Id);
+            var queueableMediaTypes = (request.QueueableMediaTypes ?? string.Empty);
+
+            var info = new PlaybackInfo
+            {
+                CanSeek = request.CanSeek,
+                Item = item,
+                SessionId = GetSession().Id,
+                QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
+            };
+
+            _sessionManager.OnPlaybackStart(info);
         }
 
         /// <summary>
@@ -693,7 +718,12 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = _dtoService.GetItemByDtoId(request.Id, user.Id);
 
-            var task = _sessionManager.OnPlaybackStopped(item, request.PositionTicks, GetSession().Id);
+            // Kill the encoding
+            ApiEntryPoint.Instance.KillSingleTranscodingJob(item.Path);
+
+            var session = GetSession();
+
+            var task = _sessionManager.OnPlaybackStopped(item, request.PositionTicks, session.Id);
 
             Task.WaitAll(task);
         }

+ 87 - 0
MediaBrowser.Common.Implementations/Archiving/ZipClient.cs

@@ -0,0 +1,87 @@
+using MediaBrowser.Model.IO;
+using SharpCompress.Archive.SevenZip;
+using SharpCompress.Common;
+using SharpCompress.Reader;
+using System.IO;
+
+namespace MediaBrowser.Common.Implementations.Archiving
+{
+    /// <summary>
+    /// Class DotNetZipClient
+    /// </summary>
+    public class ZipClient : IZipClient
+    {
+        /// <summary>
+        /// Extracts all.
+        /// </summary>
+        /// <param name="sourceFile">The source file.</param>
+        /// <param name="targetPath">The target path.</param>
+        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
+        public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
+        {
+            using (var fileStream = File.OpenRead(sourceFile))
+            {
+                ExtractAll(fileStream, targetPath, overwriteExistingFiles);
+            }
+        }
+
+        /// <summary>
+        /// Extracts all.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="targetPath">The target path.</param>
+        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
+        public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
+        {
+            using (var reader = ReaderFactory.Open(source))
+            {
+                var options = ExtractOptions.ExtractFullPath;
+
+                if (overwriteExistingFiles)
+                {
+                    options = options | ExtractOptions.Overwrite;
+                }
+
+                reader.WriteAllToDirectory(targetPath, options);
+            }
+        }
+
+        /// <summary>
+        /// Extracts all from7z.
+        /// </summary>
+        /// <param name="sourceFile">The source file.</param>
+        /// <param name="targetPath">The target path.</param>
+        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
+        public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
+        {
+            using (var fileStream = File.OpenRead(sourceFile))
+            {
+                ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
+            }
+        }
+
+        /// <summary>
+        /// Extracts all from7z.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="targetPath">The target path.</param>
+        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
+        public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
+        {
+            using (var archive = SevenZipArchive.Open(source))
+            {
+                using (var reader = archive.ExtractAllEntries())
+                {
+                    var options = ExtractOptions.ExtractFullPath;
+
+                    if (overwriteExistingFiles)
+                    {
+                        options = options | ExtractOptions.Overwrite;
+                    }
+
+                    reader.WriteAllToDirectory(targetPath, options);
+                }
+            }
+        }
+    }
+}

+ 29 - 30
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Implementations.Archiving;
 using MediaBrowser.Common.Implementations.NetworkManagement;
 using MediaBrowser.Common.Implementations.ScheduledTasks;
 using MediaBrowser.Common.Implementations.Security;
@@ -10,6 +11,7 @@ using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.Security;
 using MediaBrowser.Common.Updates;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Updates;
@@ -149,6 +151,12 @@ namespace MediaBrowser.Common.Implementations
         /// <value>The installation manager.</value>
         protected IInstallationManager InstallationManager { get; set; }
 
+        /// <summary>
+        /// Gets or sets the zip client.
+        /// </summary>
+        /// <value>The zip client.</value>
+        protected IZipClient ZipClient { get; set; }
+        
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
         /// </summary>
@@ -202,12 +210,27 @@ namespace MediaBrowser.Common.Implementations
             {
                 Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
 
-                Task.Run(() => ConfigureAutoRunAtStartup());
+                Task.Run(() => ConfigureAutorun());
 
                 ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
             });
         }
 
+        /// <summary>
+        /// Configures the autorun.
+        /// </summary>
+        private void ConfigureAutorun()
+        {
+            try
+            {
+                ConfigureAutoRunAtStartup(ConfigurationManager.CommonConfiguration.RunAtStartup);
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error configuring autorun", ex);
+            }
+        }
+
         /// <summary>
         /// Gets the composable part assemblies.
         /// </summary>
@@ -281,6 +304,9 @@ namespace MediaBrowser.Common.Implementations
 
                 InstallationManager = new InstallationManager(Logger, this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, NetworkManager, ConfigurationManager);
                 RegisterSingleInstance(InstallationManager);
+
+                ZipClient = new ZipClient();
+                RegisterSingleInstance(ZipClient);
             });
         }
 
@@ -453,11 +479,6 @@ namespace MediaBrowser.Common.Implementations
             }
         }
 
-        /// <summary>
-        /// Defines the full path to our shortcut in the start menu
-        /// </summary>
-        protected abstract string ProductShortcutPath { get; }
-
         /// <summary>
         /// Handles the ConfigurationUpdated event of the ConfigurationManager control.
         /// </summary>
@@ -466,32 +487,10 @@ namespace MediaBrowser.Common.Implementations
         /// <exception cref="System.NotImplementedException"></exception>
         protected virtual void OnConfigurationUpdated(object sender, EventArgs e)
         {
-            ConfigureAutoRunAtStartup();
+            ConfigureAutorun();
         }
 
-        /// <summary>
-        /// Configures the auto run at startup.
-        /// </summary>
-        private void ConfigureAutoRunAtStartup()
-        {
-            if (ConfigurationManager.CommonConfiguration.RunAtStartup)
-            {
-                //Copy our shortut into the startup folder for this user
-                File.Copy(ProductShortcutPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(ProductShortcutPath) ?? "MBstartup.lnk"), true);
-            }
-            else
-            {
-                //Remove our shortcut from the startup folder for this user
-                try
-                {
-                    File.Delete(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(ProductShortcutPath) ?? "MBstartup.lnk"));
-                }
-                catch (FileNotFoundException)
-                {
-                    //This is okay - trying to remove it anyway
-                }
-            }
-        }
+        protected abstract void ConfigureAutoRunAtStartup(bool autorun);
 
         /// <summary>
         /// Removes the plugin.

+ 7 - 11
MediaBrowser.Common.Implementations/Logging/NlogManager.cs

@@ -5,7 +5,6 @@ using NLog.Targets;
 using System;
 using System.IO;
 using System.Linq;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Implementations.Logging
 {
@@ -193,17 +192,14 @@ namespace MediaBrowser.Common.Implementations.Logging
 
             if (LoggerLoaded != null)
             {
-                Task.Run(() =>
+                try
                 {
-                    try
-                    {
-                        LoggerLoaded(this, EventArgs.Empty);
-                    }
-                    catch (Exception ex)
-                    {
-                        GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
-                    }
-                });
+                    LoggerLoaded(this, EventArgs.Empty);
+                }
+                catch (Exception ex)
+                {
+                    GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
+                }
             }
         }
     }

+ 16 - 13
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -13,6 +13,8 @@
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -35,17 +37,8 @@
     <RunPostBuildEvent>Always</RunPostBuildEvent>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="NLog, Version=2.0.1.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\NLog.2.0.1.2\lib\net45\NLog.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
-    </Reference>
-    <Reference Include="SimpleInjector, Version=2.3.5.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath>
+    <Reference Include="SharpCompress">
+      <HintPath>..\packages\sharpcompress.0.10.1.3\lib\net40\SharpCompress.dll</HintPath>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
@@ -54,11 +47,21 @@
     <Reference Include="System.Net" />
     <Reference Include="System.Net.Http" />
     <Reference Include="System.Xml" />
+    <Reference Include="NLog">
+      <HintPath>..\packages\NLog.2.0.1.2\lib\net45\NLog.dll</HintPath>
+    </Reference>
+    <Reference Include="ServiceStack.Text">
+      <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
+    </Reference>
+    <Reference Include="SimpleInjector">
+      <HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
+    <Compile Include="Archiving\ZipClient.cs" />
     <Compile Include="BaseApplicationHost.cs" />
     <Compile Include="BaseApplicationPaths.cs" />
     <Compile Include="Configuration\BaseConfigurationManager.cs" />
@@ -88,11 +91,11 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
-      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
       <Name>MediaBrowser.Common</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
-      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>

+ 13 - 2
MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs

@@ -1,5 +1,4 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Model.Logging;
@@ -8,6 +7,7 @@ using MediaBrowser.Model.Tasks;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Implementations.ScheduledTasks
 {
@@ -77,6 +77,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             QueueScheduledTask<T>();
         }
 
+        /// <summary>
+        /// Cancels if running
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        public void CancelIfRunning<T>()
+                 where T : IScheduledTask
+        {
+            var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T));
+            ((ScheduledTaskWorker)task).CancelIfRunning();
+        }
+
         /// <summary>
         /// Queues the scheduled task.
         /// </summary>

+ 18 - 19
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -54,33 +54,32 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// <returns>Task.</returns>
         public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            return Task.Run(() =>
-            {
-                // Delete log files more than n days old
-                var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
+            // Delete log files more than n days old
+            var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
+
+            var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
+                          .Where(f => f.LastWriteTimeUtc < minDateModified)
+                          .ToList();
 
-                var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
-                              .Where(f => f.LastWriteTimeUtc < minDateModified)
-                              .ToList();
+            var index = 0;
 
-                var index = 0;
+            foreach (var file in filesToDelete)
+            {
+                double percent = index;
+                percent /= filesToDelete.Count;
 
-                foreach (var file in filesToDelete)
-                {
-                    double percent = index;
-                    percent /= filesToDelete.Count;
+                progress.Report(100 * percent);
 
-                    progress.Report(100 * percent);
+                cancellationToken.ThrowIfCancellationRequested();
 
-                    cancellationToken.ThrowIfCancellationRequested();
+                File.Delete(file.FullName);
 
-                    File.Delete(file.FullName);
+                index++;
+            }
 
-                    index++;
-                }
+            progress.Report(100);
 
-                progress.Report(100);
-            });
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 5 - 1
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs

@@ -58,7 +58,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 
             progress.Report(0);
 
-            return Task.Run(() => LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info));
+            LogManager.ReloadLogger(ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
+                                        ? LogSeverity.Debug
+                                        : LogSeverity.Info);
+
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 1 - 0
MediaBrowser.Common.Implementations/packages.config

@@ -2,5 +2,6 @@
 <packages>
   <package id="NLog" version="2.0.1.2" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
+  <package id="sharpcompress" version="0.10.1.3" targetFramework="net45" />
   <package id="SimpleInjector" version="2.3.5" targetFramework="net45" />
 </packages>

+ 9 - 14
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -13,6 +13,8 @@
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -32,26 +34,19 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
-  <PropertyGroup>
-    <ApplicationIcon>
-    </ApplicationIcon>
-  </PropertyGroup>
   <ItemGroup>
-    <Reference Include="ServiceStack.Common, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="ServiceStack.Common">
       <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Interfaces, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Interfaces">
       <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Text">
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
     </Reference>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="Microsoft.CSharp" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
@@ -113,7 +108,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
-      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>

+ 7 - 0
MediaBrowser.Common/ScheduledTasks/ITaskManager.cs

@@ -21,6 +21,13 @@ namespace MediaBrowser.Common.ScheduledTasks
         void CancelIfRunningAndQueue<T>()
             where T : IScheduledTask;
 
+        /// <summary>
+        /// Cancels if running.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        void CancelIfRunning<T>()
+            where T : IScheduledTask;
+
         /// <summary>
         /// Queues the scheduled task.
         /// </summary>

+ 26 - 3
MediaBrowser.Controller/Entities/Folder.cs

@@ -16,7 +16,6 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
-using MoreLinq;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -171,6 +170,25 @@ namespace MediaBrowser.Controller.Entities
             return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
         }
 
+        /// <summary>
+        /// Clears the children.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task ClearChildren(CancellationToken cancellationToken)
+        {
+            var items = ActualChildren.ToList();
+
+            ClearChildrenInternal();
+
+            foreach (var item in items)
+            {
+                LibraryManager.ReportItemRemoved(item);
+            }
+
+            return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
+        }
+
         #region Indexing
 
         /// <summary>
@@ -671,7 +689,7 @@ namespace MediaBrowser.Controller.Entities
 
             var options = new ParallelOptions
             {
-                MaxDegreeOfParallelism = 20
+                MaxDegreeOfParallelism = 10
             };
 
             Parallel.ForEach(nonCachedChildren, options, child =>
@@ -733,6 +751,11 @@ namespace MediaBrowser.Controller.Entities
                 if (actualRemovals.Count > 0)
                 {
                     RemoveChildrenInternal(actualRemovals);
+
+                    foreach (var item in actualRemovals)
+                    {
+                        LibraryManager.ReportItemRemoved(item);
+                    }
                 }
 
                 await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
@@ -781,7 +804,7 @@ namespace MediaBrowser.Controller.Entities
 
             foreach (var tuple in list)
             {
-                if (tasks.Count > 8)
+                if (tasks.Count > 5)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                 }

+ 12 - 6
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -13,6 +13,8 @@
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -42,6 +44,8 @@
     <PlatformTarget>x86</PlatformTarget>
     <ErrorReport>prompt</ErrorReport>
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <WarningLevel>4</WarningLevel>
+    <Optimize>false</Optimize>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
     <OutputPath>bin\x86\Release\</OutputPath>
@@ -51,12 +55,9 @@
     <PlatformTarget>x86</PlatformTarget>
     <ErrorReport>prompt</ErrorReport>
     <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+    <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
-    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Data" />
@@ -66,6 +67,9 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Xml" />
     <Reference Include="System.Xml.Linq" />
+    <Reference Include="MoreLinq">
+      <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
@@ -165,6 +169,8 @@
     <Compile Include="Kernel.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Providers\BaseMetadataProvider.cs" />
+    <Compile Include="Session\ISessionRemoteController.cs" />
+    <Compile Include="Session\PlaybackInfo.cs" />
     <Compile Include="Session\SessionInfo.cs" />
     <Compile Include="Sorting\IBaseItemComparer.cs" />
     <Compile Include="Sorting\IUserBaseItemComparer.cs" />
@@ -172,11 +178,11 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
-      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
       <Name>MediaBrowser.Common</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
-      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>

+ 1 - 1
MediaBrowser.Controller/Providers/BaseMetadataProvider.cs

@@ -385,7 +385,7 @@ namespace MediaBrowser.Controller.Providers
             var sb = new StringBuilder();
 
             var extensions = FileStampExtensionsDictionary;
-            var numExtensions = extensions.Count;
+            var numExtensions = FilestampExtensions.Length;
 
             // Record the name of each file 
             // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order

+ 56 - 4
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Session;
 using System;
 using System.Collections.Generic;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Session
@@ -11,6 +13,12 @@ namespace MediaBrowser.Controller.Session
     /// </summary>
     public interface ISessionManager
     {
+        /// <summary>
+        /// Adds the parts.
+        /// </summary>
+        /// <param name="remoteControllers">The remote controllers.</param>
+        void AddParts(IEnumerable<ISessionRemoteController> remoteControllers);
+
         /// <summary>
         /// Occurs when [playback start].
         /// </summary>
@@ -47,11 +55,9 @@ namespace MediaBrowser.Controller.Session
         /// <summary>
         /// Used to report that playback has started for an item
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="sessionId">The session id.</param>
+        /// <param name="info">The info.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        Task OnPlaybackStart(BaseItem item, Guid sessionId);
+        Task OnPlaybackStart(PlaybackInfo info);
 
         /// <summary>
         /// Used to report playback progress for an item
@@ -59,6 +65,7 @@ namespace MediaBrowser.Controller.Session
         /// <param name="item">The item.</param>
         /// <param name="positionTicks">The position ticks.</param>
         /// <param name="isPaused">if set to <c>true</c> [is paused].</param>
+        /// <param name="isMuted">if set to <c>true</c> [is muted].</param>
         /// <param name="sessionId">The session id.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
@@ -73,5 +80,50 @@ namespace MediaBrowser.Controller.Session
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException"></exception>
         Task OnPlaybackStopped(BaseItem item, long? positionTicks, Guid sessionId);
+
+        /// <summary>
+        /// Sends the system command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the message command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the play command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the browse command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the playstate command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken);
     }
 }

+ 61 - 0
MediaBrowser.Controller/Session/ISessionRemoteController.cs

@@ -0,0 +1,61 @@
+using MediaBrowser.Model.Session;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Session
+{
+    public interface ISessionRemoteController
+    {
+        /// <summary>
+        /// Supportses the specified session.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        bool Supports(SessionInfo session);
+
+        /// <summary>
+        /// Sends the system command.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendSystemCommand(SessionInfo session, SystemCommand command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the message command.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendMessageCommand(SessionInfo session, MessageCommand command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the play command.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendPlayCommand(SessionInfo session, PlayRequest command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the browse command.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendBrowseCommand(SessionInfo session, BrowseRequest command, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the playstate command.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SendPlaystateCommand(SessionInfo session, PlaystateRequest command, CancellationToken cancellationToken);
+    }
+}

+ 38 - 0
MediaBrowser.Controller/Session/PlaybackInfo.cs

@@ -0,0 +1,38 @@
+using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Session
+{
+    public class PlaybackInfo
+    {
+        public PlaybackInfo()
+        {
+            QueueableMediaTypes = new List<string>();
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can seek.
+        /// </summary>
+        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the queueable media types.
+        /// </summary>
+        /// <value>The queueable media types.</value>
+        public List<string> QueueableMediaTypes { get; set; }
+
+        /// <summary>
+        /// Gets or sets the item.
+        /// </summary>
+        /// <value>The item.</value>
+        public BaseItem Item { get; set; }
+
+        /// <summary>
+        /// Gets or sets the session id.
+        /// </summary>
+        /// <value>The session id.</value>
+        public Guid SessionId { get; set; }
+    }
+}

+ 16 - 3
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -15,8 +15,21 @@ namespace MediaBrowser.Controller.Session
         public SessionInfo()
         {
             WebSockets = new List<IWebSocketConnection>();
+            QueueableMediaTypes = new List<string>();
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can seek.
+        /// </summary>
+        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the queueable media types.
+        /// </summary>
+        /// <value>The queueable media types.</value>
+        public List<string> QueueableMediaTypes { get; set; }
+
         /// <summary>
         /// Gets or sets the id.
         /// </summary>
@@ -70,7 +83,7 @@ namespace MediaBrowser.Controller.Session
         /// </summary>
         /// <value>The name of the now viewing item.</value>
         public string NowViewingItemName { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the now playing item.
         /// </summary>
@@ -94,7 +107,7 @@ namespace MediaBrowser.Controller.Session
         /// </summary>
         /// <value><c>true</c> if this instance is muted; otherwise, <c>false</c>.</value>
         public bool IsMuted { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the device id.
         /// </summary>
@@ -126,7 +139,7 @@ namespace MediaBrowser.Controller.Session
                     return WebSockets.Any(i => i.State == WebSocketState.Open);
                 }
 
-                return (DateTime.UtcNow - LastActivityDate).TotalMinutes <= 5;
+                return (DateTime.UtcNow - LastActivityDate).TotalMinutes <= 10;
             }
         }
 

+ 3 - 1
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -497,9 +497,11 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// <param name="itemId">The item id.</param>
         /// <param name="userId">The user id.</param>
+        /// <param name="isSeekable">if set to <c>true</c> [is seekable].</param>
+        /// <param name="queueableMediaTypes">The list of media types that the client is capable of queuing onto the playlist. See MediaType class.</param>
         /// <returns>Task{UserItemDataDto}.</returns>
         /// <exception cref="ArgumentNullException">itemId</exception>
-        Task ReportPlaybackStartAsync(string itemId, string userId);
+        Task ReportPlaybackStartAsync(string itemId, string userId, bool isSeekable, List<string> queueableMediaTypes);
 
         /// <summary>
         /// Reports playback progress to the server

+ 16 - 0
MediaBrowser.Model/IO/IZipClient.cs

@@ -22,5 +22,21 @@ namespace MediaBrowser.Model.IO
         /// <param name="targetPath">The target path.</param>
         /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
         void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles);
+
+        /// <summary>
+        /// Extracts all from7z.
+        /// </summary>
+        /// <param name="sourceFile">The source file.</param>
+        /// <param name="targetPath">The target path.</param>
+        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
+        void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles);
+
+        /// <summary>
+        /// Extracts all from7z.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="targetPath">The target path.</param>
+        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
+        void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles);
     }
 }

+ 6 - 5
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -14,6 +14,8 @@
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
     <FodyPath>..\packages\Fody.1.17.0.0</FodyPath>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -162,14 +164,13 @@
   </ItemGroup>
   <ItemGroup>
     <Reference Include="Microsoft.CSharp" />
-    <Reference Include="PropertyChanged, Version=1.41.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\PropertyChanged.Fody.1.41.0.0\Lib\NET35\PropertyChanged.dll</HintPath>
-      <Private>False</Private>
-    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="PropertyChanged">
+      <HintPath>..\packages\PropertyChanged.Fody.1.41.0.0\Lib\NET35\PropertyChanged.dll</HintPath>
+      <Private>False</Private>
+    </Reference>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>

+ 15 - 2
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -1,11 +1,24 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
 using System;
+using System.Collections.Generic;
+using System.ComponentModel;
 
 namespace MediaBrowser.Model.Session
 {
     public class SessionInfoDto : INotifyPropertyChanged
     {
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance can seek.
+        /// </summary>
+        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
+        public bool CanSeek { get; set; }
+
+        /// <summary>
+        /// Gets or sets the queueable media types.
+        /// </summary>
+        /// <value>The queueable media types.</value>
+        public List<string> QueueableMediaTypes { get; set; }
+        
         /// <summary>
         /// Gets or sets the id.
         /// </summary>

+ 68 - 0
MediaBrowser.Mono.sln

@@ -0,0 +1,68 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common.Implementations", "MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj", "{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Implementations", "MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj", "{2E781478-814D-4A48-9D80-BFF206441A65}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Mono", "MediaBrowser.Server.Mono\MediaBrowser.Server.Mono.csproj", "{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|x86
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.Build.0 = Debug|x86
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|x86
+		{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|x86
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.Build.0 = Debug|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU
+		{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.Build.0 = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.Build.0 = Debug|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU
+		{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.Build.0 = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.Build.0 = Debug|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU
+		{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.Build.0 = Debug|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
+		{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.Build.0 = Debug|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
+		{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.Build.0 = Debug|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
+		{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU
+		{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Debug|x86.ActiveCfg = Debug|x86
+		{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Debug|x86.Build.0 = Debug|x86
+		{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Release|x86.ActiveCfg = Release|x86
+		{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}.Release|x86.Build.0 = Release|x86
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.Build.0 = Debug|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.ActiveCfg = Release|Any CPU
+		{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(MonoDevelopProperties) = preSolution
+		StartupItem = MediaBrowser.Server.Mono\MediaBrowser.Server.Mono.csproj
+	EndGlobalSection
+EndGlobal

+ 19 - 0
MediaBrowser.Mono.userprefs

@@ -0,0 +1,19 @@
+<Properties>
+  <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug|x86" />
+  <MonoDevelop.Ide.Workbench ActiveDocument="d:\Development\MediaBrowser\MediaBrowser.Server.Mono\Native\Sqlite.cs">
+    <Files>
+      <File FileName="MediaBrowser.Server.Mono\Program.cs" Line="1" Column="1" />
+      <File FileName="MediaBrowser.Server.Mono\MainWindow.cs" Line="8" Column="12" />
+      <File FileName="MediaBrowser.Server.Mono\Native\Autorun.cs" Line="17" Column="4" />
+      <File FileName="MediaBrowser.Server.Mono\Native\ServerAuthorization.cs" Line="23" Column="1" />
+      <File FileName="MediaBrowser.Server.Mono\FFMpeg\FFMpegDownloader.cs" Line="7" Column="14" />
+      <File FileName="MediaBrowser.ServerApplication\ApplicationHost.cs" Line="548" Column="61" />
+      <File FileName="MediaBrowser.Server.Mono\Native\NativeApp.cs" Line="22" Column="13" />
+      <File FileName="d:\Development\MediaBrowser\MediaBrowser.Server.Mono\Native\Sqlite.cs" Line="21" Column="14" />
+    </Files>
+  </MonoDevelop.Ide.Workbench>
+  <MonoDevelop.Ide.DebuggingService.Breakpoints>
+    <BreakpointStore />
+  </MonoDevelop.Ide.DebuggingService.Breakpoints>
+  <MonoDevelop.Ide.DebuggingService.PinnedWatches />
+</Properties>

+ 8 - 7
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -13,6 +13,8 @@
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -32,10 +34,6 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
-    </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Net" />
@@ -44,6 +42,9 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Data" />
     <Reference Include="System.Xml" />
+    <Reference Include="MoreLinq">
+      <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Extensions\XDocumentExtensions.cs" />
@@ -116,15 +117,15 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
-      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
       <Name>MediaBrowser.Common</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
-      <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+      <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
       <Name>MediaBrowser.Controller</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
-      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>

+ 1 - 0
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs

@@ -178,6 +178,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 }
             }
 
+            SetLastRefreshed(item, DateTime.UtcNow);
             return true;
         }
 

+ 0 - 1
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;

+ 3 - 1
MediaBrowser.Providers/Music/SoundtrackPostScanTask.cs

@@ -23,7 +23,9 @@ namespace MediaBrowser.Providers.Music
 
         public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            return Task.Run(() => RunInternal(progress, cancellationToken));
+            RunInternal(progress, cancellationToken);
+
+            return Task.FromResult(true);
         }
 
         private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)

+ 3 - 2
MediaBrowser.Providers/Savers/AlbumXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
@@ -58,7 +59,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { });
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             PersonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);

+ 3 - 2
MediaBrowser.Providers/Savers/ArtistXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
@@ -70,7 +71,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { });
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             ArtistProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);

+ 3 - 2
MediaBrowser.Providers/Savers/BoxSetXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
@@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { });
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
 
             BoxSetProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);
         }

+ 3 - 2
MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -87,7 +88,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new[]
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
                 {
                     "FirstAired",
                     "SeasonNumber",

+ 2 - 1
MediaBrowser.Providers/Savers/FolderXmlSaver.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -77,7 +78,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { });
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
 
             FolderProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);
         }

+ 4 - 3
MediaBrowser.Providers/Savers/GameXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Providers.Movies;
@@ -66,7 +67,7 @@ namespace MediaBrowser.Providers.Savers
 
             if (!string.IsNullOrEmpty(game.GameSystem))
             {
-                builder.Append("<GameSystem><![CDATA[" + game.GameSystem + "]]></GameSystem>");
+                builder.Append("<GameSystem>" + SecurityElement.Escape(game.GameSystem) + "</GameSystem>");
             }
             
             XmlSaverHelpers.AddCommonNodes(item, builder);
@@ -75,7 +76,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new[]
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
                 {
                     "Players",
                     "GameSystem"

+ 3 - 2
MediaBrowser.Providers/Savers/MovieXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
@@ -103,7 +104,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new[]
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
                 {
                     "IMDBrating",
                     "Description",

+ 3 - 2
MediaBrowser.Providers/Savers/PersonXmlSaver.cs

@@ -1,4 +1,5 @@
-using System.Security;
+using System.Collections.Generic;
+using System.Security;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Providers.Movies;
@@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new[]
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
                 {
                     "PlaceOfBirth"
                 });

+ 2 - 1
MediaBrowser.Providers/Savers/SeasonXmlSaver.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
@@ -57,7 +58,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new string[] { });
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
 
             SeasonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);
         }

+ 7 - 3
MediaBrowser.Providers/Savers/SeriesXmlSaver.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System.Collections.Generic;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -105,7 +106,7 @@ namespace MediaBrowser.Providers.Savers
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new[]
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
                 {
                     "id", 
                     "SeriesName",
@@ -113,7 +114,10 @@ namespace MediaBrowser.Providers.Savers
                     "Network",
                     "Airs_Time",
                     "Airs_DayOfWeek",
-                    "FirstAired"
+                    "FirstAired",
+
+                    // Don't preserve old series node
+                    "Series"
                 });
 
             // Set last refreshed so that the provider doesn't trigger after the file save

+ 40 - 13
MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -29,13 +29,11 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="xml">The XML.</param>
         /// <param name="path">The path.</param>
         /// <param name="xmlTagsUsed">The XML tags used.</param>
-        public static void Save(StringBuilder xml, string path, IEnumerable<string> xmlTagsUsed)
+        public static void Save(StringBuilder xml, string path, List<string> xmlTagsUsed)
         {
             if (File.Exists(path))
             {
-                var tags = xmlTagsUsed.ToList();
-
-                tags.AddRange(new[]
+                xmlTagsUsed.AddRange(new[]
                 {
                     "MediaInfo",
                     "ContentRating",
@@ -88,7 +86,7 @@ namespace MediaBrowser.Providers.Savers
                 });
 
                 var position = xml.ToString().LastIndexOf("</", StringComparison.OrdinalIgnoreCase);
-                xml.Insert(position, GetCustomTags(path, tags));
+                xml.Insert(position, GetCustomTags(path, xmlTagsUsed));
             }
 
             var xmlDocument = new XmlDocument();
@@ -142,17 +140,46 @@ namespace MediaBrowser.Providers.Savers
         /// <param name="path">The path.</param>
         /// <param name="xmlTagsUsed">The XML tags used.</param>
         /// <returns>System.String.</returns>
-        private static string GetCustomTags(string path, ICollection<string> xmlTagsUsed)
+        private static string GetCustomTags(string path, IEnumerable<string> xmlTagsUsed)
         {
-            var doc = new XmlDocument();
-            doc.Load(path);
+            var settings = new XmlReaderSettings
+            {
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
+
+            var tagsDictionary = xmlTagsUsed.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
-            var nodes = doc.DocumentElement.ChildNodes.Cast<XmlNode>()
-                .Where(i => !xmlTagsUsed.Contains(i.Name))
-                .Select(i => i.OuterXml)
-                .ToArray();
+            var builder = new StringBuilder();
+
+            using (var streamReader = new StreamReader(path, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
+                {
+                    reader.MoveToContent();
 
-            return string.Join(Environment.NewLine, nodes);
+                    // Loop through each element
+                    while (reader.Read())
+                    {
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            if (!tagsDictionary.ContainsKey(reader.Name))
+                            {
+                                builder.AppendLine(reader.ReadOuterXml());
+                            }
+                            else
+                            {
+                                reader.Skip();
+                            }
+                        }
+                    }
+                }
+            }
+            
+            return builder.ToString();
         }
 
         /// <summary>

+ 3 - 1
MediaBrowser.Providers/TV/SeriesPostScanTask.cs

@@ -21,7 +21,9 @@ namespace MediaBrowser.Providers.TV
 
         public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            return Task.Run(() => RunInternal(progress, cancellationToken));
+            RunInternal(progress, cancellationToken);
+
+            return Task.FromResult(true);
         }
 
         private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)

+ 56 - 38
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
             }
             catch (IOException)
             {
-                // Cache file doesn't exist or is currently being written ro
+                // Cache file doesn't exist or is currently being written to
             }
 
             var semaphore = GetLock(cacheFilePath);
@@ -129,21 +129,24 @@ namespace MediaBrowser.Server.Implementations.Drawing
             await semaphore.WaitAsync().ConfigureAwait(false);
 
             // Check again in case of lock contention
-            if (File.Exists(cacheFilePath))
+            try
             {
-                try
-                {
-                    using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
-                    {
-                        await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
-                        return;
-                    }
-                }
-                finally
+                using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                 {
+                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                     semaphore.Release();
+                    return;
                 }
             }
+            catch (IOException)
+            {
+                // Cache file doesn't exist or is currently being written to
+            }
+            catch
+            {
+                semaphore.Release();
+                throw;
+            }
 
             try
             {
@@ -188,12 +191,10 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
                                         var bytes = outputMemoryStream.ToArray();
 
-                                        var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length);
+                                        await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
 
                                         // kick off a task to cache the result
-                                        var cacheTask = CacheResizedImage(cacheFilePath, bytes);
-
-                                        await Task.WhenAll(outputTask, cacheTask).ConfigureAwait(false);
+                                        CacheResizedImage(cacheFilePath, bytes, semaphore);
                                     }
                                 }
                             }
@@ -202,12 +203,51 @@ namespace MediaBrowser.Server.Implementations.Drawing
                     }
                 }
             }
-            finally
+            catch
             {
                 semaphore.Release();
+
+                throw;
             }
         }
 
+        /// <summary>
+        /// Caches the resized image.
+        /// </summary>
+        /// <param name="cacheFilePath">The cache file path.</param>
+        /// <param name="bytes">The bytes.</param>
+        /// <param name="semaphore">The semaphore.</param>
+        private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore)
+        {
+            Task.Run(async () =>
+            {
+                try
+                {
+                    var parentPath = Path.GetDirectoryName(cacheFilePath);
+
+                    if (!Directory.Exists(parentPath))
+                    {
+                        Directory.CreateDirectory(parentPath);
+                    }
+
+                    // Save to the cache location
+                    using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                    {
+                        // Save to the filestream
+                        await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath);
+                }
+                finally
+                {
+                    semaphore.Release();
+                }
+            });
+        }
+
         /// <summary>
         /// Sets the color of the background.
         /// </summary>
@@ -363,28 +403,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
             return croppedImagePath;
         }
 
-        /// <summary>
-        /// Caches the resized image.
-        /// </summary>
-        /// <param name="cacheFilePath">The cache file path.</param>
-        /// <param name="bytes">The bytes.</param>
-        private async Task CacheResizedImage(string cacheFilePath, byte[] bytes)
-        {
-            var parentPath = Path.GetDirectoryName(cacheFilePath);
-
-            if (!Directory.Exists(parentPath))
-            {
-                Directory.CreateDirectory(parentPath);
-            }
-
-            // Save to the cache location
-            using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
-            {
-                // Save to the filestream
-                await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-            }
-        }
-
         /// <summary>
         /// Gets the cache file path based on a set of parameters
         /// </summary>

+ 3 - 1
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -237,7 +237,9 @@ namespace MediaBrowser.Server.Implementations.Dto
                 NowViewingItemId = session.NowViewingItemId,
                 NowViewingItemName = session.NowViewingItemName,
                 NowViewingItemType = session.NowViewingItemType,
-                ApplicationVersion = session.ApplicationVersion
+                ApplicationVersion = session.ApplicationVersion,
+                CanSeek = session.CanSeek,
+                QueueableMediaTypes = session.QueueableMediaTypes
             };
 
             if (session.NowPlayingItem != null)

+ 4 - 2
MediaBrowser.ServerApplication/EntryPoints/UdpServerEntryPoint.cs → MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs

@@ -5,7 +5,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Server.Implementations.Udp;
 using System.Net.Sockets;
 
-namespace MediaBrowser.ServerApplication.EntryPoints
+namespace MediaBrowser.Server.Implementations.EntryPoints
 {
     /// <summary>
     /// Class UdpServerEntryPoint
@@ -35,6 +35,8 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         /// </summary>
         private readonly IHttpServer _httpServer;
 
+        public const int PortNumber = 7359;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UdpServerEntryPoint"/> class.
         /// </summary>
@@ -59,7 +61,7 @@ namespace MediaBrowser.ServerApplication.EntryPoints
 
             try
             {
-                udpServer.Start(ApplicationHost.UdpServerPort);
+                udpServer.Start(PortNumber);
 
                 UdpServer = udpServer;
             }

+ 11 - 4
MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs

@@ -314,6 +314,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <param name="context">The CTX.</param>
         private async void ProcessHttpRequestAsync(HttpListenerContext context)
         {
+            var date = DateTime.Now;
+
             LogHttpRequest(context);
 
             if (context.Request.IsWebSocketRequest)
@@ -360,7 +362,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                     var url = context.Request.Url.ToString();
                     var endPoint = context.Request.RemoteEndPoint;
 
-                    LogResponse(context, url, endPoint);
+                    var duration = DateTime.Now - date;
+
+                    LogResponse(context, url, endPoint, duration);
 
                 }
                 catch (Exception ex)
@@ -461,14 +465,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <param name="ctx">The CTX.</param>
         /// <param name="url">The URL.</param>
         /// <param name="endPoint">The end point.</param>
-        private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint)
+        /// <param name="duration">The duration.</param>
+        private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration)
         {
             if (!EnableHttpRequestLogging)
             {
                 return;
             }
 
-            var statusode = ctx.Response.StatusCode;
+            var statusCode = ctx.Response.StatusCode;
 
             var log = new StringBuilder();
 
@@ -476,7 +481,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
             log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
 
-            var msg = "Http Response Sent (" + statusode + ") to " + endPoint;
+            var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds);
+
+            var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime;
 
             _logger.LogMultiline(msg, LogSeverity.Debug, log);
         }

+ 16 - 38
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -829,10 +829,6 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>Task.</returns>
         public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            const int maxTasks = 3;
-
-            var tasks = new List<Task>();
-
             var people = RootFolder.RecursiveChildren
                 .SelectMany(c => c.People)
                 .DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
@@ -842,47 +838,27 @@ namespace MediaBrowser.Server.Implementations.Library
 
             foreach (var person in people)
             {
-                if (tasks.Count > maxTasks)
+                cancellationToken.ThrowIfCancellationRequested();
+
+                try
                 {
-                    await Task.WhenAll(tasks).ConfigureAwait(false);
-                    tasks.Clear();
+                    var item = GetPerson(person.Name);
 
-                    // Safe cancellation point, when there are no pending tasks
-                    cancellationToken.ThrowIfCancellationRequested();
+                    await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
                 }
-
-                // Avoid accessing the foreach variable within the closure
-                var currentPerson = person;
-
-                tasks.Add(Task.Run(async () =>
+                catch (IOException ex)
                 {
-                    cancellationToken.ThrowIfCancellationRequested();
-
-                    try
-                    {
-                        var item = GetPerson(currentPerson.Name);
-
-                        await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
-                    }
-                    catch (IOException ex)
-                    {
-                        _logger.ErrorException("Error validating IBN entry {0}", ex, currentPerson.Name);
-                    }
+                    _logger.ErrorException("Error validating IBN entry {0}", ex, person.Name);
+                }
 
-                    // Update progress
-                    lock (progress)
-                    {
-                        numComplete++;
-                        double percent = numComplete;
-                        percent /= people.Count;
+                // Update progress
+                numComplete++;
+                double percent = numComplete;
+                percent /= people.Count;
 
-                        progress.Report(100 * percent);
-                    }
-                }));
+                progress.Report(100 * percent);
             }
 
-            await Task.WhenAll(tasks).ConfigureAwait(false);
-
             progress.Report(100);
 
             _logger.Info("People validation complete");
@@ -956,7 +932,9 @@ namespace MediaBrowser.Server.Implementations.Library
         public Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken)
         {
             // Just run the scheduled task so that the user can see it
-            return Task.Run(() => _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>());
+            _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
+
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 4 - 2
MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs

@@ -41,8 +41,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            var allItems = _libraryManager.RootFolder.RecursiveChildren.OfType<Game>().ToList();
-
             var userLibraries = _userManager.Users
                 .Select(i => new Tuple<Guid, List<Game>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<Game>().ToList()))
                 .ToList();
@@ -79,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
                 {
                     await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
                 }
+                catch (OperationCanceledException)
+                {
+                    // Don't clutter the log
+                }
                 catch (Exception ex)
                 {
                     _logger.ErrorException("Error updating counts for {0}", ex, name);

+ 4 - 6
MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs

@@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            var allItems = _libraryManager.RootFolder.RecursiveChildren
-                .Where(i => !(i is IHasMusicGenres) && !(i is Game))
-                .ToList();
-
             var userLibraries = _userManager.Users
                 .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => !(m is IHasMusicGenres) && !(m is Game)).ToList()))
                 .ToList();
 
-            var allLibraryItems = allItems;
-
             var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase);
 
             // Populate counts of items
@@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
                 {
                     await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
                 }
+                catch (OperationCanceledException)
+                {
+                    // Don't clutter the log
+                }
                 catch (Exception ex)
                 {
                     _logger.ErrorException("Error updating counts for {0}", ex, name);

+ 4 - 6
MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs

@@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            var allItems = _libraryManager.RootFolder.RecursiveChildren
-                .Where(i => i is IHasMusicGenres)
-                .ToList();
-
             var userLibraries = _userManager.Users
                 .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => m is IHasMusicGenres).ToList()))
                 .ToList();
 
-            var allLibraryItems = allItems;
-
             var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase);
 
             // Populate counts of items
@@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
                 {
                     await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
                 }
+                catch (OperationCanceledException)
+                {
+                    // Don't clutter the log
+                }
                 catch (Exception ex)
                 {
                     _logger.ErrorException("Error updating counts for {0}", ex, name);

+ 3 - 1
MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs

@@ -41,7 +41,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
         /// <returns>Task.</returns>
         public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            return Task.Run(() => RunInternal(progress, cancellationToken));
+            RunInternal(progress, cancellationToken);
+
+            return Task.FromResult(true);
         }
 
         private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken)

+ 4 - 4
MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs

@@ -41,14 +41,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
-            var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList();
-
             var userLibraries = _userManager.Users
                 .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).ToList()))
                 .ToList();
 
-            var allLibraryItems = allItems;
-
             var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase);
 
             // Populate counts of items
@@ -81,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
                 {
                     await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false);
                 }
+                catch (OperationCanceledException)
+                {
+                    // Don't clutter the log
+                }
                 catch (Exception ex)
                 {
                     _logger.ErrorException("Error updating counts for {0}", ex, name);

+ 39 - 47
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -13,6 +13,8 @@
     <FileAlignment>512</FileAlignment>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
     <RestorePackages>true</RestorePackages>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -35,77 +37,65 @@
     <Reference Include="Alchemy">
       <HintPath>..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll</HintPath>
     </Reference>
-    <Reference Include="BdInfo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll</HintPath>
-    </Reference>
     <Reference Include="ICSharpCode.SharpZipLib">
       <HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
     </Reference>
     <Reference Include="Lucene.Net">
       <HintPath>..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll</HintPath>
     </Reference>
-    <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Reactive.Core">
+      <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Reactive.Interfaces">
+      <HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Reactive.Linq">
+      <HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Xml" />
+    <Reference Include="BDInfo">
+      <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll</HintPath>
+    </Reference>
+    <Reference Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack">
       <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Api.Swagger, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Api.Swagger">
       <HintPath>..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Common, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Common">
       <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Interfaces, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Interfaces">
       <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.OrmLite.SqlServer, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.OrmLite.SqlServer">
       <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Redis, Version=3.9.43.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Redis">
       <HintPath>..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.ServiceInterface, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.ServiceInterface">
       <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll</HintPath>
     </Reference>
-    <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Text">
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
     </Reference>
-    <Reference Include="System" />
-    <Reference Include="System.Core" />
-    <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="System.Data.SQLite">
       <HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.dll</HintPath>
     </Reference>
-    <Reference Include="System.Data.SQLite.Linq, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="System.Data.SQLite.Linq">
       <HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
     </Reference>
-    <Reference Include="System.Drawing" />
-    <Reference Include="System.Reactive.Core">
-      <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
-    </Reference>
-    <Reference Include="System.Reactive.Interfaces">
-      <HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll</HintPath>
-    </Reference>
-    <Reference Include="System.Reactive.Linq">
-      <HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath>
-    </Reference>
-    <Reference Include="System.Runtime.Serialization" />
-    <Reference Include="Microsoft.CSharp" />
-    <Reference Include="System.Data" />
-    <Reference Include="System.Web" />
-    <Reference Include="System.Xml" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
@@ -123,6 +113,7 @@
     <Compile Include="EntryPoints\Notifications\RemoteNotifications.cs" />
     <Compile Include="EntryPoints\Notifications\WebSocketNotifier.cs" />
     <Compile Include="EntryPoints\RefreshUsersMetadata.cs" />
+    <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
     <Compile Include="EntryPoints\WebSocketEvents.cs" />
     <Compile Include="HttpServer\HttpResultFactory.cs" />
     <Compile Include="HttpServer\HttpServer.cs" />
@@ -183,6 +174,7 @@
       <SubType>Code</SubType>
     </Compile>
     <Compile Include="Session\SessionWebSocketListener.cs" />
+    <Compile Include="Session\WebSocketController.cs" />
     <Compile Include="Sorting\AlbumArtistComparer.cs" />
     <Compile Include="Sorting\AlbumComparer.cs" />
     <Compile Include="Sorting\AlbumCountComparer.cs" />
@@ -222,19 +214,19 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj">
-      <Project>{c4d2573a-3fd3-441f-81af-174ac4cd4e1d}</Project>
+      <Project>{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}</Project>
       <Name>MediaBrowser.Common.Implementations</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
-      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
       <Name>MediaBrowser.Common</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
-      <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+      <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
       <Name>MediaBrowser.Controller</Name>
     </ProjectReference>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
-      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>

+ 1 - 1
MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs

@@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// <summary>
         /// The FF probe resource pool
         /// </summary>
-        private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2);
+        private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(1, 1);
 
         public string FFMpegPath { get; private set; }
 

+ 7 - 8
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -333,17 +333,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
         /// <returns>Task.</returns>
         public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
         {
-            return Task.Run(() =>
+            if (!Directory.Exists(_criticReviewsPath))
             {
-                if (!Directory.Exists(_criticReviewsPath))
-                {
-                    Directory.CreateDirectory(_criticReviewsPath);
-                }
+                Directory.CreateDirectory(_criticReviewsPath);
+            }
 
-                var path = Path.Combine(_criticReviewsPath, itemId + ".json");
+            var path = Path.Combine(_criticReviewsPath, itemId + ".json");
+
+            _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
 
-                _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
-            });
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 12 - 0
MediaBrowser.Server.Implementations/Providers/ImageSaver.cs

@@ -102,6 +102,18 @@ namespace MediaBrowser.Server.Implementations.Providers
 
                 using (source)
                 {
+                    // If the file is currently hidden we'll have to remove that or the save will fail
+                    var file = new FileInfo(path);
+
+                    // This will fail if the file is hidden
+                    if (file.Exists)
+                    {
+                        if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+                        {
+                            file.Attributes &= ~FileAttributes.Hidden;
+                        }
+                    } 
+                    
                     using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
                     {
                         await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);

+ 4 - 0
MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs

@@ -159,6 +159,10 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
             {
                 previouslyFailedImages = new List<string>();
             }
+            catch (DirectoryNotFoundException)
+            {
+                previouslyFailedImages = new List<string>();
+            }
 
             foreach (var video in videos)
             {

+ 140 - 11
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
@@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -75,6 +77,12 @@ namespace MediaBrowser.Server.Implementations.Session
             _userRepository = userRepository;
         }
 
+        private List<ISessionRemoteController> _remoteControllers;
+        public void AddParts(IEnumerable<ISessionRemoteController> remoteControllers)
+        {
+            _remoteControllers = remoteControllers.ToList();
+        }
+
         /// <summary>
         /// Gets all connections.
         /// </summary>
@@ -122,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Session
             var activityDate = DateTime.UtcNow;
 
             var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user);
-            
+
             session.LastActivityDate = activityDate;
 
             if (user == null)
@@ -207,25 +215,33 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <summary>
         /// Used to report that playback has started for an item
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="sessionId">The session id.</param>
+        /// <param name="info">The info.</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task OnPlaybackStart(BaseItem item, Guid sessionId)
+        /// <exception cref="System.ArgumentNullException">info</exception>
+        public async Task OnPlaybackStart(PlaybackInfo info)
         {
-            if (item == null)
+            if (info == null)
             {
-                throw new ArgumentNullException();
+                throw new ArgumentNullException("info");
+            }
+            if (info.SessionId == Guid.Empty)
+            {
+                throw new ArgumentNullException("info");
             }
 
-            var session = Sessions.First(i => i.Id.Equals(sessionId));
+            var session = Sessions.First(i => i.Id.Equals(info.SessionId));
+
+            var item = info.Item;
 
             UpdateNowPlayingItem(session, item, false, false);
 
+            session.CanSeek = info.CanSeek;
+            session.QueueableMediaTypes = info.QueueableMediaTypes;
+
             var key = item.GetUserDataKey();
 
             var user = session.User;
-            
+
             var data = _userDataRepository.GetUserData(user.Id, key);
 
             data.PlayCount++;
@@ -313,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Session
             {
                 throw new ArgumentOutOfRangeException("positionTicks");
             }
-            
+
             var session = Sessions.First(i => i.Id.Equals(sessionId));
 
             RemoveNowPlayingItem(session, item);
@@ -321,7 +337,7 @@ namespace MediaBrowser.Server.Implementations.Session
             var key = item.GetUserDataKey();
 
             var user = session.User;
-            
+
             var data = _userDataRepository.GetUserData(user.Id, key);
 
             if (positionTicks.HasValue)
@@ -400,5 +416,118 @@ namespace MediaBrowser.Server.Implementations.Session
 
             data.PlaybackPositionTicks = positionTicks;
         }
+
+        /// <summary>
+        /// Gets the session for remote control.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <returns>SessionInfo.</returns>
+        /// <exception cref="ResourceNotFoundException"></exception>
+        private SessionInfo GetSessionForRemoteControl(Guid sessionId)
+        {
+            var session = Sessions.First(i => i.Id.Equals(sessionId));
+
+            if (session == null)
+            {
+                throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
+            }
+
+            if (!session.SupportsRemoteControl)
+            {
+                throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
+            }
+            
+            return session;
+        }
+
+        /// <summary>
+        /// Gets the controllers.
+        /// </summary>
+        /// <param name="session">The session.</param>
+        /// <returns>IEnumerable{ISessionRemoteController}.</returns>
+        private IEnumerable<ISessionRemoteController> GetControllers(SessionInfo session)
+        {
+            return _remoteControllers.Where(i => i.Supports(session));
+        }
+
+        /// <summary>
+        /// Sends the system command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken)
+        {
+            var session = GetSessionForRemoteControl(sessionId);
+
+            var tasks = GetControllers(session).Select(i => i.SendSystemCommand(session, command, cancellationToken));
+
+            return Task.WhenAll(tasks);
+        }
+
+        /// <summary>
+        /// Sends the message command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken)
+        {
+            var session = GetSessionForRemoteControl(sessionId);
+
+            var tasks = GetControllers(session).Select(i => i.SendMessageCommand(session, command, cancellationToken));
+
+            return Task.WhenAll(tasks);
+        }
+
+        /// <summary>
+        /// Sends the play command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken)
+        {
+            var session = GetSessionForRemoteControl(sessionId);
+
+            var tasks = GetControllers(session).Select(i => i.SendPlayCommand(session, command, cancellationToken));
+
+            return Task.WhenAll(tasks);
+        }
+
+        /// <summary>
+        /// Sends the browse command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
+        {
+            var session = GetSessionForRemoteControl(sessionId);
+
+            var tasks = GetControllers(session).Select(i => i.SendBrowseCommand(session, command, cancellationToken));
+
+            return Task.WhenAll(tasks);
+        }
+
+        /// <summary>
+        /// Sends the playstate command.
+        /// </summary>
+        /// <param name="sessionId">The session id.</param>
+        /// <param name="command">The command.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken)
+        {
+            var session = GetSessionForRemoteControl(sessionId);
+
+            var tasks = GetControllers(session).Select(i => i.SendPlaystateCommand(session, command, cancellationToken));
+
+            return Task.WhenAll(tasks);
+        }
     }
 }

+ 42 - 10
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -101,16 +101,7 @@ namespace MediaBrowser.Server.Implementations.Session
             }
             else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase))
             {
-                _logger.Debug("Received PlaybackStart message");
-                
-                var session = _sessionManager.Sessions.FirstOrDefault(i => i.WebSockets.Contains(message.Connection));
-
-                if (session != null && session.User != null)
-                {
-                    var item = _dtoService.GetItemByDtoId(message.Data);
-
-                    _sessionManager.OnPlaybackStart(item, session.Id);
-                }
+                ReportPlaybackStart(message);
             }
             else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase))
             {
@@ -170,5 +161,46 @@ namespace MediaBrowser.Server.Implementations.Session
 
             return _trueTaskResult;
         }
+
+        /// <summary>
+        /// Reports the playback start.
+        /// </summary>
+        /// <param name="message">The message.</param>
+        private void ReportPlaybackStart(WebSocketMessageInfo message)
+        {
+            _logger.Debug("Received PlaybackStart message");
+            
+            var session = _sessionManager.Sessions
+                .FirstOrDefault(i => i.WebSockets.Contains(message.Connection));
+
+            if (session != null && session.User != null)
+            {
+                var vals = message.Data.Split('|');
+
+                var item = _dtoService.GetItemByDtoId(vals[0]);
+
+                var queueableMediaTypes = string.Empty;
+                var canSeek = true;
+
+                if (vals.Length > 1)
+                {
+                    canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase);
+                }
+                if (vals.Length > 2)
+                {
+                    queueableMediaTypes = vals[2];
+                }
+  
+                var info = new PlaybackInfo
+                {
+                    CanSeek = canSeek,
+                    Item = item,
+                    SessionId = session.Id,
+                    QueueableMediaTypes = queueableMediaTypes.Split(',').ToList()
+                };
+
+                _sessionManager.OnPlaybackStart(info);
+            }
+        }
     }
 }

+ 92 - 0
MediaBrowser.Server.Implementations/Session/WebSocketController.cs

@@ -0,0 +1,92 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Session;
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Session
+{
+    public class WebSocketController : ISessionRemoteController
+    {
+        public bool Supports(SessionInfo session)
+        {
+            return session.WebSockets.Any(i => i.State == WebSocketState.Open);
+        }
+
+        private IWebSocketConnection GetSocket(SessionInfo session)
+        {
+            var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+
+
+            if (socket == null)
+            {
+                throw new InvalidOperationException("The requested session does not have an open web socket.");
+            }
+
+            return socket;
+        }
+
+        public Task SendSystemCommand(SessionInfo session, SystemCommand command, CancellationToken cancellationToken)
+        {
+            var socket = GetSocket(session);
+
+            return socket.SendAsync(new WebSocketMessage<string>
+            {
+                MessageType = "SystemCommand",
+                Data = command.ToString()
+
+            }, cancellationToken);
+        }
+
+        public Task SendMessageCommand(SessionInfo session, MessageCommand command, CancellationToken cancellationToken)
+        {
+            var socket = GetSocket(session);
+
+            return socket.SendAsync(new WebSocketMessage<MessageCommand>
+            {
+                MessageType = "MessageCommand",
+                Data = command
+
+            }, cancellationToken);
+        }
+
+        public Task SendPlayCommand(SessionInfo session, PlayRequest command, CancellationToken cancellationToken)
+        {
+            var socket = GetSocket(session);
+
+            return socket.SendAsync(new WebSocketMessage<PlayRequest>
+            {
+                MessageType = "Play",
+                Data = command
+
+            }, cancellationToken);
+        }
+
+        public Task SendBrowseCommand(SessionInfo session, BrowseRequest command, CancellationToken cancellationToken)
+        {
+            var socket = GetSocket(session);
+
+            return socket.SendAsync(new WebSocketMessage<BrowseRequest>
+            {
+                MessageType = "Browse",
+                Data = command
+
+            }, cancellationToken);
+        }
+
+        public Task SendPlaystateCommand(SessionInfo session, PlaystateRequest command, CancellationToken cancellationToken)
+        {
+            var socket = GetSocket(session);
+
+            return socket.SendAsync(new WebSocketMessage<PlaystateRequest>
+            {
+                MessageType = "Playstate",
+                Data = command
+
+            }, cancellationToken);
+        }
+    }
+}

+ 3 - 1
MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs

@@ -92,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.WebSocket
         /// <returns>Task.</returns>
         public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
         {
-            return Task.Run(() => UserContext.Send(bytes));
+            UserContext.Send(bytes);
+
+            return Task.FromResult(true);
         }
 
         /// <summary>

+ 36 - 0
MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloader.cs

@@ -0,0 +1,36 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ServerApplication.FFMpeg
+{
+    public class FFMpegDownloader
+    {
+        private readonly IHttpClient _httpClient;
+        private readonly IApplicationPaths _appPaths;
+        private readonly ILogger _logger;
+        private readonly IZipClient _zipClient;
+
+        public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient)
+        {
+            _logger = logger;
+            _appPaths = appPaths;
+            _httpClient = httpClient;
+            _zipClient = zipClient;
+        }
+
+        public Task<FFMpegInfo> GetFFMpegInfo()
+        {
+			return Task.FromResult (new FFMpegInfo());
+        }
+    }
+}

+ 16 - 0
MediaBrowser.Server.Mono/MainWindow.cs

@@ -0,0 +1,16 @@
+using System;
+using Gtk;
+
+public partial class MainWindow: Gtk.Window
+{	
+	public MainWindow (): base (Gtk.WindowType.Toplevel)
+	{
+		Build ();
+	}
+
+	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
+	{
+		Application.Quit ();
+		a.RetVal = true;
+	}
+}

+ 119 - 0
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>10.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{A7FE75CD-3CB4-4E71-A5BF-5347721EC8E0}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
+    <AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>x86</PlatformTarget>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <PlatformTarget>x86</PlatformTarget>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" />
+    <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <Reference Include="glade-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <Reference Include="pango-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <Reference Include="atk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Data" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="gtk-gui\gui.stetic">
+      <LogicalName>gui.stetic</LogicalName>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="gtk-gui\generated.cs" />
+    <Compile Include="MainWindow.cs" />
+    <Compile Include="gtk-gui\MainWindow.cs" />
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="..\MediaBrowser.ServerApplication\EntryPoints\StartupWizard.cs">
+      <Link>EntryPoints\StartupWizard.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.ServerApplication\Native\BrowserLauncher.cs">
+      <Link>Native\BrowserLauncher.cs</Link>
+    </Compile>
+    <Compile Include="Native\Autorun.cs" />
+    <Compile Include="Native\ServerAuthorization.cs" />
+    <Compile Include="FFMpeg\FFMpegDownloader.cs" />
+    <Compile Include="..\MediaBrowser.ServerApplication\FFMpeg\FFMpegInfo.cs">
+      <Link>FFMpeg\FFMpegInfo.cs</Link>
+    </Compile>
+    <Compile Include="..\MediaBrowser.ServerApplication\ApplicationHost.cs">
+      <Link>ApplicationHost.cs</Link>
+    </Compile>
+    <Compile Include="Native\HttpMessageHandlerFactory.cs" />
+    <Compile Include="Native\Assemblies.cs" />
+    <Compile Include="Native\Sqlite.cs" />
+    <Compile Include="Native\NativeApp.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj">
+      <Project>{5624B7B5-B5A7-41D8-9F10-CC5611109619}</Project>
+      <Name>MediaBrowser.WebDashboard</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj">
+      <Project>{2E781478-814D-4A48-9D80-BFF206441A65}</Project>
+      <Name>MediaBrowser.Server.Implementations</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj">
+      <Project>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</Project>
+      <Name>MediaBrowser.Providers</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+      <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
+      <Name>MediaBrowser.Model</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+      <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
+      <Name>MediaBrowser.Controller</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj">
+      <Project>{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}</Project>
+      <Name>MediaBrowser.Common.Implementations</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+      <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project>
+      <Name>MediaBrowser.Common</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj">
+      <Project>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</Project>
+      <Name>MediaBrowser.Api</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="EntryPoints\" />
+    <Folder Include="Implementations\" />
+    <Folder Include="Native\" />
+    <Folder Include="FFMpeg\" />
+  </ItemGroup>
+</Project>

+ 22 - 0
MediaBrowser.Server.Mono/Native/Assemblies.cs

@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class Assemblies
+    /// </summary>
+    public static class Assemblies
+    {
+        /// <summary>
+        /// Gets the assemblies with parts.
+        /// </summary>
+        /// <returns>List{Assembly}.</returns>
+        public static List<Assembly> GetAssembliesWithParts()
+        {
+            var list = new List<Assembly>();
+
+            return list;
+        }
+    }
+}

+ 20 - 0
MediaBrowser.Server.Mono/Native/Autorun.cs

@@ -0,0 +1,20 @@
+using System;
+using System.IO;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class Autorun
+    /// </summary>
+    public static class Autorun
+    {
+        /// <summary>
+        /// Configures the specified autorun.
+        /// </summary>
+        /// <param name="autorun">if set to <c>true</c> [autorun].</param>
+        public static void Configure(bool autorun)
+        {
+
+        }
+    }
+}

+ 25 - 0
MediaBrowser.Server.Mono/Native/HttpMessageHandlerFactory.cs

@@ -0,0 +1,25 @@
+using System.Net;
+using System.Net.Cache;
+using System.Net.Http;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class HttpMessageHandlerFactory
+    /// </summary>
+    public static class HttpMessageHandlerFactory
+    {
+        /// <summary>
+        /// Gets the HTTP message handler.
+        /// </summary>
+        /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param>
+        /// <returns>HttpMessageHandler.</returns>
+        public static HttpMessageHandler GetHttpMessageHandler(bool enableHttpCompression)
+        {
+			return new HttpClientHandler
+            {
+                AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None
+            };
+        }
+    }
+}

+ 25 - 0
MediaBrowser.Server.Mono/Native/NativeApp.cs

@@ -0,0 +1,25 @@
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class NativeApp
+    /// </summary>
+    public static class NativeApp
+    {
+        /// <summary>
+        /// Shutdowns this instance.
+        /// </summary>
+        public static void Shutdown()
+        {
+            
+        }
+
+        /// <summary>
+        /// Restarts this instance.
+        /// </summary>
+        public static void Restart()
+        {
+            
+        }
+    }
+}

+ 26 - 0
MediaBrowser.Server.Mono/Native/ServerAuthorization.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class Authorization
+    /// </summary>
+    public static class ServerAuthorization
+    {
+        /// <summary>
+        /// Authorizes the server.
+        /// </summary>
+        /// <param name="httpServerPort">The HTTP server port.</param>
+        /// <param name="httpServerUrlPrefix">The HTTP server URL prefix.</param>
+        /// <param name="webSocketPort">The web socket port.</param>
+        /// <param name="udpPort">The UDP port.</param>
+        /// <param name="tempDirectory">The temp directory.</param>
+        public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int webSocketPort, int udpPort, string tempDirectory)
+        {
+
+        }
+    }
+}

+ 36 - 0
MediaBrowser.Server.Mono/Native/Sqlite.cs

@@ -0,0 +1,36 @@
+using System.Data;
+using System.Data.SQLite;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class Sqlite
+    /// </summary>
+    public static class Sqlite
+    {
+        /// <summary>
+        /// Connects to db.
+        /// </summary>
+        /// <param name="dbPath">The db path.</param>
+        /// <returns>Task{IDbConnection}.</returns>
+        /// <exception cref="System.ArgumentNullException">dbPath</exception>
+        public static async Task<IDbConnection> OpenDatabase(string dbPath)
+        {
+            var connectionstr = new SQLiteConnectionStringBuilder
+            {
+                PageSize = 4096,
+                CacheSize = 4096,
+                SyncMode = SynchronizationModes.Normal,
+                DataSource = dbPath,
+                JournalMode = SQLiteJournalModeEnum.Wal
+            };
+
+            var connection = new SQLiteConnection(connectionstr.ConnectionString);
+
+            await connection.OpenAsync().ConfigureAwait(false);
+
+            return connection;
+        }
+    }
+}

+ 16 - 0
MediaBrowser.Server.Mono/Program.cs

@@ -0,0 +1,16 @@
+using System;
+using Gtk;
+
+namespace MediaBrowser.Server.Mono
+{
+	class MainClass
+	{
+		public static void Main (string[] args)
+		{
+			Application.Init ();
+			MainWindow win = new MainWindow ();
+			win.Show ();
+			Application.Run ();
+		}
+	}
+}

+ 22 - 0
MediaBrowser.Server.Mono/Properties/AssemblyInfo.cs

@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes. 
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle ("MediaBrowser.Server.Mono")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("Luke")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion ("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly, 
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+

+ 20 - 0
MediaBrowser.Server.Mono/gtk-gui/MainWindow.cs

@@ -0,0 +1,20 @@
+
+// This file has been generated by the GUI designer. Do not modify.
+public partial class MainWindow
+{
+	protected virtual void Build ()
+	{
+		global::Stetic.Gui.Initialize (this);
+		// Widget MainWindow
+		this.Name = "MainWindow";
+		this.Title = global::Mono.Unix.Catalog.GetString ("MainWindow");
+		this.WindowPosition = ((global::Gtk.WindowPosition)(4));
+		if ((this.Child != null)) {
+			this.Child.ShowAll ();
+		}
+		this.DefaultWidth = 400;
+		this.DefaultHeight = 300;
+		this.Show ();
+		this.DeleteEvent += new global::Gtk.DeleteEventHandler (this.OnDeleteEvent);
+	}
+}

+ 29 - 0
MediaBrowser.Server.Mono/gtk-gui/generated.cs

@@ -0,0 +1,29 @@
+
+// This file has been generated by the GUI designer. Do not modify.
+namespace Stetic
+{
+	internal class Gui
+	{
+		private static bool initialized;
+
+		internal static void Initialize (Gtk.Widget iconRenderer)
+		{
+			if ((Stetic.Gui.initialized == false)) {
+				Stetic.Gui.initialized = true;
+			}
+		}
+	}
+
+	internal class ActionGroups
+	{
+		public static Gtk.ActionGroup GetActionGroup (System.Type type)
+		{
+			return Stetic.ActionGroups.GetActionGroup (type.FullName);
+		}
+
+		public static Gtk.ActionGroup GetActionGroup (string name)
+		{
+			return null;
+		}
+	}
+}

+ 20 - 0
MediaBrowser.Server.Mono/gtk-gui/gui.stetic

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<stetic-interface>
+  <configuration>
+    <images-root-path>..</images-root-path>
+    <target-gtk-version>2.12</target-gtk-version>
+  </configuration>
+  <import>
+    <widget-library name="glade-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <widget-library name="../bin/Debug/MediaBrowser.Server.Mono.exe" internal="true" />
+  </import>
+  <widget class="Gtk.Window" id="MainWindow" design-size="400 300">
+    <property name="MemberName" />
+    <property name="Title" translatable="yes">MainWindow</property>
+    <property name="WindowPosition">CenterOnParent</property>
+    <signal name="DeleteEvent" handler="OnDeleteEvent" />
+    <child>
+      <placeholder />
+    </child>
+  </widget>
+</stetic-interface>

+ 0 - 53
MediaBrowser.ServerApplication/App.xaml.cs

@@ -154,58 +154,5 @@ namespace MediaBrowser.ServerApplication
         {
             Dispatcher.Invoke(Shutdown);
         }
-
-        /// <summary>
-        /// Opens the dashboard page.
-        /// </summary>
-        /// <param name="page">The page.</param>
-        /// <param name="loggedInUser">The logged in user.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="appHost">The app host.</param>
-        public static void OpenDashboardPage(string page, User loggedInUser, IServerConfigurationManager configurationManager, IServerApplicationHost appHost)
-        {
-            var url = "http://localhost:" + configurationManager.Configuration.HttpServerPortNumber + "/" +
-                      appHost.WebApplicationName + "/dashboard/" + page;
-
-            OpenUrl(url);
-        }
-
-        /// <summary>
-        /// Opens the URL.
-        /// </summary>
-        /// <param name="url">The URL.</param>
-        public static void OpenUrl(string url)
-        {
-            var process = new Process
-            {
-                StartInfo = new ProcessStartInfo
-                {
-                    FileName = url
-                },
-
-                EnableRaisingEvents = true
-            };
-
-            process.Exited += ProcessExited;
-
-            try
-            {
-                process.Start();
-            }
-            catch (Exception ex)
-            {
-                MessageBox.Show("There was an error launching your web browser. Please check your defualt browser settings.");
-            }
-        }
-
-        /// <summary>
-        /// Processes the exited.
-        /// </summary>
-        /// <param name="sender">The sender.</param>
-        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
-        static void ProcessExited(object sender, EventArgs e)
-        {
-            ((Process)sender).Dispose();
-        }
     }
 }

+ 46 - 97
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -24,7 +24,6 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
-using MediaBrowser.IsoMounter;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
@@ -36,6 +35,7 @@ using MediaBrowser.Server.Implementations.BdInfo;
 using MediaBrowser.Server.Implementations.Configuration;
 using MediaBrowser.Server.Implementations.Drawing;
 using MediaBrowser.Server.Implementations.Dto;
+using MediaBrowser.Server.Implementations.EntryPoints;
 using MediaBrowser.Server.Implementations.HttpServer;
 using MediaBrowser.Server.Implementations.IO;
 using MediaBrowser.Server.Implementations.Library;
@@ -46,16 +46,14 @@ using MediaBrowser.Server.Implementations.Providers;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Session;
 using MediaBrowser.Server.Implementations.WebSocket;
-using MediaBrowser.ServerApplication.Implementations;
+using MediaBrowser.ServerApplication.FFMpeg;
+using MediaBrowser.ServerApplication.Native;
 using MediaBrowser.WebDashboard.Api;
 using System;
 using System.Collections.Generic;
-using System.Data.SQLite;
-using System.Diagnostics;
+using System.Data;
 using System.IO;
 using System.Linq;
-using System.Net;
-using System.Net.Cache;
 using System.Net.Http;
 using System.Reflection;
 using System.Threading;
@@ -68,8 +66,6 @@ namespace MediaBrowser.ServerApplication
     /// </summary>
     public class ApplicationHost : BaseApplicationHost<ServerApplicationPaths>, IServerApplicationHost
     {
-        internal const int UdpServerPort = 7359;
-
         /// <summary>
         /// Gets the server kernel.
         /// </summary>
@@ -142,11 +138,6 @@ namespace MediaBrowser.ServerApplication
         /// <value>The provider manager.</value>
         private IProviderManager ProviderManager { get; set; }
         /// <summary>
-        /// Gets or sets the zip client.
-        /// </summary>
-        /// <value>The zip client.</value>
-        private IZipClient ZipClient { get; set; }
-        /// <summary>
         /// Gets or sets the HTTP server.
         /// </summary>
         /// <value>The HTTP server.</value>
@@ -161,6 +152,7 @@ namespace MediaBrowser.ServerApplication
         private IMediaEncoder MediaEncoder { get; set; }
 
         private IIsoManager IsoManager { get; set; }
+        private ISessionManager SessionManager { get; set; }
 
         private ILocalizationManager LocalizationManager { get; set; }
 
@@ -174,14 +166,6 @@ namespace MediaBrowser.ServerApplication
         private IItemRepository ItemRepository { get; set; }
         private INotificationsRepository NotificationsRepository { get; set; }
 
-        /// <summary>
-        /// The full path to our startmenu shortcut
-        /// </summary>
-        protected override string ProductShortcutPath
-        {
-            get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Media Browser 3", "Media Browser Server.lnk"); }
-        }
-
         private Task<IHttpServer> _httpServerCreationTask;
 
         /// <summary>
@@ -255,8 +239,7 @@ namespace MediaBrowser.ServerApplication
 
             RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer());
 
-            ZipClient = new DotNetZipClient();
-            RegisterSingleInstance(ZipClient);
+            var mediaEncoderTask = RegisterMediaEncoder();
 
             UserDataRepository = new SqliteUserDataRepository(ApplicationPaths, JsonSerializer, LogManager);
             RegisterSingleInstance(UserDataRepository);
@@ -284,10 +267,8 @@ namespace MediaBrowser.ServerApplication
 
             RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager));
 
-            await RegisterMediaEncoder().ConfigureAwait(false);
-
-            var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository);
-            RegisterSingleInstance<ISessionManager>(clientConnectionManager);
+            SessionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository);
+            RegisterSingleInstance<ISessionManager>(SessionManager);
 
             HttpServer = await _httpServerCreationTask.ConfigureAwait(false);
             RegisterSingleInstance(HttpServer, false);
@@ -310,7 +291,7 @@ namespace MediaBrowser.ServerApplication
 
             await ConfigureNotificationsRepository().ConfigureAwait(false);
 
-            await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask).ConfigureAwait(false);
+            await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask, mediaEncoderTask).ConfigureAwait(false);
 
             SetKernelProperties();
         }
@@ -406,27 +387,14 @@ namespace MediaBrowser.ServerApplication
         /// <param name="dbPath">The db path.</param>
         /// <returns>Task{IDbConnection}.</returns>
         /// <exception cref="System.ArgumentNullException">dbPath</exception>
-        private static async Task<SQLiteConnection> ConnectToDb(string dbPath)
+        private static Task<IDbConnection> ConnectToDb(string dbPath)
         {
             if (string.IsNullOrEmpty(dbPath))
             {
                 throw new ArgumentNullException("dbPath");
             }
 
-            var connectionstr = new SQLiteConnectionStringBuilder
-            {
-                PageSize = 4096,
-                CacheSize = 4096,
-                SyncMode = SynchronizationModes.Normal,
-                DataSource = dbPath,
-                JournalMode = SQLiteJournalModeEnum.Wal
-            };
-
-            var connection = new SQLiteConnection(connectionstr.ConnectionString);
-
-            await connection.OpenAsync().ConfigureAwait(false);
-
-            return connection;
+            return Sqlite.OpenDatabase(dbPath);
         }
 
         /// <summary>
@@ -477,6 +445,8 @@ namespace MediaBrowser.ServerApplication
 
             IsoManager.AddParts(GetExports<IIsoMounter>());
 
+            SessionManager.AddParts(GetExports<ISessionRemoteController>());
+
             ImageProcessor.AddParts(GetExports<IImageEnhancer>());
         }
 
@@ -527,7 +497,6 @@ namespace MediaBrowser.ServerApplication
             {
                 NotifyPendingRestart();
             }
-
         }
 
         /// <summary>
@@ -544,7 +513,7 @@ namespace MediaBrowser.ServerApplication
                 Logger.ErrorException("Error sending server restart web socket message", ex);
             }
 
-            MainStartup.Restart();
+            NativeApp.Restart();
         }
 
         /// <summary>
@@ -568,44 +537,44 @@ namespace MediaBrowser.ServerApplication
         /// <returns>IEnumerable{Assembly}.</returns>
         protected override IEnumerable<Assembly> GetComposablePartAssemblies()
         {
+            var list = Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)
+                .Select(LoadAssembly)
+                .Where(a => a != null)
+                .ToList();
+            
             // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
             // This will prevent the .dll file from getting locked, and allow us to replace it when needed
-            foreach (var pluginAssembly in Directory
-                .EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)
-                .Select(LoadAssembly).Where(a => a != null))
-            {
-                yield return pluginAssembly;
-            }
 
             // Include composable parts in the Api assembly 
-            yield return typeof(ApiEntryPoint).Assembly;
+            list.Add(typeof(ApiEntryPoint).Assembly);
 
             // Include composable parts in the Dashboard assembly 
-            yield return typeof(DashboardInfo).Assembly;
+            list.Add(typeof(DashboardInfo).Assembly);
 
             // Include composable parts in the Model assembly 
-            yield return typeof(SystemInfo).Assembly;
+            list.Add(typeof(SystemInfo).Assembly);
 
             // Include composable parts in the Common assembly 
-            yield return typeof(IApplicationHost).Assembly;
+            list.Add(typeof(IApplicationHost).Assembly);
 
             // Include composable parts in the Controller assembly 
-            yield return typeof(Kernel).Assembly;
+            list.Add(typeof(Kernel).Assembly);
 
             // Include composable parts in the Providers assembly 
-            yield return typeof(ImagesByNameProvider).Assembly;
+            list.Add(typeof(ImagesByNameProvider).Assembly);
 
             // Common implementations
-            yield return typeof(TaskManager).Assembly;
+            list.Add(typeof(TaskManager).Assembly);
 
             // Server implementations
-            yield return typeof(ServerApplicationPaths).Assembly;
+            list.Add(typeof(ServerApplicationPaths).Assembly);
 
-            // Pismo
-            yield return typeof(PismoIsoManager).Assembly;
+            list.AddRange(Assemblies.GetAssembliesWithParts());
 
             // Include composable parts in the running assembly
-            yield return GetType().Assembly;
+            list.Add(GetType().Assembly);
+
+            return list;
         }
 
         private readonly string _systemId = Environment.MachineName.GetMD5().ToString();
@@ -664,7 +633,7 @@ namespace MediaBrowser.ServerApplication
                 Logger.ErrorException("Error sending server shutdown web socket message", ex);
             }
 
-            MainStartup.Shutdown();
+            NativeApp.Shutdown();
         }
 
         /// <summary>
@@ -674,36 +643,16 @@ namespace MediaBrowser.ServerApplication
         {
             Logger.Info("Requesting administrative access to authorize http server");
 
-            // Create a temp file path to extract the bat file to
-            var tmpFile = Path.Combine(ConfigurationManager.CommonApplicationPaths.TempDirectory, Guid.NewGuid() + ".bat");
-
-            // Extract the bat file
-            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.ServerApplication.RegisterServer.bat"))
+            try
             {
-                using (var fileStream = File.Create(tmpFile))
-                {
-                    stream.CopyTo(fileStream);
-                }
+                ServerAuthorization.AuthorizeServer(ServerConfigurationManager.Configuration.HttpServerPortNumber,
+                    HttpServerUrlPrefix, ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber,
+                    UdpServerEntryPoint.PortNumber,
+                    ConfigurationManager.CommonApplicationPaths.TempDirectory);
             }
-
-            var startInfo = new ProcessStartInfo
-            {
-                FileName = tmpFile,
-
-                Arguments = string.Format("{0} {1} {2} {3}", ServerConfigurationManager.Configuration.HttpServerPortNumber,
-                HttpServerUrlPrefix,
-                UdpServerPort,
-                ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber),
-
-                CreateNoWindow = true,
-                WindowStyle = ProcessWindowStyle.Hidden,
-                Verb = "runas",
-                ErrorDialog = false
-            };
-
-            using (var process = Process.Start(startInfo))
+            catch (Exception ex)
             {
-                process.WaitForExit();
+                Logger.ErrorException("Error authorizing server", ex);
             }
         }
 
@@ -713,8 +662,7 @@ namespace MediaBrowser.ServerApplication
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="progress">The progress.</param>
         /// <returns>Task{CheckForUpdateResult}.</returns>
-        public override async Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken,
-                                                                    IProgress<double> progress)
+        public override async Task<CheckForUpdateResult> CheckForApplicationUpdate(CancellationToken cancellationToken, IProgress<double> progress)
         {
             var availablePackages = await InstallationManager.GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
 
@@ -745,11 +693,12 @@ namespace MediaBrowser.ServerApplication
         /// <returns>HttpMessageHandler.</returns>
         protected override HttpMessageHandler GetHttpMessageHandler(bool enableHttpCompression)
         {
-            return new WebRequestHandler
-            {
-                CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate),
-                AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None
-            };
+            return HttpMessageHandlerFactory.GetHttpMessageHandler(enableHttpCompression);
+        }
+
+        protected override void ConfigureAutoRunAtStartup(bool autorun)
+        {
+            Autorun.Configure(autorun);
         }
     }
 }

+ 8 - 6
MediaBrowser.ServerApplication/EntryPoints/StartupWizard.cs

@@ -3,9 +3,10 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Logging;
-using System.ComponentModel;
+using System;
 using System.Linq;
-using System.Windows;
+using System.Windows.Forms;
+using MediaBrowser.ServerApplication.Native;
 
 namespace MediaBrowser.ServerApplication.EntryPoints
 {
@@ -31,9 +32,10 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         /// </summary>
         /// <param name="appHost">The app host.</param>
         /// <param name="userManager">The user manager.</param>
-        public StartupWizard(IServerApplicationHost appHost, IUserManager userManager, IServerConfigurationManager configurationManager)
+        public StartupWizard(IServerApplicationHost appHost, IUserManager userManager, IServerConfigurationManager configurationManager, ILogger logger)
         {
             _appHost = appHost;
+            _logger = logger;
             _userManager = userManager;
             _configurationManager = configurationManager;
         }
@@ -58,9 +60,9 @@ namespace MediaBrowser.ServerApplication.EntryPoints
 
             try
             {
-                App.OpenDashboardPage("wizardstart.html", user, _configurationManager, _appHost);
+                BrowserLauncher.OpenDashboardPage("wizardstart.html", user, _configurationManager, _appHost, _logger);
             }
-            catch (Win32Exception ex)
+            catch (Exception ex)
             {
                 _logger.ErrorException("Error launching startup wizard", ex);
 
@@ -75,4 +77,4 @@ namespace MediaBrowser.ServerApplication.EntryPoints
         {
         }
     }
-}
+}

+ 302 - 0
MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs

@@ -0,0 +1,302 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ServerApplication.FFMpeg
+{
+    public class FFMpegDownloader
+    {
+        private readonly IHttpClient _httpClient;
+        private readonly IApplicationPaths _appPaths;
+        private readonly ILogger _logger;
+        private readonly IZipClient _zipClient;
+
+        private const string Version = "ffmpeg20130904";
+
+        private readonly string[] _fontUrls = new[]
+            {
+                "https://www.dropbox.com/s/pj847twf7riq0j7/ARIALUNI.7z?dl=1"
+            };
+
+        private readonly string[] _ffMpegUrls = new[]
+                {
+                    "https://github.com/MediaBrowser/MediaBrowser/raw/master/MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z",
+
+                    "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20130904-git-f974289-win32-static.7z",
+                    "https://www.dropbox.com/s/a81cb2ob23fwcfs/ffmpeg-20130904-git-f974289-win32-static.7z?dl=1"
+                };
+
+        public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient)
+        {
+            _logger = logger;
+            _appPaths = appPaths;
+            _httpClient = httpClient;
+            _zipClient = zipClient;
+        }
+
+        public async Task<FFMpegInfo> GetFFMpegInfo()
+        {
+            var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), Version);
+
+            var info = new FFMpegInfo
+            {
+                ProbePath = Path.Combine(versionedDirectoryPath, "ffprobe.exe"),
+                Path = Path.Combine(versionedDirectoryPath, "ffmpeg.exe"),
+                Version = Version
+            };
+
+            if (!Directory.Exists(versionedDirectoryPath))
+            {
+                Directory.CreateDirectory(versionedDirectoryPath);
+            }
+
+            var tasks = new List<Task>();
+
+            if (!File.Exists(info.ProbePath) || !File.Exists(info.Path))
+            {
+                tasks.Add(DownloadFFMpeg(info));
+            }
+
+            tasks.Add(DownloadFonts(versionedDirectoryPath));
+
+            await Task.WhenAll(tasks).ConfigureAwait(false);
+
+            return info;
+        }
+
+        private async Task DownloadFFMpeg(FFMpegInfo info)
+        {
+            foreach (var url in _ffMpegUrls)
+            {
+                try
+                {
+                    var tempFile = await DownloadFFMpeg(info, url).ConfigureAwait(false);
+
+                    ExtractFFMpeg(tempFile, Path.GetDirectoryName(info.Path));
+                    return;
+                }
+                catch (HttpException ex)
+                {
+
+                }
+            }
+
+            throw new ApplicationException("Unable to download required components. Please try again later.");
+        }
+
+        private Task<string> DownloadFFMpeg(FFMpegInfo info, string url)
+        {
+            return _httpClient.GetTempFile(new HttpRequestOptions
+            {
+                Url = url,
+                CancellationToken = CancellationToken.None,
+                Progress = new Progress<double>(),
+
+                // Make it look like a browser
+                // Try to hide that we're direct linking
+                UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.47 Safari/537.36"
+            });
+        }
+
+        private void ExtractFFMpeg(string tempFile, string targetFolder)
+        {
+            _logger.Debug("Extracting ffmpeg from {0}", tempFile);
+
+            var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
+
+            if (!Directory.Exists(tempFolder))
+            {
+                Directory.CreateDirectory(tempFolder);
+            }
+
+            try
+            {
+                Extract7zArchive(tempFile, tempFolder);
+
+                var files = Directory.EnumerateFiles(tempFolder, "*.exe", SearchOption.AllDirectories).ToList();
+
+                foreach (var file in files)
+                {
+                    File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file)));
+                }
+            }
+            finally
+            {
+                DeleteFile(tempFile);
+            }
+        }
+
+        private void Extract7zArchive(string archivePath, string targetPath)
+        {
+            _zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
+        }
+
+        private void DeleteFile(string path)
+        {
+            try
+            {
+                File.Delete(path);
+            }
+            catch (IOException ex)
+            {
+                _logger.ErrorException("Error deleting temp file {0}", ex, path);
+            }
+        }
+
+        /// <summary>
+        /// Extracts the fonts.
+        /// </summary>
+        /// <param name="targetPath">The target path.</param>
+        private async Task DownloadFonts(string targetPath)
+        {
+            try
+            {
+                var fontsDirectory = Path.Combine(targetPath, "fonts");
+
+                if (!Directory.Exists(fontsDirectory))
+                {
+                    Directory.CreateDirectory(fontsDirectory);
+                }
+
+                const string fontFilename = "ARIALUNI.TTF";
+
+                var fontFile = Path.Combine(fontsDirectory, fontFilename);
+
+                if (!File.Exists(fontFile))
+                {
+                    await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false);
+                }
+
+                await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
+            }
+            catch (HttpException ex)
+            {
+                // Don't let the server crash because of this
+                _logger.ErrorException("Error downloading ffmpeg font files", ex);
+            }
+            catch (Exception ex)
+            {
+                // Don't let the server crash because of this
+                _logger.ErrorException("Error writing ffmpeg font files", ex);
+            }
+        }
+
+        /// <summary>
+        /// Downloads the font file.
+        /// </summary>
+        /// <param name="fontsDirectory">The fonts directory.</param>
+        /// <param name="fontFilename">The font filename.</param>
+        /// <returns>Task.</returns>
+        private async Task DownloadFontFile(string fontsDirectory, string fontFilename)
+        {
+            var existingFile = Directory
+                .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
+                .FirstOrDefault();
+
+            if (existingFile != null)
+            {
+                try
+                {
+                    File.Copy(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
+                    return;
+                }
+                catch (IOException ex)
+                {
+                    // Log this, but don't let it fail the operation
+                    _logger.ErrorException("Error copying file", ex);
+                }
+            }
+
+            string tempFile = null;
+
+            foreach (var url in _fontUrls)
+            {
+                try
+                {
+                    tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
+                    {
+                        Url = url,
+                        Progress = new Progress<double>()
+
+                    }).ConfigureAwait(false);
+
+                    break;
+                }
+                catch (Exception ex)
+                {
+                    // The core can function without the font file, so handle this
+                    _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url);
+                }
+            }
+
+            if (string.IsNullOrEmpty(tempFile))
+            {
+                return;
+            }
+
+            Extract7zArchive(tempFile, fontsDirectory);
+
+            try
+            {
+                File.Delete(tempFile);
+            }
+            catch (IOException ex)
+            {
+                // Log this, but don't let it fail the operation
+                _logger.ErrorException("Error deleting temp file {0}", ex, tempFile);
+            }
+        }
+
+        /// <summary>
+        /// Writes the font config file.
+        /// </summary>
+        /// <param name="fontsDirectory">The fonts directory.</param>
+        /// <returns>Task.</returns>
+        private async Task WriteFontConfigFile(string fontsDirectory)
+        {
+            const string fontConfigFilename = "fonts.conf";
+            var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
+
+            if (!File.Exists(fontConfigFile))
+            {
+                var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
+
+                var bytes = Encoding.UTF8.GetBytes(contents);
+
+                using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
+                                                    FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize,
+                                                    FileOptions.Asynchronous))
+                {
+                    await fileStream.WriteAsync(bytes, 0, bytes.Length);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the media tools path.
+        /// </summary>
+        /// <param name="create">if set to <c>true</c> [create].</param>
+        /// <returns>System.String.</returns>
+        private string GetMediaToolsPath(bool create)
+        {
+            var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
+
+            if (create && !Directory.Exists(path))
+            {
+                Directory.CreateDirectory(path);
+            }
+
+            return path;
+        }
+    }
+}

+ 24 - 0
MediaBrowser.ServerApplication/FFMpeg/FFMpegInfo.cs

@@ -0,0 +1,24 @@
+namespace MediaBrowser.ServerApplication.FFMpeg
+{
+    /// <summary>
+    /// Class FFMpegInfo
+    /// </summary>
+    public class FFMpegInfo
+    {
+        /// <summary>
+        /// Gets or sets the path.
+        /// </summary>
+        /// <value>The path.</value>
+        public string Path { get; set; }
+        /// <summary>
+        /// Gets or sets the probe path.
+        /// </summary>
+        /// <value>The probe path.</value>
+        public string ProbePath { get; set; }
+        /// <summary>
+        /// Gets or sets the version.
+        /// </summary>
+        /// <value>The version.</value>
+        public string Version { get; set; }
+    }
+}

+ 1 - 0
MediaBrowser.ServerApplication/FFMpeg/ffmpeg-20130904-git-f974289-win32-static.7z.REMOVED.git-id

@@ -0,0 +1 @@
+8f1dfd62d31e48c31bef4b9ccc0e514f46650a79

+ 0 - 43
MediaBrowser.ServerApplication/Implementations/DotNetZipClient.cs

@@ -1,43 +0,0 @@
-using Ionic.Zip;
-using MediaBrowser.Model.IO;
-using System.IO;
-
-namespace MediaBrowser.ServerApplication.Implementations
-{
-    /// <summary>
-    /// Class DotNetZipClient
-    /// </summary>
-    public class DotNetZipClient : IZipClient
-    {
-        /// <summary>
-        /// Extracts all.
-        /// </summary>
-        /// <param name="sourceFile">The source file.</param>
-        /// <param name="targetPath">The target path.</param>
-        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
-        public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
-        {
-            using (var fileStream = File.OpenRead(sourceFile))
-            {
-                using (var zipFile = ZipFile.Read(fileStream))
-                {
-                    zipFile.ExtractAll(targetPath, overwriteExistingFiles ? ExtractExistingFileAction.OverwriteSilently : ExtractExistingFileAction.DoNotOverwrite);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Extracts all.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="targetPath">The target path.</param>
-        /// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
-        public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
-        {
-            using (var zipFile = ZipFile.Read(source))
-            {
-                zipFile.ExtractAll(targetPath, overwriteExistingFiles ? ExtractExistingFileAction.OverwriteSilently : ExtractExistingFileAction.DoNotOverwrite);
-            }
-        }
-    }
-}

+ 0 - 205
MediaBrowser.ServerApplication/Implementations/FFMpegDownloader.cs

@@ -1,205 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.ServerApplication.Implementations
-{
-    public class FFMpegDownloader
-    {
-        private readonly IZipClient _zipClient;
-        private readonly IHttpClient _httpClient;
-        private readonly IApplicationPaths _appPaths;
-        private readonly ILogger _logger;
-
-        public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient)
-        {
-            _logger = logger;
-            _appPaths = appPaths;
-            _httpClient = httpClient;
-            _zipClient = zipClient;
-        }
-
-        public async Task<FFMpegInfo> GetFFMpegInfo()
-        {
-            var assembly = GetType().Assembly;
-
-            var prefix = GetType().Namespace + ".";
-
-            var srch = prefix + "ffmpeg";
-
-            var resource = assembly.GetManifestResourceNames().First(r => r.StartsWith(srch));
-
-            var filename =
-                resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length);
-
-            var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true),
-                                                      Path.GetFileNameWithoutExtension(filename));
-
-            if (!Directory.Exists(versionedDirectoryPath))
-            {
-                Directory.CreateDirectory(versionedDirectoryPath);
-            }
-
-            await ExtractTools(assembly, resource, versionedDirectoryPath).ConfigureAwait(false);
-
-            return new FFMpegInfo
-            {
-                ProbePath = Path.Combine(versionedDirectoryPath, "ffprobe.exe"),
-                Path = Path.Combine(versionedDirectoryPath, "ffmpeg.exe"),
-                Version = Path.GetFileNameWithoutExtension(versionedDirectoryPath)
-            };
-        }
-
-        /// <summary>
-        /// Extracts the tools.
-        /// </summary>
-        /// <param name="assembly">The assembly.</param>
-        /// <param name="zipFileResourcePath">The zip file resource path.</param>
-        /// <param name="targetPath">The target path.</param>
-        private async Task ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
-        {
-            using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
-            {
-                _zipClient.ExtractAll(resourceStream, targetPath, false);
-            }
-
-            try
-            {
-                await DownloadFonts(targetPath).ConfigureAwait(false);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error getting ffmpeg font files", ex);
-            }
-        }
-
-        private const string FontUrl = "https://www.dropbox.com/s/9nb76tybcsw5xrk/ARIALUNI.zip?dl=1";
-
-        /// <summary>
-        /// Extracts the fonts.
-        /// </summary>
-        /// <param name="targetPath">The target path.</param>
-        private async Task DownloadFonts(string targetPath)
-        {
-            var fontsDirectory = Path.Combine(targetPath, "fonts");
-
-            if (!Directory.Exists(fontsDirectory))
-            {
-                Directory.CreateDirectory(fontsDirectory);
-            }
-
-            const string fontFilename = "ARIALUNI.TTF";
-
-            var fontFile = Path.Combine(fontsDirectory, fontFilename);
-
-            if (!File.Exists(fontFile))
-            {
-                await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false);
-            }
-
-            await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Downloads the font file.
-        /// </summary>
-        /// <param name="fontsDirectory">The fonts directory.</param>
-        /// <param name="fontFilename">The font filename.</param>
-        /// <returns>Task.</returns>
-        private async Task DownloadFontFile(string fontsDirectory, string fontFilename)
-        {
-            var existingFile = Directory
-                .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
-                .FirstOrDefault();
-
-            if (existingFile != null)
-            {
-                try
-                {
-                    File.Copy(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
-                    return;
-                }
-                catch (IOException ex)
-                {
-                    // Log this, but don't let it fail the operation
-                    _logger.ErrorException("Error copying file", ex);
-                }
-            }
-
-            var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
-            {
-                Url = FontUrl,
-                Progress = new Progress<double>()
-            });
-
-            _zipClient.ExtractAll(tempFile, fontsDirectory, true);
-
-            try
-            {
-                File.Delete(tempFile);
-            }
-            catch (IOException ex)
-            {
-                // Log this, but don't let it fail the operation
-                _logger.ErrorException("Error deleting temp file {0}", ex, tempFile);
-            }
-        }
-
-        /// <summary>
-        /// Writes the font config file.
-        /// </summary>
-        /// <param name="fontsDirectory">The fonts directory.</param>
-        /// <returns>Task.</returns>
-        private async Task WriteFontConfigFile(string fontsDirectory)
-        {
-            const string fontConfigFilename = "fonts.conf";
-            var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
-
-            if (!File.Exists(fontConfigFile))
-            {
-                var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
-
-                var bytes = Encoding.UTF8.GetBytes(contents);
-
-                using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
-                                                    FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize,
-                                                    FileOptions.Asynchronous))
-                {
-                    await fileStream.WriteAsync(bytes, 0, bytes.Length);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the media tools path.
-        /// </summary>
-        /// <param name="create">if set to <c>true</c> [create].</param>
-        /// <returns>System.String.</returns>
-        private string GetMediaToolsPath(bool create)
-        {
-            var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
-
-            if (create && !Directory.Exists(path))
-            {
-                Directory.CreateDirectory(path);
-            }
-
-            return path;
-        }
-    }
-
-    public class FFMpegInfo
-    {
-        public string Path { get; set; }
-        public string ProbePath { get; set; }
-        public string Version { get; set; }
-    }
-}

+ 0 - 1
MediaBrowser.ServerApplication/Implementations/ffmpeg20130904.zip.REMOVED.git-id

@@ -1 +0,0 @@
-3496b2cde22e7c4cb56b480dd2da637167d51e78

+ 0 - 5
MediaBrowser.ServerApplication/Implementations/readme.txt

@@ -1,5 +0,0 @@
-This is the 32-bit static build of ffmpeg, located at:
-
-http://ffmpeg.zeranoe.com/builds/
-
-The zip file contains both ffmpeg and ffprobe, and is suffixed with the date of the build.

+ 0 - 1
MediaBrowser.ServerApplication/MainStartup.cs

@@ -11,7 +11,6 @@ using System.IO;
 using System.Linq;
 using System.ServiceProcess;
 using System.Threading;
-using System.Threading.Tasks;
 using System.Windows;
 
 namespace MediaBrowser.ServerApplication

+ 9 - 8
MediaBrowser.ServerApplication/MainWindow.xaml.cs

@@ -12,6 +12,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.Windows;
 using System.Windows.Threading;
+using MediaBrowser.ServerApplication.Native;
 
 namespace MediaBrowser.ServerApplication
 {
@@ -188,19 +189,19 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
         void cmdApiDocs_Click(object sender, EventArgs e)
         {
-            App.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" +
-                      _appHost.WebApplicationName + "/metadata");
+            BrowserLauncher.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" +
+                      _appHost.WebApplicationName + "/metadata", _logger);
         }
 
         void cmdSwaggerApiDocs_Click(object sender, EventArgs e)
         {
-            App.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" +
-                      _appHost.WebApplicationName + "/swagger-ui/index.html");
+            BrowserLauncher.OpenUrl("http://localhost:" + _configurationManager.Configuration.HttpServerPortNumber + "/" +
+                      _appHost.WebApplicationName + "/swagger-ui/index.html", _logger);
         }
 
         void cmdGithubWiki_Click(object sender, EventArgs e)
         {
-            App.OpenUrl("https://github.com/MediaBrowser/MediaBrowser/wiki");
+            BrowserLauncher.OpenUrl("https://github.com/MediaBrowser/MediaBrowser/wiki", _logger);
         }
 
         /// <summary>
@@ -254,7 +255,7 @@ namespace MediaBrowser.ServerApplication
         /// </summary>
         private void OpenDashboard(User loggedInUser)
         {
-            App.OpenDashboardPage("dashboard.html", loggedInUser, _configurationManager, _appHost);
+            BrowserLauncher.OpenDashboardPage("dashboard.html", loggedInUser, _configurationManager, _appHost, _logger);
         }
 
         /// <summary>
@@ -264,7 +265,7 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
         private void cmVisitCT_click(object sender, RoutedEventArgs e)
         {
-            App.OpenUrl("http://community.mediabrowser.tv/");
+            BrowserLauncher.OpenUrl("http://community.mediabrowser.tv/", _logger);
         }
 
         /// <summary>
@@ -275,7 +276,7 @@ namespace MediaBrowser.ServerApplication
         private void cmdBrowseLibrary_click(object sender, RoutedEventArgs e)
         {
             var user = _userManager.Users.FirstOrDefault(u => u.Configuration.IsAdministrator);
-            App.OpenDashboardPage("index.html", user, _configurationManager, _appHost);
+            BrowserLauncher.OpenDashboardPage("index.html", user, _configurationManager, _appHost, _logger);
         }
 
         /// <summary>

+ 12 - 13
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -130,10 +130,6 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.56\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
     </Reference>
-    <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
-    </Reference>
     <Reference Include="NLog, Version=2.0.1.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\NLog.2.0.1.2\lib\net45\NLog.dll</HintPath>
@@ -187,7 +183,6 @@
     <Reference Include="System.Net" />
     <Reference Include="System.Net.Http" />
     <Reference Include="System.Net.Http.WebRequest" />
-    <Reference Include="System.Runtime.Remoting" />
     <Reference Include="System.ServiceProcess" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@@ -209,12 +204,19 @@
       <SubType>Component</SubType>
     </Compile>
     <Compile Include="EntryPoints\StartupWizard.cs" />
-    <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
-    <Compile Include="Implementations\FFMpegDownloader.cs" />
+    <Compile Include="FFMpeg\FFMpegInfo.cs" />
+    <Compile Include="Native\Assemblies.cs" />
+    <Compile Include="Native\HttpMessageHandlerFactory.cs" />
+    <Compile Include="Native\NativeApp.cs" />
+    <Compile Include="Native\ServerAuthorization.cs" />
+    <Compile Include="Native\Autorun.cs" />
+    <Compile Include="Native\BrowserLauncher.cs" />
+    <Compile Include="FFMpeg\FFMpegDownloader.cs" />
     <Compile Include="MainStartup.cs" />
     <Compile Include="BackgroundServiceInstaller.cs">
       <SubType>Component</SubType>
     </Compile>
+    <Compile Include="Native\Sqlite.cs" />
     <Compile Include="Splash\SplashWindow.xaml.cs">
       <DependentUpon>SplashWindow.xaml</DependentUpon>
     </Compile>
@@ -242,7 +244,6 @@
       <SubType>Code</SubType>
     </Compile>
     <Compile Include="ApplicationHost.cs" />
-    <Compile Include="Implementations\DotNetZipClient.cs" />
     <Compile Include="LibraryExplorer.xaml.cs">
       <DependentUpon>LibraryExplorer.xaml</DependentUpon>
     </Compile>
@@ -278,14 +279,15 @@
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
     <None Include="app.manifest" />
-    <EmbeddedResource Include="Implementations\ffmpeg20130904.zip" />
+    <None Include="FFMpeg\ARIALUNI.7z" />
+    <None Include="FFMpeg\ffmpeg-20130904-git-f974289-win32-static.7z" />
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
       <LastGenOutput>Settings.Designer.cs</LastGenOutput>
     </None>
     <AppDesigner Include="Properties\" />
-    <EmbeddedResource Include="RegisterServer.bat" />
+    <EmbeddedResource Include="Native\RegisterServer.bat" />
   </ItemGroup>
   <ItemGroup>
     <None Include="App.config">
@@ -390,9 +392,6 @@
   <ItemGroup>
     <Resource Include="Resources\Images\mb3logo800.png" />
   </ItemGroup>
-  <ItemGroup>
-    <EmbeddedResource Include="Implementations\readme.txt" />
-  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>if $(ConfigurationName) == Release (

+ 25 - 0
MediaBrowser.ServerApplication/Native/Assemblies.cs

@@ -0,0 +1,25 @@
+using MediaBrowser.IsoMounter;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class Assemblies
+    /// </summary>
+    public static class Assemblies
+    {
+        /// <summary>
+        /// Gets the assemblies with parts.
+        /// </summary>
+        /// <returns>List{Assembly}.</returns>
+        public static List<Assembly> GetAssembliesWithParts()
+        {
+            var list = new List<Assembly>();
+
+            list.Add(typeof(PismoIsoManager).Assembly);
+
+            return list;
+        }
+    }
+}

+ 31 - 0
MediaBrowser.ServerApplication/Native/Autorun.cs

@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+    /// <summary>
+    /// Class Autorun
+    /// </summary>
+    public static class Autorun
+    {
+        /// <summary>
+        /// Configures the specified autorun.
+        /// </summary>
+        /// <param name="autorun">if set to <c>true</c> [autorun].</param>
+        public static void Configure(bool autorun)
+        {
+            var shortcutPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Media Browser 3", "Media Browser Server.lnk");
+
+            if (autorun)
+            {
+                //Copy our shortut into the startup folder for this user
+                File.Copy(shortcutPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(shortcutPath) ?? "MBstartup.lnk"), true);
+            }
+            else
+            {
+                //Remove our shortcut from the startup folder for this user
+                File.Delete(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileName(shortcutPath) ?? "MBstartup.lnk"));
+            }
+        }
+    }
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است