Ver código fonte

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

Eric Reed 12 anos atrás
pai
commit
00449ff66d
35 arquivos alterados com 477 adições e 341 exclusões
  1. 8 3
      MediaBrowser.Api/Images/ImageService.cs
  2. 3 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  3. 18 1
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  4. 64 1
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  5. 75 0
      MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs
  6. 1 1
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  7. 1 1
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  8. 1 3
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  9. 86 82
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  10. 6 2
      MediaBrowser.Controller/Entities/BaseItem.cs
  11. 1 1
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  12. 1 1
      MediaBrowser.Controller/Entities/Folder.cs
  13. 4 4
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  14. 4 3
      MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs
  15. 2 1
      MediaBrowser.Controller/Providers/ImagesByNameProvider.cs
  16. 2 2
      MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs
  17. 19 31
      MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs
  18. 48 109
      MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs
  19. 9 2
      MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs
  20. 2 1
      MediaBrowser.Controller/Resolvers/ResolverPriority.cs
  21. 1 1
      MediaBrowser.Controller/Session/ISessionManager.cs
  22. 3 3
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  23. 1 1
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  24. 6 6
      MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs
  25. 2 2
      MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
  26. 1 1
      MediaBrowser.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs
  27. 4 3
      MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs
  28. 23 14
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  29. 11 2
      MediaBrowser.ServerApplication/App.xaml.cs
  30. 3 54
      MediaBrowser.ServerApplication/ApplicationHost.cs
  31. 61 0
      MediaBrowser.ServerApplication/EntryPoints/UdpServerEntryPoint.cs
  32. 1 0
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  33. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  34. 1 1
      Nuget/MediaBrowser.Common.nuspec
  35. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 8 - 3
MediaBrowser.Api/Images/ImageService.cs

@@ -361,7 +361,7 @@ namespace MediaBrowser.Api.Images
 
                 index++;
             }
-            
+
             index = 0;
 
             foreach (var image in item.ScreenshotImagePaths)
@@ -422,7 +422,7 @@ namespace MediaBrowser.Api.Images
 
             return list;
         }
-        
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -765,7 +765,7 @@ namespace MediaBrowser.Api.Images
                 }
 
                 // Don't save locally if there's no parent (special feature, trailer, etc)
-                var saveLocally = (!(entity is Audio) && entity.Parent != null && !string.IsNullOrEmpty(entity.MetaLocation)) || entity is User;
+                var saveLocally = !(entity is Audio) && entity.Parent != null && !string.IsNullOrEmpty(entity.MetaLocation) || entity is User;
 
                 if (imageType != ImageType.Primary)
                 {
@@ -775,6 +775,11 @@ namespace MediaBrowser.Api.Images
                     }
                 }
 
+                if (entity.LocationType != LocationType.FileSystem)
+                {
+                    saveLocally = false;
+                }
+
                 var imagePath = _providerManager.GetSavePath(entity, filename + "." + extension, saveLocally);
 
                 // Save to file system

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

@@ -56,6 +56,8 @@
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Data" />
     <Reference Include="System.Drawing" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Net.Http.WebRequest" />
     <Reference Include="System.XML" />
   </ItemGroup>
   <ItemGroup>
@@ -82,6 +84,7 @@
     <Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
     <Compile Include="Playback\BaseStreamingService.cs" />
     <Compile Include="Playback\Progressive\ProgressiveStreamWriter.cs" />
+    <Compile Include="Playback\StaticRemoteStreamWriter.cs" />
     <Compile Include="Playback\StreamRequest.cs" />
     <Compile Include="Playback\StreamState.cs" />
     <Compile Include="Playback\Progressive\VideoService.cs" />

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

@@ -621,10 +621,27 @@ namespace MediaBrowser.Api.Playback
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         protected string GetUserAgentParam(BaseItem item)
+        {
+            var useragent = GetUserAgent(item);
+
+            if (!string.IsNullOrEmpty(useragent))
+            {
+                return "-user-agent \"" + useragent + "\"";
+            }
+
+            return string.Empty;
+        }
+
+        /// <summary>
+        /// Gets the user agent.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>System.String.</returns>
+        protected string GetUserAgent(BaseItem item)
         {
             if (item.Path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
             {
-                return "-user-agent \"QuickTime/7.6.2\"";
+                return "QuickTime/7.6.2";
             }
 
             return string.Empty;

+ 64 - 1
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,4 +1,7 @@
-using MediaBrowser.Api.Images;
+using System.Net;
+using System.Net.Cache;
+using System.Net.Http;
+using MediaBrowser.Api.Images;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.Net;
@@ -188,6 +191,11 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             var responseHeaders = new Dictionary<string, string>();
 
+            if (request.Static && state.Item.LocationType == LocationType.Remote)
+            {
+                return GetStaticRemoteStreamResult(state.Item, responseHeaders, isHeadRequest).Result;
+            }
+
             var outputPath = GetOutputFilePath(state);
             var outputPathExists = File.Exists(outputPath);
 
@@ -209,6 +217,61 @@ namespace MediaBrowser.Api.Playback.Progressive
             return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
         }
 
+        /// <summary>
+        /// Gets the static remote stream result.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <returns>Task{System.Object}.</returns>
+        private async Task<object> GetStaticRemoteStreamResult(BaseItem item, Dictionary<string, string> responseHeaders, bool isHeadRequest)
+        {
+            responseHeaders["Accept-Ranges"] = "none";
+
+            var httpClient = new HttpClient(new WebRequestHandler
+            {
+                CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache),
+                AutomaticDecompression = DecompressionMethods.None
+            });
+
+            using (var message = new HttpRequestMessage(HttpMethod.Get, item.Path))
+            {
+                var useragent = GetUserAgent(item);
+
+                if (!string.IsNullOrEmpty(useragent))
+                {
+                    message.Headers.Add("User-Agent", useragent);
+                }
+
+                var response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
+
+                response.EnsureSuccessStatusCode();
+
+                var contentType = response.Content.Headers.ContentType.MediaType;
+
+                // Headers only
+                if (isHeadRequest)
+                {
+                    response.Dispose();
+                    httpClient.Dispose();
+
+                    return ResultFactory.GetResult(null, contentType, responseHeaders);
+                }
+
+                var result = new StaticRemoteStreamWriter(response, httpClient);
+
+                result.Options["Content-Type"] = contentType;
+
+                // Add the response headers to the result object
+                foreach (var header in responseHeaders)
+                {
+                    result.Options[header.Key] = header.Value;
+                }
+
+                return result;
+            }
+        }
+
         /// <summary>
         /// Gets the album art response.
         /// </summary>

+ 75 - 0
MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs

@@ -0,0 +1,75 @@
+using ServiceStack.Service;
+using ServiceStack.ServiceHost;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Playback
+{
+    /// <summary>
+    /// Class StaticRemoteStreamWriter
+    /// </summary>
+    public class StaticRemoteStreamWriter : IStreamWriter, IHasOptions
+    {
+        /// <summary>
+        /// The _input stream
+        /// </summary>
+        private readonly HttpResponseMessage _msg;
+
+        private readonly HttpClient _client;
+
+        /// <summary>
+        /// The _options
+        /// </summary>
+        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StaticRemoteStreamWriter"/> class.
+        /// </summary>
+        public StaticRemoteStreamWriter(HttpResponseMessage msg, HttpClient client)
+        {
+            _msg = msg;
+            _client = client;
+        }
+
+        /// <summary>
+        /// Gets the options.
+        /// </summary>
+        /// <value>The options.</value>
+        public IDictionary<string, string> Options
+        {
+            get { return _options; }
+        }
+
+        /// <summary>
+        /// Writes to.
+        /// </summary>
+        /// <param name="responseStream">The response stream.</param>
+        public void WriteTo(Stream responseStream)
+        {
+            var task = WriteToAsync(responseStream);
+
+            Task.WaitAll(task);
+        }
+
+        /// <summary>
+        /// Writes to async.
+        /// </summary>
+        /// <param name="responseStream">The response stream.</param>
+        /// <returns>Task.</returns>
+        public async Task WriteToAsync(Stream responseStream)
+        {
+            using (_client)
+            {
+                using (_msg)
+                {
+                    using (var input = await _msg.Content.ReadAsStreamAsync().ConfigureAwait(false))
+                    {
+                        await input.CopyToAsync(responseStream).ConfigureAwait(false);
+                    }
+                }
+            }
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
             if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
             {
-                items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.Name, StringComparison.OrdinalIgnoreCase) < 1);
+                items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.Name, StringComparison.CurrentCultureIgnoreCase) < 1);
             }
 
             var filters = request.GetFilters().ToList();

+ 1 - 1
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -456,7 +456,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
             {
-                items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.OrdinalIgnoreCase) < 1);
+                items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
             }
 
             // Filter by Series Status

+ 1 - 3
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -180,8 +180,6 @@ namespace MediaBrowser.Common.Implementations
             await RegisterResources().ConfigureAwait(false);
 
             FindParts();
-
-            await RunStartupTasks().ConfigureAwait(false);
         }
 
         protected virtual void OnLoggerLoaded()
@@ -193,7 +191,7 @@ namespace MediaBrowser.Common.Implementations
         /// Runs the startup tasks.
         /// </summary>
         /// <returns>Task.</returns>
-        protected virtual Task RunStartupTasks()
+        public virtual Task RunStartupTasks()
         {
             return Task.Run(() =>
             {

+ 86 - 82
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -97,7 +97,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 };
 
                 client = new HttpClient(handler);
-                client.Timeout = TimeSpan.FromSeconds(30);
+                client.Timeout = TimeSpan.FromSeconds(15);
                 _httpClients.TryAdd(host, client);
             }
 
@@ -136,8 +136,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 {
                     var now = DateTime.UtcNow;
 
-                    var isCacheValid = (!cachedInfo.MustRevalidate && !string.IsNullOrEmpty(cachedInfo.Etag) && (now - cachedInfo.RequestDate).TotalDays < 7)
-                        || (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > now);
+                    var isCacheValid = cachedInfo.Expires.HasValue ? cachedInfo.Expires.Value > now :
+                        !cachedInfo.MustRevalidate && !string.IsNullOrEmpty(cachedInfo.Etag) && (now - cachedInfo.RequestDate).TotalDays < 5;
 
                     if (isCacheValid)
                     {
@@ -157,89 +157,90 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             options.CancellationToken.ThrowIfCancellationRequested();
 
-            var message = GetHttpRequestMessage(options);
-
-            if (options.EnableResponseCache && cachedInfo != null)
+            using (var message = GetHttpRequestMessage(options))
             {
-                if (!string.IsNullOrEmpty(cachedInfo.Etag))
+                if (options.EnableResponseCache && cachedInfo != null)
                 {
-                    message.Headers.Add("If-None-Match", cachedInfo.Etag);
+                    if (!string.IsNullOrEmpty(cachedInfo.Etag))
+                    {
+                        message.Headers.Add("If-None-Match", cachedInfo.Etag);
+                    }
+                    else if (cachedInfo.LastModified.HasValue)
+                    {
+                        message.Headers.IfModifiedSince = new DateTimeOffset(cachedInfo.LastModified.Value);
+                    }
                 }
-                else if (cachedInfo.LastModified.HasValue)
+
+                if (options.ResourcePool != null)
                 {
-                    message.Headers.IfModifiedSince = new DateTimeOffset(cachedInfo.LastModified.Value);
+                    await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
                 }
-            }
 
-            if (options.ResourcePool != null)
-            {
-                await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
-            }
-
-            _logger.Info("HttpClientManager.Get url: {0}", options.Url);
+                _logger.Info("HttpClientManager.Get url: {0}", options.Url);
 
-            try
-            {
-                options.CancellationToken.ThrowIfCancellationRequested();
+                try
+                {
+                    options.CancellationToken.ThrowIfCancellationRequested();
 
-                var response = await GetHttpClient(GetHostFromUrl(options.Url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false);
+                    var response = await GetHttpClient(GetHostFromUrl(options.Url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false);
 
-                if (options.EnableResponseCache)
-                {
-                    if (response.StatusCode != HttpStatusCode.NotModified)
+                    if (options.EnableResponseCache)
                     {
-                        EnsureSuccessStatusCode(response);
-                    }
+                        if (response.StatusCode != HttpStatusCode.NotModified)
+                        {
+                            EnsureSuccessStatusCode(response);
+                        }
 
-                    options.CancellationToken.ThrowIfCancellationRequested();
+                        options.CancellationToken.ThrowIfCancellationRequested();
 
-                    cachedInfo = UpdateInfoCache(cachedInfo, options.Url, cachedInfoPath, response);
+                        cachedInfo = UpdateInfoCache(cachedInfo, options.Url, cachedInfoPath, response);
 
-                    if (response.StatusCode == HttpStatusCode.NotModified)
-                    {
-                        _logger.Debug("Server indicates not modified for {0}. Returning cached result.", options.Url);
-                        
-                        return GetCachedResponse(cachedReponsePath);
-                    }
+                        if (response.StatusCode == HttpStatusCode.NotModified)
+                        {
+                            _logger.Debug("Server indicates not modified for {0}. Returning cached result.", options.Url);
 
-                    if (!string.IsNullOrEmpty(cachedInfo.Etag) || cachedInfo.LastModified.HasValue ||
-                        (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow))
+                            return GetCachedResponse(cachedReponsePath);
+                        }
+
+                        if (!string.IsNullOrEmpty(cachedInfo.Etag) || cachedInfo.LastModified.HasValue ||
+                            (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow))
+                        {
+                            await UpdateResponseCache(response, cachedReponsePath).ConfigureAwait(false);
+
+                            return GetCachedResponse(cachedReponsePath);
+                        }
+                    }
+                    else
                     {
-                        await UpdateResponseCache(response, cachedReponsePath).ConfigureAwait(false);
+                        EnsureSuccessStatusCode(response);
 
-                        return GetCachedResponse(cachedReponsePath);
+                        options.CancellationToken.ThrowIfCancellationRequested();
                     }
+
+                    return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
                 }
-                else
+                catch (OperationCanceledException ex)
                 {
-                    EnsureSuccessStatusCode(response);
-
-                    options.CancellationToken.ThrowIfCancellationRequested();
+                    throw GetCancellationException(options.Url, options.CancellationToken, ex);
                 }
+                catch (HttpRequestException ex)
+                {
+                    _logger.ErrorException("Error getting response from " + options.Url, ex);
 
-                return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
-            }
-            catch (OperationCanceledException ex)
-            {
-                throw GetCancellationException(options.Url, options.CancellationToken, ex);
-            }
-            catch (HttpRequestException ex)
-            {
-                _logger.ErrorException("Error getting response from " + options.Url, ex);
-
-                throw new HttpException(ex.Message, ex);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error getting response from " + options.Url, ex);
+                    throw new HttpException(ex.Message, ex);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error getting response from " + options.Url, ex);
 
-                throw;
-            }
-            finally
-            {
-                if (options.ResourcePool != null)
+                    throw;
+                }
+                finally
                 {
-                    options.ResourcePool.Release();
+                    if (options.ResourcePool != null)
+                    {
+                        options.ResourcePool.Release();
+                    }
                 }
             }
         }
@@ -469,39 +470,42 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
             {
                 options.CancellationToken.ThrowIfCancellationRequested();
 
-                using (var response = await GetHttpClient(GetHostFromUrl(options.Url)).SendAsync(GetHttpRequestMessage(options), HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false))
+                using (var message = GetHttpRequestMessage(options))
                 {
-                    EnsureSuccessStatusCode(response);
+                    using (var response = await GetHttpClient(GetHostFromUrl(options.Url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false))
+                    {
+                        EnsureSuccessStatusCode(response);
 
-                    options.CancellationToken.ThrowIfCancellationRequested();
+                        options.CancellationToken.ThrowIfCancellationRequested();
 
-                    var contentLength = GetContentLength(response);
+                        var contentLength = GetContentLength(response);
 
-                    if (!contentLength.HasValue)
-                    {
-                        // We're not able to track progress
-                        using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
+                        if (!contentLength.HasValue)
                         {
-                            using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                            // We're not able to track progress
+                            using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                             {
-                                await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
+                                using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                                {
+                                    await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
+                                }
                             }
                         }
-                    }
-                    else
-                    {
-                        using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, contentLength.Value))
+                        else
                         {
-                            using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                            using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, contentLength.Value))
                             {
-                                await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
+                                using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                                {
+                                    await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
+                                }
                             }
                         }
-                    }
 
-                    options.Progress.Report(100);
+                        options.Progress.Report(100);
 
-                    options.CancellationToken.ThrowIfCancellationRequested();
+                        options.CancellationToken.ThrowIfCancellationRequested();
+                    }
                 }
             }
             catch (Exception ex)

+ 6 - 2
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -646,6 +646,11 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The run time ticks.</value>
         public long? RunTimeTicks { get; set; }
 
+        /// <summary>
+        /// Gets or sets the original run time ticks.
+        /// </summary>
+        /// <value>The original run time ticks.</value>
+        public long? OriginalRunTimeTicks { get; set; }
         /// <summary>
         /// Gets or sets the aspect ratio.
         /// </summary>
@@ -900,7 +905,6 @@ namespace MediaBrowser.Controller.Entities
             if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged)
             {
                 cancellationToken.ThrowIfCancellationRequested();
-
                 await LibraryManager.UpdateItem(this, cancellationToken).ConfigureAwait(false);
             }
 
@@ -1501,7 +1505,7 @@ namespace MediaBrowser.Controller.Entities
             }
 
             // Refresh metadata
-            return RefreshMetadata(CancellationToken.None);
+            return RefreshMetadata(CancellationToken.None, forceSave: true);
         }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Entities
     /// Specialized Folder class that points to a subset of the physical folders in the system.
     /// It is created from the user-specific folders within the system root
     /// </summary>
-    public class CollectionFolder : Folder, ICollectionFolder, IByReferenceItem
+    public class CollectionFolder : Folder, ICollectionFolder
     {
         /// <summary>
         /// Gets a value indicating whether this instance is virtual folder.

+ 1 - 1
MediaBrowser.Controller/Entities/Folder.cs

@@ -760,7 +760,7 @@ namespace MediaBrowser.Controller.Entities
                     var child = currentTuple.Item1;
 
                     //refresh it
-                    await child.RefreshMetadata(cancellationToken, resetResolveArgs: child.IsFolder).ConfigureAwait(false);
+                    await child.RefreshMetadata(cancellationToken, resetResolveArgs: child.IsFolder, forceSave: currentTuple.Item2).ConfigureAwait(false);
 
                     // Refresh children if a folder and the item changed or recursive is set to true
                     var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value));

+ 4 - 4
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -325,7 +325,7 @@ namespace MediaBrowser.Controller.Providers
 
                         if (!string.IsNullOrWhiteSpace(val))
                         {
-                            item.AddTrailerUrl(val);
+                            //item.AddTrailerUrl(val);
                         }
                         break;
                     }
@@ -336,10 +336,10 @@ namespace MediaBrowser.Controller.Providers
 
                         if (!string.IsNullOrWhiteSpace(val))
                         {
-                            int ProductionYear;
-                            if (int.TryParse(val, out ProductionYear) && ProductionYear > 1850)
+                            int productionYear;
+                            if (int.TryParse(val, out productionYear) && productionYear > 1850)
                             {
-                                item.ProductionYear = ProductionYear;
+                                item.ProductionYear = productionYear;
                             }
                         }
 

+ 4 - 3
MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs

@@ -91,12 +91,13 @@ namespace MediaBrowser.Controller.Providers
             }
 
             // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
-            var deletedKeys = item.Images.Keys.Where(image =>
+            var deletedKeys = item.Images.ToList().Where(image =>
             {
-                var path = item.Images[image];
+                var path = image.Value;
 
                 return IsInMetaLocation(item, path) && item.ResolveArgs.GetMetaFileByPath(path) == null;
-            }).ToList();
+
+            }).Select(i => i.Key).ToList();
 
             // Now remove them from the dictionary
             foreach (var key in deletedKeys)

+ 2 - 1
MediaBrowser.Controller/Providers/ImagesByNameProvider.cs

@@ -126,7 +126,8 @@ namespace MediaBrowser.Controller.Providers
             }
 
             data.Data = ConfigurationManager.ApplicationPaths.ItemsByNamePath.GetMD5();
-            
+            SetLastRefreshed(item, DateTime.UtcNow);
+     
             return result;
         }
 

+ 2 - 2
MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs

@@ -973,8 +973,8 @@ namespace MediaBrowser.Controller.Providers.Movies
                     boxset.OfficialRating = firstChild != null ? firstChild.OfficialRating : null;
                 }
 
-                //if (movie.RunTimeTicks == null && movieData.runtime > 0)
-                //    movie.RunTimeTicks = TimeSpan.FromMinutes(movieData.runtime).Ticks;
+                if (movieData.runtime > 0)
+                    movie.OriginalRunTimeTicks = TimeSpan.FromMinutes(movieData.runtime).Ticks;
 
                 //studios
                 if (movieData.production_companies != null)

+ 19 - 31
MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs

@@ -46,11 +46,11 @@ namespace MediaBrowser.Controller.Providers.TV
         /// <summary>
         /// The episode query
         /// </summary>
-        private const string episodeQuery = "http://www.thetvdb.com/api/{0}/series/{1}/default/{2}/{3}/{4}.xml";
+        private const string EpisodeQuery = "http://www.thetvdb.com/api/{0}/series/{1}/default/{2}/{3}/{4}.xml";
         /// <summary>
         /// The abs episode query
         /// </summary>
-        private const string absEpisodeQuery = "http://www.thetvdb.com/api/{0}/series/{1}/absolute/{2}/{3}.xml";
+        private const string AbsEpisodeQuery = "http://www.thetvdb.com/api/{0}/series/{1}/absolute/{2}/{3}.xml";
 
         /// <summary>
         /// Supportses the specified item.
@@ -179,25 +179,19 @@ namespace MediaBrowser.Controller.Providers.TV
                     seasonNumber = "0"; // Specials
                 }
 
-                var url = string.Format(episodeQuery, TVUtils.TvdbApiKey, seriesId, seasonNumber, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
+                var url = string.Format(EpisodeQuery, TVUtils.TvdbApiKey, seriesId, seasonNumber, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
                 var doc = new XmlDocument();
 
-                try
+                using (var result = await HttpClient.Get(new HttpRequestOptions
                 {
-                    using (var result = await HttpClient.Get(new HttpRequestOptions
-                    {
-                        Url = url,
-                        ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
-                        CancellationToken = cancellationToken,
-                        EnableResponseCache = true
+                    Url = url,
+                    ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
+                    CancellationToken = cancellationToken,
+                    EnableResponseCache = true
 
-                    }).ConfigureAwait(false))
-                    {
-                        doc.Load(result);
-                    }
-                }
-                catch (HttpException)
+                }).ConfigureAwait(false))
                 {
+                    doc.Load(result);
                 }
 
                 //episode does not exist under this season, try absolute numbering.
@@ -205,25 +199,19 @@ namespace MediaBrowser.Controller.Providers.TV
                 //this is basicly just for anime.
                 if (!doc.HasChildNodes && Int32.Parse(seasonNumber) == 1)
                 {
-                    url = string.Format(absEpisodeQuery, TVUtils.TvdbApiKey, seriesId, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
+                    url = string.Format(AbsEpisodeQuery, TVUtils.TvdbApiKey, seriesId, episodeNumber, ConfigurationManager.Configuration.PreferredMetadataLanguage);
 
-                    try
+                    using (var result = await HttpClient.Get(new HttpRequestOptions
                     {
-                        using (var result = await HttpClient.Get(new HttpRequestOptions
-                        {
-                            Url = url,
-                            ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
-                            CancellationToken = cancellationToken,
-                            EnableResponseCache = true
+                        Url = url,
+                        ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool,
+                        CancellationToken = cancellationToken,
+                        EnableResponseCache = true
 
-                        }).ConfigureAwait(false))
-                        {
-                            if (result != null) doc.Load(result);
-                            usingAbsoluteData = true;
-                        }
-                    }
-                    catch (HttpException)
+                    }).ConfigureAwait(false))
                     {
+                        if (result != null) doc.Load(result);
+                        usingAbsoluteData = true;
                     }
                 }
 

+ 48 - 109
MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs

@@ -1,5 +1,4 @@
-using System.Globalization;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -11,6 +10,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Net;
 using System.Text;
@@ -142,8 +142,7 @@ namespace MediaBrowser.Controller.Providers.TV
 
             if (item.DontFetchMeta) return false;
 
-            return !HasLocalMeta(item) && (ConfigurationManager.Configuration.MetadataRefreshDays != -1 &&
-                                       DateTime.UtcNow.Subtract(downloadDate).TotalDays > ConfigurationManager.Configuration.MetadataRefreshDays);
+            return !HasLocalMeta(item) && base.NeedsRefreshInternal(item, providerInfo);
         }
 
         /// <summary>
@@ -164,16 +163,17 @@ namespace MediaBrowser.Controller.Providers.TV
                 var seriesId = Path.GetFileName(path).GetAttributeValue("tvdbid") ?? await GetSeriesId(series, cancellationToken);
 
                 cancellationToken.ThrowIfCancellationRequested();
-                
+
+                var status = ProviderRefreshStatus.Success;
+
                 if (!string.IsNullOrEmpty(seriesId))
                 {
                     series.SetProviderId(MetadataProviders.Tvdb, seriesId);
-                    if (!HasCompleteMetadata(series))
-                    {
-                        await FetchSeriesData(series, seriesId, cancellationToken).ConfigureAwait(false);
-                    }
+
+                    status = await FetchSeriesData(series, seriesId, cancellationToken).ConfigureAwait(false);
                 }
-                SetLastRefreshed(item, DateTime.UtcNow);
+
+                SetLastRefreshed(item, DateTime.UtcNow, status);
                 return true;
             }
             Logger.Info("Series provider not fetching because local meta exists or requested to ignore: " + item.Name);
@@ -188,11 +188,9 @@ namespace MediaBrowser.Controller.Providers.TV
         /// <param name="seriesId">The series id.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.Boolean}.</returns>
-        private async Task<bool> FetchSeriesData(Series series, string seriesId, CancellationToken cancellationToken)
+        private async Task<ProviderRefreshStatus> FetchSeriesData(Series series, string seriesId, CancellationToken cancellationToken)
         {
-            var success = false;
-
-            var name = series.Name;
+            var status = ProviderRefreshStatus.Success;
 
             if (!string.IsNullOrEmpty(seriesId))
             {
@@ -200,22 +198,16 @@ namespace MediaBrowser.Controller.Providers.TV
                 string url = string.Format(seriesGet, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage);
                 var doc = new XmlDocument();
 
-                try
+                using (var xml = await HttpClient.Get(new HttpRequestOptions
                 {
-                    using (var xml = await HttpClient.Get(new HttpRequestOptions
-                    {
-                        Url = url,
-                        ResourcePool = TvDbResourcePool,
-                        CancellationToken = cancellationToken,
-                        EnableResponseCache = true
+                    Url = url,
+                    ResourcePool = TvDbResourcePool,
+                    CancellationToken = cancellationToken,
+                    EnableResponseCache = true
 
-                    }).ConfigureAwait(false))
-                    {
-                        doc.Load(xml);
-                    }
-                }
-                catch (HttpException)
+                }).ConfigureAwait(false))
                 {
+                    doc.Load(xml);
                 }
 
                 if (doc.HasChildNodes)
@@ -224,8 +216,6 @@ namespace MediaBrowser.Controller.Providers.TV
                     var actorTask = FetchActors(series, seriesId, doc, cancellationToken);
                     var imageTask = FetchImages(series, seriesId, cancellationToken);
 
-                    success = true;
-
                     series.Name = doc.SafeGetString("//SeriesName");
                     series.Overview = doc.SafeGetString("//Overview");
                     series.CommunityRating = doc.SafeGetSingle("//Rating", 0, 10);
@@ -268,8 +258,15 @@ namespace MediaBrowser.Controller.Providers.TV
                         }
                     }
 
-                    //wait for other tasks
-                    await Task.WhenAll(actorTask, imageTask).ConfigureAwait(false);
+                    try
+                    {
+                        //wait for other tasks
+                        await Task.WhenAll(actorTask, imageTask).ConfigureAwait(false);
+                    }
+                    catch (HttpException)
+                    {
+                        status = ProviderRefreshStatus.CompletedWithErrors;
+                    }
 
                     if (ConfigurationManager.Configuration.SaveLocalMeta)
                     {
@@ -281,9 +278,7 @@ namespace MediaBrowser.Controller.Providers.TV
                 }
             }
 
-
-
-            return success;
+            return status;
         }
 
         /// <summary>
@@ -299,22 +294,16 @@ namespace MediaBrowser.Controller.Providers.TV
             string urlActors = string.Format(getActors, TVUtils.TvdbApiKey, seriesId);
             var docActors = new XmlDocument();
 
-            try
+            using (var actors = await HttpClient.Get(new HttpRequestOptions
             {
-                using (var actors = await HttpClient.Get(new HttpRequestOptions
-                {
-                    Url = urlActors,
-                    ResourcePool = TvDbResourcePool,
-                    CancellationToken = cancellationToken,
-                    EnableResponseCache = true
+                Url = urlActors,
+                ResourcePool = TvDbResourcePool,
+                CancellationToken = cancellationToken,
+                EnableResponseCache = true
 
-                }).ConfigureAwait(false))
-                {
-                    docActors.Load(actors);
-                }
-            }
-            catch (HttpException)
+            }).ConfigureAwait(false))
             {
+                docActors.Load(actors);
             }
 
             if (docActors.HasChildNodes)
@@ -380,22 +369,16 @@ namespace MediaBrowser.Controller.Providers.TV
                 string url = string.Format("http://www.thetvdb.com/api/" + TVUtils.TvdbApiKey + "/series/{0}/banners.xml", seriesId);
                 var images = new XmlDocument();
 
-                try
+                using (var imgs = await HttpClient.Get(new HttpRequestOptions
                 {
-                    using (var imgs = await HttpClient.Get(new HttpRequestOptions
-                    {
-                        Url = url,
-                        ResourcePool = TvDbResourcePool,
-                        CancellationToken = cancellationToken,
-                        EnableResponseCache = true
+                    Url = url,
+                    ResourcePool = TvDbResourcePool,
+                    CancellationToken = cancellationToken,
+                    EnableResponseCache = true
 
-                    }).ConfigureAwait(false))
-                    {
-                        images.Load(imgs);
-                    }
-                }
-                catch (HttpException)
+                }).ConfigureAwait(false))
                 {
+                    images.Load(imgs);
                 }
 
                 if (images.HasChildNodes)
@@ -408,17 +391,7 @@ namespace MediaBrowser.Controller.Providers.TV
                             n = n.SelectSingleNode("./BannerPath");
                             if (n != null)
                             {
-                                try
-                                {
-                                    series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false);
-                                }
-                                catch (HttpException)
-                                {
-                                }
-                                catch (IOException)
-                                {
-
-                                }
+                                series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false);
                             }
                         }
                     }
@@ -431,19 +404,9 @@ namespace MediaBrowser.Controller.Providers.TV
                             n = n.SelectSingleNode("./BannerPath");
                             if (n != null)
                             {
-                                try
-                                {
-                                    var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken);
-
-                                    series.SetImage(ImageType.Banner, bannerImagePath);
-                                }
-                                catch (HttpException)
-                                {
-                                }
-                                catch (IOException)
-                                {
+                                var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken);
 
-                                }
+                                series.SetImage(ImageType.Banner, bannerImagePath);
                             }
                         }
                     }
@@ -460,17 +423,7 @@ namespace MediaBrowser.Controller.Providers.TV
                                 var bdName = "backdrop" + (bdNo > 0 ? bdNo.ToString(UsCulture) : "");
                                 if (ConfigurationManager.Configuration.RefreshItemImages || !series.HasLocalImage(bdName))
                                 {
-                                    try
-                                    {
-                                        series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false));
-                                    }
-                                    catch (HttpException)
-                                    {
-                                    }
-                                    catch (IOException)
-                                    {
-                                        
-                                    }
+                                    series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false));
                                 }
                                 bdNo++;
                                 if (bdNo >= ConfigurationManager.Configuration.MaxBackdrops) break;
@@ -480,18 +433,6 @@ namespace MediaBrowser.Controller.Providers.TV
             }
         }
 
-        /// <summary>
-        /// Determines whether [has complete metadata] [the specified series].
-        /// </summary>
-        /// <param name="series">The series.</param>
-        /// <returns><c>true</c> if [has complete metadata] [the specified series]; otherwise, <c>false</c>.</returns>
-        private bool HasCompleteMetadata(Series series)
-        {
-            return (series.HasImage(ImageType.Banner)) && (series.CommunityRating != null)
-                                && (series.Overview != null) && (series.Name != null) && (series.People != null)
-                                && (series.Genres != null) && (series.OfficialRating != null);
-        }
-
         /// <summary>
         /// Determines whether [has local meta] [the specified item].
         /// </summary>
@@ -499,9 +440,7 @@ namespace MediaBrowser.Controller.Providers.TV
         /// <returns><c>true</c> if [has local meta] [the specified item]; otherwise, <c>false</c>.</returns>
         private bool HasLocalMeta(BaseItem item)
         {
-            //need at least the xml and folder.jpg/png
-            return item.ResolveArgs.ContainsMetaFileByName(LOCAL_META_FILE_NAME) && (item.ResolveArgs.ContainsMetaFileByName("folder.jpg") ||
-                item.ResolveArgs.ContainsMetaFileByName("folder.png"));
+            return item.ResolveArgs.ContainsMetaFileByName(LOCAL_META_FILE_NAME);
         }
 
         /// <summary>

+ 9 - 2
MediaBrowser.Controller/Providers/TV/SeriesXmlParser.cs

@@ -63,8 +63,15 @@ namespace MediaBrowser.Controller.Providers.TV
                     }
 
                 case "Airs_Time":
-                    item.AirTime = reader.ReadElementContentAsString();
-                    break;
+                    {
+                        var val = reader.ReadElementContentAsString();
+
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.AirTime = val;
+                        }
+                        break;
+                    }
 
                 case "SeriesName":
                     item.Name = reader.ReadElementContentAsString();

+ 2 - 1
MediaBrowser.Controller/Resolvers/ResolverPriority.cs

@@ -18,9 +18,10 @@ namespace MediaBrowser.Controller.Resolvers
         /// The third
         /// </summary>
         Third = 3,
+        Fourth = 4,
         /// <summary>
         /// The last
         /// </summary>
-        Last = 4
+        Last = 5
     }
 }

+ 1 - 1
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Session
         /// <param name="deviceId">The device id.</param>
         /// <param name="deviceName">Name of the device.</param>
         /// <exception cref="System.ArgumentNullException"></exception>
-        void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName);
+        Task OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName);
 
         /// <summary>
         /// Used to report playback progress for an item

+ 3 - 3
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -269,16 +269,16 @@ namespace MediaBrowser.Server.Implementations.Library
             // Need to use DistinctBy Id because there could be multiple instances with the same id
             // due to sharing the default library
             var userRootFolders = _userManager.Users.Select(i => i.RootFolder)
-                .DistinctBy(i => i.Id)
+                .Distinct()
                 .ToList();
 
             items.AddRange(userRootFolders);
 
             // Get all user collection folders
+            // Skip BasePluginFolders because we already got them from RootFolder.RecursiveChildren
             var userFolders =
-                _userManager.Users.SelectMany(i => i.RootFolder.Children)
+                userRootFolders.SelectMany(i => i.Children)
                             .Where(i => !(i is BasePluginFolder))
-                            .DistinctBy(i => i.Id)
                             .ToList();
 
             items.AddRange(userFolders);

+ 1 - 1
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -408,7 +408,7 @@ namespace MediaBrowser.Server.Implementations.Providers
         {
             return (saveLocally && item.MetaLocation != null) ?
                 Path.Combine(item.MetaLocation, targetFileName) :
-                _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Path.ToLower(), targetFileName);
+                _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id.ToString(), targetFileName);
         }
 
         /// <summary>

+ 6 - 6
MediaBrowser.Server.Implementations/ScheduledTasks/AudioImagesTask.cs

@@ -46,14 +46,14 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
         private readonly List<Audio> _newlyAddedItems = new List<Audio>();
 
-        private const int NewItemDelay = 300000;
+        private const int NewItemDelay = 60000;
 
         /// <summary>
         /// The current new item timer
         /// </summary>
         /// <value>The new item timer.</value>
         private Timer NewItemTimer { get; set; }
-        
+
         /// <summary>
         /// Initializes a new instance of the <see cref="AudioImagesTask" /> class.
         /// </summary>
@@ -118,7 +118,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
             foreach (var item in newSongs
                 .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video))
-                .Take(20))
+                .Take(10))
             {
                 try
                 {
@@ -130,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 }
             }
         }
-        
+
         /// <summary>
         /// Gets the name of the task
         /// </summary>
@@ -216,7 +216,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
             var filename = item.Album ?? string.Empty;
 
-            filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString() + album.DateModified.Ticks;
+            filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks;
 
             var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg");
 
@@ -232,7 +232,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 {
                     try
                     {
-                        await _mediaEncoder.ExtractImage(new[] {item.Path}, InputType.AudioFile, null, path, cancellationToken).ConfigureAwait(false);
+                        await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.AudioFile, null, path, cancellationToken).ConfigureAwait(false);
                     }
                     finally
                     {

+ 2 - 2
MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
         private readonly List<Video> _newlyAddedItems = new List<Video>();
 
-        private const int NewItemDelay = 300000;
+        private const int NewItemDelay = 60000;
 
         /// <summary>
         /// The current new item timer
@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
             foreach (var item in newItems
                 .Where(i => i.LocationType == LocationType.FileSystem && string.IsNullOrEmpty(i.PrimaryImagePath) && i.MediaStreams.Any(m => m.Type == MediaStreamType.Video))
-                .Take(5))
+                .Take(1))
             {
                 try
                 {

+ 1 - 1
MediaBrowser.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs

@@ -1,11 +1,11 @@
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Tasks;
+using MediaBrowser.Server.Implementations.Library;
 using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Server.Implementations.Library;
 
 namespace MediaBrowser.Server.Implementations.ScheduledTasks
 {

+ 4 - 3
MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs

@@ -55,7 +55,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
         private readonly List<BaseItem> _newlyAddedItems = new List<BaseItem>();
 
-        private const int NewItemDelay = 300000;
+        private const int NewItemDelay = 60000;
 
         /// <summary>
         /// The current new item timer
@@ -124,7 +124,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 NewItemTimer = null;
             }
 
-            foreach (var item in GetItemsForExtraction(newItems.Take(5)))
+            foreach (var item in GetItemsForExtraction(newItems.Take(3)))
             {
                 try
                 {
@@ -215,7 +215,8 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
             var videos = allItems.OfType<Video>().ToList();
 
-            var items = videos;
+            var items = videos.ToList();
+
             items.AddRange(localTrailers);
 
             items.AddRange(themeVideos);

+ 23 - 14
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -200,7 +200,7 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <param name="deviceName">Name of the device.</param>
         /// <exception cref="System.ArgumentNullException">
         /// </exception>
-        public void OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName)
+        public async Task OnPlaybackStart(User user, BaseItem item, string clientType, string deviceId, string deviceName)
         {
             if (user == null)
             {
@@ -213,6 +213,15 @@ namespace MediaBrowser.Server.Implementations.Session
 
             UpdateNowPlayingItemId(user, clientType, deviceId, deviceName, item, false);
 
+            var key = item.GetUserDataKey();
+
+            var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
+
+            data.PlayCount++;
+            data.LastPlayedDate = DateTime.UtcNow;
+
+            await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
+            
             // Nothing to save here
             // Fire events to inform plugins
             EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
@@ -254,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.Session
             {
                 var data = await _userDataRepository.GetUserData(user.Id, key).ConfigureAwait(false);
 
-                UpdatePlayState(item, data, positionTicks.Value, false);
+                UpdatePlayState(item, data, positionTicks.Value);
                 await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
             }
 
@@ -297,7 +306,7 @@ namespace MediaBrowser.Server.Implementations.Session
 
             if (positionTicks.HasValue)
             {
-                UpdatePlayState(item, data, positionTicks.Value, true);
+                UpdatePlayState(item, data, positionTicks.Value);
             }
             else
             {
@@ -322,11 +331,12 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <param name="item">The item</param>
         /// <param name="data">User data for the item</param>
         /// <param name="positionTicks">The current playback position</param>
-        /// <param name="incrementPlayCount">Whether or not to increment playcount</param>
-        private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks, bool incrementPlayCount)
+        private void UpdatePlayState(BaseItem item, UserItemData data, long positionTicks)
         {
+            var hasRuntime = item.RunTimeTicks.HasValue && item.RunTimeTicks > 0;
+
             // If a position has been reported, and if we know the duration
-            if (positionTicks > 0 && item.RunTimeTicks.HasValue && item.RunTimeTicks > 0)
+            if (positionTicks > 0 && hasRuntime)
             {
                 var pctIn = Decimal.Divide(positionTicks, item.RunTimeTicks.Value) * 100;
 
@@ -334,7 +344,6 @@ namespace MediaBrowser.Server.Implementations.Session
                 if (pctIn < _configurationManager.Configuration.MinResumePct)
                 {
                     positionTicks = 0;
-                    incrementPlayCount = false;
                 }
 
                 // If we're at the end, assume completed
@@ -356,19 +365,19 @@ namespace MediaBrowser.Server.Implementations.Session
                     }
                 }
             }
+            else if (!hasRuntime)
+            {
+                // If we don't know the runtime we'll just have to assume it was fully played
+                data.Played = true;
+                positionTicks = 0;
+            }
 
             if (item is Audio)
             {
-                data.PlaybackPositionTicks = 0;
+                positionTicks = 0;
             }
 
             data.PlaybackPositionTicks = positionTicks;
-
-            if (incrementPlayCount)
-            {
-                data.PlayCount++;
-                data.LastPlayedDate = DateTime.UtcNow;
-            }
         }
     }
 }

+ 11 - 2
MediaBrowser.ServerApplication/App.xaml.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Server.Implementations;
+using MediaBrowser.ServerApplication.Splash;
 using Microsoft.Win32;
 using System;
 using System.Diagnostics;
@@ -164,11 +165,19 @@ namespace MediaBrowser.ServerApplication
 
                 Logger = CompositionRoot.LogManager.GetLogger("App");
 
+                var splash = new SplashWindow(CompositionRoot.ApplicationVersion);
+
+                splash.Show();
+                
                 await CompositionRoot.Init();
 
-                var win = new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer, CompositionRoot.DisplayPreferencesManager);
+                splash.Hide();
+
+                var task = CompositionRoot.RunStartupTasks();
+
+                new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer, CompositionRoot.DisplayPreferencesManager).Show();
 
-                win.Show();
+                await task.ConfigureAwait(false);
             }
             catch (Exception ex)
             {

+ 3 - 54
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -44,7 +44,6 @@ using MediaBrowser.Server.Implementations.Udp;
 using MediaBrowser.Server.Implementations.Updates;
 using MediaBrowser.Server.Implementations.WebSocket;
 using MediaBrowser.ServerApplication.Implementations;
-using MediaBrowser.ServerApplication.Splash;
 using MediaBrowser.WebDashboard.Api;
 using System;
 using System.Collections.Generic;
@@ -63,7 +62,7 @@ namespace MediaBrowser.ServerApplication
     /// </summary>
     public class ApplicationHost : BaseApplicationHost<ServerApplicationPaths>, IServerApplicationHost
     {
-        private const int UdpServerPort = 7359;
+        internal const int UdpServerPort = 7359;
 
         /// <summary>
         /// Gets the server kernel.
@@ -139,11 +138,6 @@ namespace MediaBrowser.ServerApplication
         /// <value>The HTTP server.</value>
         private IHttpServer HttpServer { get; set; }
 
-        /// <summary>
-        /// Gets or sets the UDP server.
-        /// </summary>
-        /// <value>The UDP server.</value>
-        private UdpServer UdpServer { get; set; }
         /// <summary>
         /// Gets or sets the display preferences manager.
         /// </summary>
@@ -175,26 +169,11 @@ namespace MediaBrowser.ServerApplication
 
         private Task<IHttpServer> _httpServerCreationTask;
 
-        /// <summary>
-        /// Inits this instance.
-        /// </summary>
-        /// <returns>Task.</returns>
-        public override async Task Init()
-        {
-            var win = new SplashWindow(ApplicationVersion);
-
-            win.Show();
-
-            await base.Init();
-
-            win.Hide();
-        }
-
         /// <summary>
         /// Runs the startup tasks.
         /// </summary>
         /// <returns>Task.</returns>
-        protected override async Task RunStartupTasks()
+        public override async Task RunStartupTasks()
         {
             await base.RunStartupTasks().ConfigureAwait(false);
 
@@ -390,21 +369,8 @@ namespace MediaBrowser.ServerApplication
 
                 () => LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>()),
 
-                () => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().ToArray()),
+                () => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().ToArray())
 
-                () =>
-                {
-                    UdpServer = new UdpServer(Logger, NetworkManager, ServerConfigurationManager);
-
-                    try
-                    {
-                        UdpServer.Start(UdpServerPort);
-                    }
-                    catch (SocketException ex)
-                    {
-                        Logger.ErrorException("Failed to start UDP Server", ex);
-                    }
-                }
                 );
         }
 
@@ -471,23 +437,6 @@ namespace MediaBrowser.ServerApplication
             get { return ConfigurationManager.CommonConfiguration.EnableAutoUpdate; }
         }
 
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected override void Dispose(bool dispose)
-        {
-            if (dispose)
-            {
-                if (UdpServer != null)
-                {
-                    UdpServer.Dispose();
-                }
-            }
-
-            base.Dispose(dispose);
-        }
-
         /// <summary>
         /// Checks for update.
         /// </summary>

+ 61 - 0
MediaBrowser.ServerApplication/EntryPoints/UdpServerEntryPoint.cs

@@ -0,0 +1,61 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.Udp;
+using System.Net.Sockets;
+
+namespace MediaBrowser.ServerApplication.EntryPoints
+{
+    public class UdpServerEntryPoint : IServerEntryPoint
+    {
+        /// <summary>
+        /// Gets or sets the UDP server.
+        /// </summary>
+        /// <value>The UDP server.</value>
+        private UdpServer UdpServer { get; set; }
+
+        private readonly ILogger _logger;
+        private readonly INetworkManager _networkManager;
+        private readonly IServerConfigurationManager _serverConfigurationManager;
+
+        public UdpServerEntryPoint(ILogger logger, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
+        {
+            _logger = logger;
+            _networkManager = networkManager;
+            _serverConfigurationManager = serverConfigurationManager;
+        }
+        
+        public void Run()
+        {
+            var udpServer = new UdpServer(_logger, _networkManager, _serverConfigurationManager);
+
+            try
+            {
+                udpServer.Start(ApplicationHost.UdpServerPort);
+
+                UdpServer = udpServer;
+            }
+            catch (SocketException ex)
+            {
+                _logger.ErrorException("Failed to start UDP Server", ex);
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+
+        protected virtual void Dispose(bool dispose)
+        {
+            if (dispose)
+            {
+                if (UdpServer != null)
+                {
+                    UdpServer.Dispose();
+                }
+            }
+        }
+    }
+}

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

@@ -196,6 +196,7 @@
     <Compile Include="EntryPoints\LoadRegistrations.cs" />
     <Compile Include="EntryPoints\RefreshUsersMetadata.cs" />
     <Compile Include="EntryPoints\StartupWizard.cs" />
+    <Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
     <Compile Include="EntryPoints\WebSocketEvents.cs" />
     <Compile Include="Splash\SplashWindow.xaml.cs">
       <DependentUpon>SplashWindow.xaml</DependentUpon>

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.103</version>
+        <version>3.0.104</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.103" />
+            <dependency id="MediaBrowser.Common" version="3.0.104" />
             <dependency id="NLog" version="2.0.1.2" />
             <dependency id="ServiceStack.Text" version="3.9.45" />
             <dependency id="SimpleInjector" version="2.2.1" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.103</version>
+        <version>3.0.104</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.103</version>
+        <version>3.0.104</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.103" />
+            <dependency id="MediaBrowser.Common" version="3.0.104" />
         </dependencies>
     </metadata>
     <files>