ソースを参照

disable chunked encoding for images

Luke Pulverenti 11 年 前
コミット
06118307dd

+ 49 - 11
MediaBrowser.Api/Images/ImageService.cs

@@ -564,25 +564,59 @@ namespace MediaBrowser.Api.Images
                 cacheDuration = TimeSpan.FromDays(365);
             }
 
-            // Avoid implicitly captured closure
-            var currentItem = item;
-            var currentRequest = request;
-
             var responseHeaders = new Dictionary<string, string>
             {
                 {"transferMode.dlna.org", "Interactive"},
                 {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
             };
 
-            return ToCachedResult(cacheGuid, originalFileImageDateModified, cacheDuration, () => new ImageWriter
+            return GetImageResult(item, 
+                request, 
+                imageInfo, 
+                supportedImageEnhancers, 
+                contentType, 
+                cacheDuration,
+                responseHeaders)
+                .Result;
+        }
+
+        private async Task<object> GetImageResult(IHasImages item, 
+            ImageRequest request,
+            ItemImageInfo image,
+            List<IImageEnhancer> enhancers,
+            string contentType,
+            TimeSpan? cacheDuration,
+            IDictionary<string,string> headers)
+        {
+            var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
+
+            if (request.CropWhitespace.HasValue)
+            {
+                cropwhitespace = request.CropWhitespace.Value;
+            }
+
+            var options = new ImageProcessingOptions
             {
-                Item = currentItem,
-                Request = currentRequest,
-                Enhancers = supportedImageEnhancers,
-                Image = imageInfo,
-                ImageProcessor = _imageProcessor
+                CropWhiteSpace = cropwhitespace,
+                Enhancers = enhancers,
+                Height = request.Height,
+                ImageIndex = request.Index ?? 0,
+                Image = image,
+                Item = item,
+                MaxHeight = request.MaxHeight,
+                MaxWidth = request.MaxWidth,
+                Quality = request.Quality,
+                Width = request.Width,
+                OutputFormat = request.Format,
+                AddPlayedIndicator = request.AddPlayedIndicator,
+                PercentPlayed = request.PercentPlayed,
+                UnplayedCount = request.UnplayedCount,
+                BackgroundColor = request.BackgroundColor
+            };
 
-            }, contentType, responseHeaders);
+            var file = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
+
+            return ResultFactory.GetStaticFileResult(Request, file, contentType, cacheDuration, FileShare.Read, headers);
         }
 
         private string GetMimeType(ImageOutputFormat format, string path)
@@ -603,6 +637,10 @@ namespace MediaBrowser.Api.Images
             {
                 return Common.Net.MimeTypes.GetMimeType("i.png");
             }
+            if (format == ImageOutputFormat.Webp)
+            {
+                return Common.Net.MimeTypes.GetMimeType("i.webp");
+            }
 
             return Common.Net.MimeTypes.GetMimeType(path);
         }

+ 0 - 96
MediaBrowser.Api/Images/ImageWriter.cs

@@ -1,96 +0,0 @@
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using ServiceStack.Web;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Api.Images
-{
-    /// <summary>
-    /// Class ImageWriter
-    /// </summary>
-    public class ImageWriter : IStreamWriter, IHasOptions
-    {
-        public List<IImageEnhancer> Enhancers;
-
-        /// <summary>
-        /// Gets or sets the request.
-        /// </summary>
-        /// <value>The request.</value>
-        public ImageRequest Request { get; set; }
-        /// <summary>
-        /// Gets or sets the item.
-        /// </summary>
-        /// <value>The item.</value>
-        public IHasImages Item { get; set; }
-        /// <summary>
-        /// The original image date modified
-        /// </summary>
-        public ItemImageInfo Image;
-
-        public IImageProcessor ImageProcessor { get; set; }
-
-        /// <summary>
-        /// The _options
-        /// </summary>
-        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
-        /// <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>
-        private Task WriteToAsync(Stream responseStream)
-        {
-            var cropwhitespace = Request.Type == ImageType.Logo || Request.Type == ImageType.Art;
-
-            if (Request.CropWhitespace.HasValue)
-            {
-                cropwhitespace = Request.CropWhitespace.Value;
-            }
-
-            var options = new ImageProcessingOptions
-            {
-                CropWhiteSpace = cropwhitespace,
-                Enhancers = Enhancers,
-                Height = Request.Height,
-                ImageIndex = Request.Index ?? 0,
-                Image = Image,
-                Item = Item,
-                MaxHeight = Request.MaxHeight,
-                MaxWidth = Request.MaxWidth,
-                Quality = Request.Quality,
-                Width = Request.Width,
-                OutputFormat = Request.Format,
-                AddPlayedIndicator = Request.AddPlayedIndicator,
-                PercentPlayed = Request.PercentPlayed,
-                UnplayedCount = Request.UnplayedCount,
-                BackgroundColor = Request.BackgroundColor
-            };
-
-            return ImageProcessor.ProcessImage(options, responseStream);
-        }
-    }
-}

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

@@ -85,7 +85,6 @@
     <Compile Include="Images\ImageByNameService.cs" />
     <Compile Include="Images\ImageRequest.cs" />
     <Compile Include="Images\ImageService.cs" />
-    <Compile Include="Images\ImageWriter.cs" />
     <Compile Include="Music\InstantMixService.cs" />
     <Compile Include="ItemLookupService.cs" />
     <Compile Include="ItemRefreshService.cs" />
@@ -102,7 +101,6 @@
     <Compile Include="PackageReviewService.cs" />
     <Compile Include="PackageService.cs" />
     <Compile Include="Playback\BifService.cs" />
-    <Compile Include="Playback\EndlessStreamCopy.cs" />
     <Compile Include="Playback\Hls\BaseHlsService.cs" />
     <Compile Include="Playback\Hls\DynamicHlsService.cs" />
     <Compile Include="Playback\Hls\HlsSegmentService.cs" />
@@ -123,6 +121,7 @@
     <Compile Include="SearchService.cs" />
     <Compile Include="SessionsService.cs" />
     <Compile Include="SimilarItemsHelper.cs" />
+    <Compile Include="Sync\SyncService.cs" />
     <Compile Include="SystemService.cs" />
     <Compile Include="Movies\TrailersService.cs" />
     <Compile Include="TvShowsService.cs" />

+ 0 - 32
MediaBrowser.Api/Playback/EndlessStreamCopy.cs

@@ -1,32 +0,0 @@
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Api.Playback
-{
-    public class EndlessStreamCopy
-    {
-        public async Task CopyStream(Stream source, Stream target, CancellationToken cancellationToken)
-        {
-            long position = 0;
-            
-            while (!cancellationToken.IsCancellationRequested)
-            {
-                await source.CopyToAsync(target, 81920, cancellationToken).ConfigureAwait(false);
-
-                var fsPosition = source.Position;
-
-                var bytesRead = fsPosition - position;
-
-                //Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, path);
-
-                if (bytesRead == 0)
-                {
-                    await Task.Delay(100, cancellationToken).ConfigureAwait(false);
-                }
-
-                position = fsPosition;
-            }
-        }
-    }
-}

+ 4 - 3
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -149,7 +149,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 using (state)
                 {
-                    return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
+                    return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, null, FileShare.Read, responseHeaders, isHeadRequest);
                 }
             }
 
@@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 try
                 {
-                    return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
+                    return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, null, FileShare.Read, responseHeaders, isHeadRequest);
                 }
                 finally
                 {
@@ -285,7 +285,8 @@ namespace MediaBrowser.Api.Playback.Progressive
                 state.Dispose();
             }
 
-            var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem);
+            var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
+            var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
 
             result.Options["Content-Type"] = contentType;
 

+ 12 - 4
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -12,6 +12,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         private string Path { get; set; }
         private ILogger Logger { get; set; }
         private readonly IFileSystem _fileSystem;
+        private readonly TranscodingJob _job;
 
         /// <summary>
         /// The _options
@@ -32,11 +33,12 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// <param name="path">The path.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="fileSystem">The file system.</param>
-        public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem)
+        public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem, TranscodingJob job)
         {
             Path = path;
             Logger = logger;
             _fileSystem = fileSystem;
+            _job = job;
         }
 
         /// <summary>
@@ -59,7 +61,8 @@ namespace MediaBrowser.Api.Playback.Progressive
         {
             try
             {
-                await new ProgressiveFileCopier(_fileSystem).StreamFile(Path, responseStream).ConfigureAwait(false);
+                await new ProgressiveFileCopier(_fileSystem, _job)
+                    .StreamFile(Path, responseStream).ConfigureAwait(false);
             }
             catch
             {
@@ -77,10 +80,12 @@ namespace MediaBrowser.Api.Playback.Progressive
     public class ProgressiveFileCopier
     {
         private readonly IFileSystem _fileSystem;
+        private readonly TranscodingJob _job;
 
-        public ProgressiveFileCopier(IFileSystem fileSystem)
+        public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
         {
             _fileSystem = fileSystem;
+            _job = job;
         }
 
         public async Task StreamFile(string path, Stream outputStream)
@@ -102,7 +107,10 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                     if (bytesRead == 0)
                     {
-                        eofCount++;
+                        if (_job == null || _job.HasExited)
+                        {
+                            eofCount++;
+                        }
                         await Task.Delay(100).ConfigureAwait(false);
                     }
                     else

+ 106 - 0
MediaBrowser.Api/Sync/SyncService.cs

@@ -0,0 +1,106 @@
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Sync;
+using ServiceStack;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Sync
+{
+    [Route("/Sync/Jobs/{Id}", "DELETE", Summary = "Cancels a sync job.")]
+    public class CancelSyncJob : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Sync/Schedules/{Id}", "DELETE", Summary = "Cancels a sync job.")]
+    public class CancelSyncSchedule : IReturnVoid
+    {
+        [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Sync/Jobs/{Id}", "GET", Summary = "Gets a sync job.")]
+    public class GetSyncJob : IReturn<SyncJob>
+    {
+        [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Sync/Schedules/{Id}", "GET", Summary = "Gets a sync job.")]
+    public class GetSyncSchedule : IReturn<SyncSchedule>
+    {
+        [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Sync/Jobs", "GET", Summary = "Gets sync jobs.")]
+    public class GetSyncJobs : IReturn<QueryResult<SyncJob>>
+    {
+    }
+
+    [Route("/Sync/Schedules", "GET", Summary = "Gets sync schedules.")]
+    public class GetSyncSchedules : IReturn<QueryResult<SyncSchedule>>
+    {
+    }
+
+    [Authenticated]
+    public class SyncService : BaseApiService
+    {
+        private readonly ISyncManager _syncManager;
+
+        public SyncService(ISyncManager syncManager)
+        {
+            _syncManager = syncManager;
+        }
+
+        public object Get(GetSyncJobs request)
+        {
+            var result = _syncManager.GetJobs(new SyncJobQuery
+            {
+                 
+            });
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetSyncSchedules request)
+        {
+            var result = _syncManager.GetSchedules(new SyncScheduleQuery
+            {
+                 
+            }); 
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetSyncJob request)
+        {
+            var result = _syncManager.GetJob(request.Id);
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetSyncSchedule request)
+        {
+            var result = _syncManager.GetSchedule(request.Id);
+
+            return ToOptimizedResult(result);
+        }
+
+        public void Delete(CancelSyncJob request)
+        {
+            var task = _syncManager.CancelJob(request.Id);
+
+            Task.WaitAll(task);
+        }
+
+        public void Delete(CancelSyncSchedule request)
+        {
+            var task = _syncManager.CancelSchedule(request.Id);
+
+            Task.WaitAll(task);
+        }
+    }
+}

+ 4 - 0
MediaBrowser.Common/Net/MimeTypes.cs

@@ -143,6 +143,10 @@ namespace MediaBrowser.Common.Net
             {
                 return "image/png";
             }
+            if (ext.Equals(".webp", StringComparison.OrdinalIgnoreCase))
+            {
+                return "image/webp";
+            }
             if (ext.Equals(".ico", StringComparison.OrdinalIgnoreCase))
             {
                 return "image/vnd.microsoft.icon";

+ 7 - 0
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -73,6 +73,13 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="toStream">To stream.</param>
         /// <returns>Task.</returns>
         Task ProcessImage(ImageProcessingOptions options, Stream toStream);
+        
+        /// <summary>
+        /// Processes the image.
+        /// </summary>
+        /// <param name="options">The options.</param>
+        /// <returns>Task.</returns>
+        Task<string> ProcessImage(ImageProcessingOptions options);
 
         /// <summary>
         /// Gets the enhanced image.

+ 15 - 4
MediaBrowser.Controller/Net/IHttpResultFactory.cs

@@ -80,9 +80,13 @@ namespace MediaBrowser.Controller.Net
         /// <param name="responseHeaders">The response headers.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>System.Object.</returns>
-        object GetStaticResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified,
-                               TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn,
-                               IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false);
+        object GetStaticResult(IRequest requestContext, 
+            Guid cacheKey, 
+            DateTime? lastDateModified,
+            TimeSpan? cacheDuration, 
+            string contentType, Func<Task<Stream>> factoryFn,
+            IDictionary<string, string> responseHeaders = null,
+            bool isHeadRequest = false);
 
         /// <summary>
         /// Gets the static file result.
@@ -101,11 +105,18 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestContext">The request context.</param>
         /// <param name="path">The path.</param>
         /// <param name="contentType">Type of the content.</param>
+        /// <param name="cacheCuration">The cache curation.</param>
         /// <param name="fileShare">The file share.</param>
         /// <param name="responseHeaders">The response headers.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>System.Object.</returns>
-        object GetStaticFileResult(IRequest requestContext, string path, string contentType, FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false);
+        object GetStaticFileResult(IRequest requestContext, 
+            string path, 
+            string contentType,
+            TimeSpan? cacheCuration = null,
+            FileShare fileShare = FileShare.Read, 
+            IDictionary<string, string> responseHeaders = null, 
+            bool isHeadRequest = false);
         
         /// <summary>
         /// Gets the optimized serialized result using cache.

+ 14 - 0
MediaBrowser.Controller/Sync/ISyncManager.cs

@@ -33,6 +33,20 @@ namespace MediaBrowser.Controller.Sync
         /// <returns>QueryResult&lt;SyncSchedule&gt;.</returns>
         QueryResult<SyncSchedule> GetSchedules(SyncScheduleQuery query);
 
+        /// <summary>
+        /// Gets the job.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <returns>SyncJob.</returns>
+        SyncJob GetJob(string id);
+
+        /// <summary>
+        /// Gets the schedule.
+        /// </summary>
+        /// <param name="id">The identifier.</param>
+        /// <returns>SyncSchedule.</returns>
+        SyncSchedule GetSchedule(string id);
+        
         /// <summary>
         /// Cancels the job.
         /// </summary>

+ 1 - 1
MediaBrowser.Dlna/DlnaManager.cs

@@ -37,7 +37,7 @@ namespace MediaBrowser.Dlna
             _logger = logger;
             _jsonSerializer = jsonSerializer;
 
-            DumpProfiles();
+            //DumpProfiles();
         }
 
         public IEnumerable<DeviceProfile> GetProfiles()

+ 1 - 2
MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Model.Dlna;
 using System.Xml.Serialization;
-using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Dlna.Profiles
 {
@@ -198,7 +197,7 @@ namespace MediaBrowser.Dlna.Profiles
             {
                 new SubtitleProfile
                 {
-                    Format = SubtitleFormat.SRT
+                    Format = "srt"
                 }
             };
         }

+ 1 - 1
MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs

@@ -343,7 +343,7 @@ namespace MediaBrowser.Dlna.Profiles
             {
                 new SubtitleProfile
                 {
-                    Format = SubtitleFormat.SMI
+                    Format = "smi"
                 }
             };
         }

+ 21 - 77
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -118,14 +118,19 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
         public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
         {
-            if (options == null)
+            var file = await ProcessImage(options).ConfigureAwait(false);
+
+            using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
             {
-                throw new ArgumentNullException("options");
+                await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
             }
+        }
 
-            if (toStream == null)
+        public async Task<string> ProcessImage(ImageProcessingOptions options)
+        {
+            if (options == null)
             {
-                throw new ArgumentNullException("toStream");
+                throw new ArgumentNullException("options");
             }
 
             var originalImagePath = options.Image.Path;
@@ -133,11 +138,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
             if (options.HasDefaultOptions() && options.Enhancers.Count == 0 && !options.CropWhiteSpace)
             {
                 // Just spit out the original file if all the options are default
-                using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                {
-                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
-                    return;
-                }
+                return originalImagePath;
             }
 
             var dateModified = options.Image.DateModified;
@@ -166,30 +167,13 @@ namespace MediaBrowser.Server.Implementations.Drawing
             if (options.HasDefaultOptionsWithoutSize() && newSize.Equals(originalImageSize) && options.Enhancers.Count == 0)
             {
                 // Just spit out the original file if the new size equals the old
-                using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                {
-                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
-                    return;
-                }
+                return originalImagePath;
             }
 
             var quality = options.Quality ?? 90;
 
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, options.OutputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor);
 
-            try
-            {
-                using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
-                {
-                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
-                    return;
-                }
-            }
-            catch (IOException)
-            {
-                // Cache file doesn't exist or is currently being written to
-            }
-
             var semaphore = GetLock(cacheFilePath);
 
             await semaphore.WaitAsync().ConfigureAwait(false);
@@ -197,17 +181,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
             // Check again in case of lock contention
             try
             {
-                using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
+                if (File.Exists(cacheFilePath))
                 {
-                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                     semaphore.Release();
-                    return;
+                    return cacheFilePath;
                 }
             }
-            catch (IOException)
-            {
-                // Cache file doesn't exist or is currently being written to
-            }
             catch
             {
                 semaphore.Release();
@@ -218,37 +197,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
             {
                 var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue;
 
-                //if (!hasPostProcessing)
-                //{
-                //    using (var outputStream = await _mediaEncoder.EncodeImage(new ImageEncodingOptions
-                //    {
-                //        InputPath = originalImagePath,
-                //        MaxHeight = options.MaxHeight,
-                //        MaxWidth = options.MaxWidth,
-                //        Height = options.Height,
-                //        Width = options.Width,
-                //        Quality = options.Quality,
-                //        Format = options.OutputFormat == ImageOutputFormat.Original ? Path.GetExtension(originalImagePath).TrimStart('.') : options.OutputFormat.ToString().ToLower()
-
-                //    }, CancellationToken.None).ConfigureAwait(false))
-                //    {
-                //        using (var outputMemoryStream = new MemoryStream())
-                //        {
-                //            // Save to the memory stream
-                //            await outputStream.CopyToAsync(outputMemoryStream).ConfigureAwait(false);
-
-                //            var bytes = outputMemoryStream.ToArray();
-
-                //            await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-
-                //            // kick off a task to cache the result
-                //            await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false);
-                //        }
-
-                //        return;
-                //    }
-                //}
-
                 using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
                     // Copy to memory stream to avoid Image locking file
@@ -289,18 +237,16 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
                                     var outputFormat = GetOutputFormat(originalImage, options.OutputFormat);
 
-                                    using (var outputMemoryStream = new MemoryStream())
+                                    Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+
+                                    // Save to the cache location
+                                    using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
                                     {
                                         // Save to the memory stream
-                                        thumbnail.Save(outputFormat, outputMemoryStream, quality);
-
-                                        var bytes = outputMemoryStream.ToArray();
-
-                                        await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-
-                                        // kick off a task to cache the result
-                                        await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false);
+                                        thumbnail.Save(outputFormat, cacheFileStream, quality);
                                     }
+
+                                    return cacheFilePath;
                                 }
                             }
 
@@ -324,9 +270,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
         {
             try
             {
-                var parentPath = Path.GetDirectoryName(cacheFilePath);
-
-                Directory.CreateDirectory(parentPath);
+                Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
 
                 // Save to the cache location
                 using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))

+ 8 - 4
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -306,11 +306,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 throw new ArgumentNullException("path");
             }
 
-            return GetStaticFileResult(requestContext, path, MimeTypes.GetMimeType(path), fileShare, responseHeaders, isHeadRequest);
+            return GetStaticFileResult(requestContext, path, MimeTypes.GetMimeType(path), null, fileShare, responseHeaders, isHeadRequest);
         }
 
-        public object GetStaticFileResult(IRequest requestContext, string path, string contentType,
-            FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null,
+        public object GetStaticFileResult(IRequest requestContext, 
+            string path, 
+            string contentType,
+            TimeSpan? cacheCuration = null,
+            FileShare fileShare = FileShare.Read, 
+            IDictionary<string, string> responseHeaders = null,
             bool isHeadRequest = false)
         {
             if (string.IsNullOrEmpty(path))
@@ -327,7 +331,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
             var cacheKey = path + dateModified.Ticks;
 
-            return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, contentType, () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest);
+            return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, cacheCuration, contentType, () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest);
         }
 
         /// <summary>

+ 2 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -319,5 +319,6 @@
     "ButtonAudioTracks":  "Audio Tracks",
     "ButtonSubtitles":  "Subtitles",
     "ButtonScenes":  "Scenes",
-    "ButtonQuality":  "Quality"
+    "ButtonQuality":  "Quality",
+    "HeaderNotifications":  "Notifications"
 }

+ 8 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -882,5 +882,12 @@
 	"LabelAppName": "App name",
 	"LabelAppNameExample": "Example: Sickbeard, NzbDrone",
 	"HeaderNewApiKeyHelp": "Grant an application permission to communicate with Media Browser.",
-    "ButtonEnterSupporterKey":  "Enter supporter key"
+    "ButtonEnterSupporterKey":  "Enter supporter key",
+    "HeaderHttpHeaders":  "Http Headers",
+    "HeaderIdentificationHeader":  "Identification Header",
+    "LabelValue":  "Value:",
+    "LabelMatchType":  "Match type:",
+    "OptionEquals":  "Equals",
+    "OptionRegex":  "Regex",
+    "OptionSubstring":  "Substring"
 }

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

@@ -261,6 +261,7 @@
     <Compile Include="Persistence\SqliteUserRepository.cs" />
     <Compile Include="Sorting\StudioComparer.cs" />
     <Compile Include="Sorting\VideoBitRateComparer.cs" />
+    <Compile Include="Sync\SyncManager.cs" />
     <Compile Include="Themes\AppThemeManager.cs" />
     <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" />
     <Compile Include="Udp\UdpServer.cs" />

+ 52 - 0
MediaBrowser.Server.Implementations/Sync/SyncManager.cs

@@ -0,0 +1,52 @@
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Sync;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Sync
+{
+    public class SyncManager : ISyncManager
+    {
+        public Task<List<SyncJob>> CreateJob(SyncJobRequest request)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task<SyncSchedule> CreateSchedule(SyncScheduleRequest request)
+        {
+            throw new NotImplementedException();
+        }
+
+        public QueryResult<SyncJob> GetJobs(SyncJobQuery query)
+        {
+            throw new NotImplementedException();
+        }
+
+        public QueryResult<SyncSchedule> GetSchedules(SyncScheduleQuery query)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task CancelJob(string id)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task CancelSchedule(string id)
+        {
+            throw new NotImplementedException();
+        }
+
+        public SyncJob GetJob(string id)
+        {
+            throw new NotImplementedException();
+        }
+
+        public SyncSchedule GetSchedule(string id)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 4 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -33,6 +33,7 @@ using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Subtitles;
+using MediaBrowser.Controller.Sync;
 using MediaBrowser.Controller.Themes;
 using MediaBrowser.Dlna;
 using MediaBrowser.Dlna.ConnectionManager;
@@ -69,6 +70,7 @@ using MediaBrowser.Server.Implementations.Persistence;
 using MediaBrowser.Server.Implementations.Security;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Session;
+using MediaBrowser.Server.Implementations.Sync;
 using MediaBrowser.Server.Implementations.Themes;
 using MediaBrowser.Server.Implementations.WebSocket;
 using MediaBrowser.ServerApplication.EntryPoints;
@@ -629,6 +631,8 @@ namespace MediaBrowser.ServerApplication
                 MediaEncoder, ChapterManager);
             RegisterSingleInstance(EncodingManager);
 
+            RegisterSingleInstance<ISyncManager>(new SyncManager());
+
             var authContext = new AuthorizationContext();
             RegisterSingleInstance<IAuthorizationContext>(authContext);
             RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));