浏览代码

re-factored some file system access

Luke Pulverenti 12 年之前
父节点
当前提交
02fedead11
共有 24 个文件被更改,包括 339 次插入258 次删除
  1. 2 1
      MediaBrowser.Api/Images/ImageService.cs
  2. 4 1
      MediaBrowser.Api/Images/ImageWriter.cs
  3. 14 0
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  4. 24 3
      MediaBrowser.Api/SimilarItemsHelper.cs
  5. 34 41
      MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  6. 10 55
      MediaBrowser.Common/IO/FileSystemRepository.cs
  7. 80 32
      MediaBrowser.Controller/Drawing/ImageManager.cs
  8. 0 1
      MediaBrowser.Controller/Entities/BaseItem.cs
  9. 8 18
      MediaBrowser.Controller/Entities/User.cs
  10. 11 46
      MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
  11. 11 3
      MediaBrowser.Controller/Providers/MediaInfo/AudioImageProvider.cs
  12. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  13. 28 0
      MediaBrowser.Model/ApiClient/IApiClient.cs
  14. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  15. 29 0
      MediaBrowser.Model/Querying/SimilarItemsQuery.cs
  16. 15 24
      MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
  17. 11 4
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  18. 8 0
      MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
  19. 14 7
      MediaBrowser.Server.Implementations/ScheduledTasks/ImageCleanupTask.cs
  20. 12 4
      MediaBrowser.Server.Implementations/ScheduledTasks/VideoImagesTask.cs
  21. 15 13
      MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs
  22. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  23. 1 1
      Nuget/MediaBrowser.Common.nuspec
  24. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

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

@@ -696,7 +696,8 @@ namespace MediaBrowser.Api.Images
                 Item = item,
                 Request = request,
                 CropWhiteSpace = request.Type == ImageType.Logo || request.Type == ImageType.Art,
-                OriginalImageDateModified = originalFileImageDateModified
+                OriginalImageDateModified = originalFileImageDateModified,
+                Enhancers = supportedImageEnhancers
 
             }, contentType);
         }

+ 4 - 1
MediaBrowser.Api/Images/ImageWriter.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
 using ServiceStack.Service;
 using ServiceStack.ServiceHost;
 using System;
@@ -14,6 +15,8 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     public class ImageWriter : IStreamWriter, IHasOptions
     {
+        public List<IImageEnhancer> Enhancers;
+
         /// <summary>
         /// Gets or sets the request.
         /// </summary>
@@ -67,7 +70,7 @@ namespace MediaBrowser.Api.Images
         {
             return Kernel.Instance.ImageManager.ProcessImage(Item, Request.Type, Request.Index ?? 0, CropWhiteSpace,
                                                     OriginalImageDateModified, responseStream, Request.Width, Request.Height, Request.MaxWidth,
-                                                    Request.MaxHeight, Request.Quality);
+                                                    Request.MaxHeight, Request.Quality, Enhancers);
         }
     }
 }

+ 14 - 0
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -340,6 +340,13 @@ namespace MediaBrowser.Api.Playback
 
                 try
                 {
+                    var parentPath = Path.GetDirectoryName(path);
+
+                    if (!Directory.Exists(parentPath))
+                    {
+                        Directory.CreateDirectory(parentPath);
+                    }
+
                     var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, subtitleStream.Index, offset, path, CancellationToken.None);
 
                     Task.WaitAll(task);
@@ -371,6 +378,13 @@ namespace MediaBrowser.Api.Playback
             {
                 try
                 {
+                    var parentPath = Path.GetDirectoryName(path);
+
+                    if (!Directory.Exists(parentPath))
+                    {
+                        Directory.CreateDirectory(parentPath);
+                    }
+
                     var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, offset, CancellationToken.None);
 
                     Task.WaitAll(task);

+ 24 - 3
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
@@ -38,6 +37,29 @@ namespace MediaBrowser.Api
         /// <value>The limit.</value>
         [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int? Limit { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: AudioInfo, Budget, Chapters, CriticRatingSummary, DateCreated, DisplayMediaType, EndDate, Genres, HomePageUrl, ItemCounts, IndexOptions, Locations, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SeriesInfo, SortName, Studios, Taglines, TrailerUrls, UserData", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string Fields { get; set; }
+
+        /// <summary>
+        /// Gets the item fields.
+        /// </summary>
+        /// <returns>IEnumerable{ItemFields}.</returns>
+        public IEnumerable<ItemFields> GetItemFields()
+        {
+            var val = Fields;
+
+            if (string.IsNullOrEmpty(val))
+            {
+                return new ItemFields[] { };
+            }
+
+            return val.Split(',').Select(v => (ItemFields)Enum.Parse(typeof(ItemFields), v, true));
+        }
     }
 
     /// <summary>
@@ -64,8 +86,7 @@ namespace MediaBrowser.Api
                 (request.UserId.HasValue ? user.RootFolder :
                 (Folder)libraryManager.RootFolder) : DtoBuilder.GetItemByClientId(request.Id, userManager, libraryManager, request.UserId);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
+            var fields = request.GetItemFields().ToList();
 
             var dtoBuilder = new DtoBuilder(logger, libraryManager, userDataRepository);
 

+ 34 - 41
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -123,9 +123,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
                 {
                     try
                     {
-                        return JsonSerializer.DeserializeFromFile<TaskResult>(GetHistoryFilePath());
+                        return JsonSerializer.DeserializeFromFile<TaskResult>(GetHistoryFilePath(false));
                     }
-                    catch (IOException)
+                    catch (DirectoryNotFoundException)
+                    {
+                        // File doesn't exist. No biggie
+                        return null;
+                    }
+                    catch (FileNotFoundException)
                     {
                         // File doesn't exist. No biggie
                         return null;
@@ -413,63 +418,46 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             }
         }
 
-        /// <summary>
-        /// The _scheduled tasks configuration directory
-        /// </summary>
-        private string _scheduledTasksConfigurationDirectory;
         /// <summary>
         /// Gets the scheduled tasks configuration directory.
         /// </summary>
-        /// <value>The scheduled tasks configuration directory.</value>
-        private string ScheduledTasksConfigurationDirectory
+        /// <param name="create">if set to <c>true</c> [create].</param>
+        /// <returns>System.String.</returns>
+        private string GetScheduledTasksConfigurationDirectory(bool create)
         {
-            get
-            {
-                if (_scheduledTasksConfigurationDirectory == null)
-                {
-                    _scheduledTasksConfigurationDirectory = Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
+            var path = Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
 
-                    if (!Directory.Exists(_scheduledTasksConfigurationDirectory))
-                    {
-                        Directory.CreateDirectory(_scheduledTasksConfigurationDirectory);
-                    }
-                }
-                return _scheduledTasksConfigurationDirectory;
+            if (create && !Directory.Exists(path))
+            {
+                Directory.CreateDirectory(path);
             }
+
+            return path;
         }
 
-        /// <summary>
-        /// The _scheduled tasks data directory
-        /// </summary>
-        private string _scheduledTasksDataDirectory;
         /// <summary>
         /// Gets the scheduled tasks data directory.
         /// </summary>
-        /// <value>The scheduled tasks data directory.</value>
-        private string ScheduledTasksDataDirectory
+        /// <param name="create">if set to <c>true</c> [create].</param>
+        /// <returns>System.String.</returns>
+        private string GetScheduledTasksDataDirectory(bool create)
         {
-            get
-            {
-                if (_scheduledTasksDataDirectory == null)
-                {
-                    _scheduledTasksDataDirectory = Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks");
+            var path = Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks");
 
-                    if (!Directory.Exists(_scheduledTasksDataDirectory))
-                    {
-                        Directory.CreateDirectory(_scheduledTasksDataDirectory);
-                    }
-                }
-                return _scheduledTasksDataDirectory;
+            if (create && !Directory.Exists(path))
+            {
+                Directory.CreateDirectory(path);
             }
+            return path;
         }
 
         /// <summary>
         /// Gets the history file path.
         /// </summary>
         /// <value>The history file path.</value>
-        private string GetHistoryFilePath()
+        private string GetHistoryFilePath(bool createDirectory)
         {
-            return Path.Combine(ScheduledTasksDataDirectory, Id + ".js");
+            return Path.Combine(GetScheduledTasksDataDirectory(createDirectory), Id + ".js");
         }
 
         /// <summary>
@@ -478,7 +466,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <returns>System.String.</returns>
         private string GetConfigurationFilePath()
         {
-            return Path.Combine(ScheduledTasksConfigurationDirectory, Id + ".js");
+            return Path.Combine(GetScheduledTasksConfigurationDirectory(false), Id + ".js");
         }
 
         /// <summary>
@@ -493,7 +481,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
                 .Select(ScheduledTaskHelpers.GetTrigger)
                 .ToList();
             }
-            catch (IOException)
+            catch (FileNotFoundException)
+            {
+                // File doesn't exist. No biggie. Return defaults.
+                return ScheduledTask.GetDefaultTriggers();
+            }
+            catch (DirectoryNotFoundException)
             {
                 // File doesn't exist. No biggie. Return defaults.
                 return ScheduledTask.GetDefaultTriggers();
@@ -530,7 +523,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
                 Id = Id
             };
 
-            JsonSerializer.SerializeToFile(result, GetHistoryFilePath());
+            JsonSerializer.SerializeToFile(result, GetHistoryFilePath(true));
 
             LastExecutionResult = result;
 

+ 10 - 55
MediaBrowser.Common/IO/FileSystemRepository.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using System;
-using System.Collections.Concurrent;
 using System.IO;
 
 namespace MediaBrowser.Common.IO
@@ -11,12 +10,6 @@ namespace MediaBrowser.Common.IO
     /// </summary>
     public class FileSystemRepository
     {
-        /// <summary>
-        /// Contains the list of subfolders under the main directory
-        /// The directory entry is created when the item is first added to the dictionary
-        /// </summary>
-        private readonly ConcurrentDictionary<string, string> _subFolderPaths = new ConcurrentDictionary<string, string>();
-
         /// <summary>
         /// Gets or sets the path.
         /// </summary>
@@ -36,18 +29,6 @@ namespace MediaBrowser.Common.IO
             }
 
             Path = path;
-            Initialize();
-        }
-
-        /// <summary>
-        /// Initializes this instance.
-        /// </summary>
-        protected void Initialize()
-        {
-            if (!Directory.Exists(Path))
-            {
-                Directory.CreateDirectory(Path);
-            }
         }
 
         /// <summary>
@@ -56,17 +37,18 @@ namespace MediaBrowser.Common.IO
         /// <param name="uniqueName">Name of the unique.</param>
         /// <param name="fileExtension">The file extension.</param>
         /// <returns>System.String.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
+        /// <exception cref="System.ArgumentNullException">
+        /// </exception>
         public string GetResourcePath(string uniqueName, string fileExtension)
         {
             if (string.IsNullOrEmpty(uniqueName))
             {
-                throw new ArgumentNullException();
+                throw new ArgumentNullException("uniqueName");
             }
 
             if (string.IsNullOrEmpty(fileExtension))
             {
-                throw new ArgumentNullException();
+                throw new ArgumentNullException("fileExtension");
             }
             
             var filename = uniqueName.GetMD5() + fileExtension;
@@ -75,7 +57,7 @@ namespace MediaBrowser.Common.IO
         }
 
         /// <summary>
-        /// Gets the full path of where a file should be stored within the repository
+        /// Gets the resource path.
         /// </summary>
         /// <param name="filename">The filename.</param>
         /// <returns>System.String.</returns>
@@ -84,41 +66,14 @@ namespace MediaBrowser.Common.IO
         {
             if (string.IsNullOrEmpty(filename))
             {
-                throw new ArgumentNullException();
+                throw new ArgumentNullException("filename");
             }
             
-            return GetInternalResourcePath(filename);
-        }
-
-        /// <summary>
-        /// Takes a filename and returns the full path of where it should be stored
-        /// </summary>
-        /// <param name="filename">The filename.</param>
-        /// <returns>System.String.</returns>
-        private string GetInternalResourcePath(string filename)
-        {
             var prefix = filename.Substring(0, 1);
 
-            var folder = _subFolderPaths.GetOrAdd(prefix, GetCachePath);
-
-            return System.IO.Path.Combine(folder, filename);
-        }
-
-        /// <summary>
-        /// Creates a subfolder under the image cache directory and returns the full path
-        /// </summary>
-        /// <param name="prefix">The prefix.</param>
-        /// <returns>System.String.</returns>
-        private string GetCachePath(string prefix)
-        {
             var path = System.IO.Path.Combine(Path, prefix);
-
-            if (!Directory.Exists(path))
-            {
-                Directory.CreateDirectory(path);
-            }
-
-            return path;
+            
+            return System.IO.Path.Combine(path, filename);
         }
 
         /// <summary>
@@ -144,8 +99,8 @@ namespace MediaBrowser.Common.IO
             {
                 throw new ArgumentNullException();
             }
-            
-            return ContainsFilePath(GetInternalResourcePath(filename));
+
+            return ContainsFilePath(GetResourcePath(filename));
         }
 
         /// <summary>

+ 80 - 32
MediaBrowser.Controller/Drawing/ImageManager.cs

@@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// <value>The image enhancers.</value>
         public IEnumerable<IImageEnhancer> ImageEnhancers { get; set; }
-        
+
         /// <summary>
         /// Gets the image size cache.
         /// </summary>
@@ -106,9 +106,10 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
         /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
         /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
+        /// <param name="enhancers">The enhancers.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">entity</exception>
-        public async Task ProcessImage(BaseItem entity, ImageType imageType, int imageIndex, bool cropWhitespace, DateTime dateModified, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
+        public async Task ProcessImage(BaseItem entity, ImageType imageType, int imageIndex, bool cropWhitespace, DateTime dateModified, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality, List<IImageEnhancer> enhancers)
         {
             if (entity == null)
             {
@@ -127,28 +128,13 @@ namespace MediaBrowser.Controller.Drawing
                 originalImagePath = await GetCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
             }
 
-            var supportedEnhancers = ImageEnhancers.Where(i =>
-            {
-                try
-                {
-                    return i.Supports(entity, imageType);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error in image enhancer: {0}", ex, i.GetType().Name);
-
-                    return false;
-                }
-
-            }).ToList();
-
             // No enhancement - don't cache
-            if (supportedEnhancers.Count > 0)
+            if (enhancers.Count > 0)
             {
                 try
                 {
                     // Enhance if we have enhancers
-                    var ehnancedImagePath = await GetEnhancedImage(originalImagePath, dateModified, entity, imageType, imageIndex, supportedEnhancers).ConfigureAwait(false);
+                    var ehnancedImagePath = await GetEnhancedImage(originalImagePath, dateModified, entity, imageType, imageIndex, enhancers).ConfigureAwait(false);
 
                     // If the path changed update dateModified
                     if (!ehnancedImagePath.Equals(originalImagePath, StringComparison.OrdinalIgnoreCase))
@@ -175,6 +161,19 @@ namespace MediaBrowser.Controller.Drawing
 
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality.Value, dateModified);
 
+            try
+            {
+                using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                {
+                    await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
+                    return;
+                }
+            }
+            catch (IOException)
+            {
+                // Cache file doesn't exist or is currently being written ro
+            }
+
             var semaphore = GetLock(cacheFilePath);
 
             await semaphore.WaitAsync().ConfigureAwait(false);
@@ -262,6 +261,13 @@ namespace MediaBrowser.Controller.Drawing
         /// <param name="bytes">The bytes.</param>
         private async Task CacheResizedImage(string cacheFilePath, byte[] bytes)
         {
+            var parentPath = Path.GetDirectoryName(cacheFilePath);
+
+            if (!Directory.Exists(parentPath))
+            {
+                Directory.CreateDirectory(parentPath);
+            }
+
             // Save to the cache location
             using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
             {
@@ -323,7 +329,7 @@ namespace MediaBrowser.Controller.Drawing
         }
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-        
+
         /// <summary>
         /// Gets the size of the image.
         /// </summary>
@@ -335,25 +341,53 @@ namespace MediaBrowser.Controller.Drawing
             // Now check the file system cache
             var fullCachePath = ImageSizeCache.GetResourcePath(keyName, ".txt");
 
+            try
+            {
+                var result = File.ReadAllText(fullCachePath).Split('|').Select(i => double.Parse(i, UsCulture)).ToArray();
+
+                return new ImageSize { Width = result[0], Height = result[1] };
+            }
+            catch (IOException)
+            {
+                // Cache file doesn't exist or is currently being written to
+            }
+
             var semaphore = GetLock(fullCachePath);
 
             await semaphore.WaitAsync().ConfigureAwait(false);
 
             try
             {
-                try
-                {
-                    var result = File.ReadAllText(fullCachePath).Split('|').Select(i => double.Parse(i, UsCulture)).ToArray();
+                var result = File.ReadAllText(fullCachePath).Split('|').Select(i => double.Parse(i, UsCulture)).ToArray();
 
-                    return new ImageSize { Width = result[0], Height = result[1] };
-                }
-                catch (FileNotFoundException)
-                {
-                    // Cache file doesn't exist no biggie
-                }
+                return new ImageSize { Width = result[0], Height = result[1] };
+            }
+            catch (FileNotFoundException)
+            {
+                // Cache file doesn't exist no biggie
+            }
+            catch (DirectoryNotFoundException)
+            {
+                // Cache file doesn't exist no biggie
+            }
+            catch
+            {
+                semaphore.Release();
+
+                throw;
+            }
 
+            try
+            {
                 var size = await ImageHeader.GetDimensions(imagePath, _logger).ConfigureAwait(false);
 
+                var parentPath = Path.GetDirectoryName(fullCachePath);
+
+                if (!Directory.Exists(parentPath))
+                {
+                    Directory.CreateDirectory(parentPath);
+                }
+
                 // Update the file system cache
                 File.WriteAllText(fullCachePath, size.Width.ToString(UsCulture) + @"|" + size.Height.ToString(UsCulture));
 
@@ -490,12 +524,12 @@ namespace MediaBrowser.Controller.Drawing
             await semaphore.WaitAsync().ConfigureAwait(false);
 
             // Check again in case of contention
-            if (CroppedImageCache.ContainsFilePath(croppedImagePath))
+            if (File.Exists(croppedImagePath))
             {
                 semaphore.Release();
                 return croppedImagePath;
             }
-            
+
             try
             {
                 using (var fileStream = new FileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
@@ -511,6 +545,13 @@ namespace MediaBrowser.Controller.Drawing
 
                             using (var croppedImage = originalImage.CropWhitespace())
                             {
+                                var parentPath = Path.GetDirectoryName(croppedImagePath);
+
+                                if (!Directory.Exists(parentPath))
+                                {
+                                    Directory.CreateDirectory(parentPath);
+                                }
+
                                 using (var outputStream = new FileStream(croppedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read))
                                 {
                                     croppedImage.Save(outputFormat, outputStream, 100);
@@ -568,7 +609,7 @@ namespace MediaBrowser.Controller.Drawing
             await semaphore.WaitAsync().ConfigureAwait(false);
 
             // Check again in case of contention
-            if (EnhancedImageCache.ContainsFilePath(enhancedImagePath))
+            if (File.Exists(enhancedImagePath))
             {
                 semaphore.Release();
                 return enhancedImagePath;
@@ -588,6 +629,13 @@ namespace MediaBrowser.Controller.Drawing
                             //Pass the image through registered enhancers
                             using (var newImage = await ExecuteImageEnhancers(supportedEnhancers, originalImage, item, imageType, imageIndex).ConfigureAwait(false))
                             {
+                                var parentDirectory = Path.GetDirectoryName(enhancedImagePath);
+
+                                if (!Directory.Exists(parentDirectory))
+                                {
+                                    Directory.CreateDirectory(parentDirectory);
+                                }
+
                                 //And then save it in the cache
                                 using (var outputStream = new FileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read))
                                 {

+ 0 - 1
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -304,7 +304,6 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// We attach these to the item so that we only ever have to hit the file system once
         /// (this includes the children of the containing folder)
-        /// Use ResolveArgs.FileSystemDictionary to check for the existence of files instead of File.Exists
         /// </summary>
         /// <value>The resolve args.</value>
         [IgnoreDataMember]

+ 8 - 18
MediaBrowser.Controller/Entities/User.cs

@@ -19,10 +19,6 @@ namespace MediaBrowser.Controller.Entities
         public static IUserManager UserManager { get; set; }
         public static IXmlSerializer XmlSerializer { get; set; }
 
-        /// <summary>
-        /// The _root folder path
-        /// </summary>
-        private string _rootFolderPath;
         /// <summary>
         /// Gets the root folder path.
         /// </summary>
@@ -32,23 +28,19 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
-                if (_rootFolderPath == null)
+                if (Configuration.UseCustomLibrary)
                 {
-                    if (Configuration.UseCustomLibrary)
-                    {
-                        _rootFolderPath = GetRootFolderPath(Name);
+                    var rootFolderPath = GetRootFolderPath(Name);
 
-                        if (!Directory.Exists(_rootFolderPath))
-                        {
-                            Directory.CreateDirectory(_rootFolderPath);
-                        }
-                    }
-                    else
+                    if (!Directory.Exists(rootFolderPath))
                     {
-                        _rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+                        Directory.CreateDirectory(rootFolderPath);
                     }
+
+                    return rootFolderPath;
                 }
-                return _rootFolderPath;
+
+                return ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
             }
         }
 
@@ -261,7 +253,6 @@ namespace MediaBrowser.Controller.Entities
 
             // Force these to be lazy loaded again
             _configurationDirectoryPath = null;
-            _rootFolderPath = null;
             RootFolder = null;
 
             // Kick off a task to validate the media library
@@ -378,7 +369,6 @@ namespace MediaBrowser.Controller.Entities
             // Force these to be lazy loaded again
             if (customLibraryChanged)
             {
-                _rootFolderPath = null;
                 RootFolder = null;
             }
         }

+ 11 - 46
MediaBrowser.Controller/MediaInfo/FFMpegManager.cs

@@ -55,10 +55,6 @@ namespace MediaBrowser.Controller.MediaInfo
             SubtitleCache = new FileSystemRepository(SubtitleCachePath);
         }
 
-        /// <summary>
-        /// The _video images data path
-        /// </summary>
-        private string _videoImagesDataPath;
         /// <summary>
         /// Gets the video images data path.
         /// </summary>
@@ -67,24 +63,10 @@ namespace MediaBrowser.Controller.MediaInfo
         {
             get
             {
-                if (_videoImagesDataPath == null)
-                {
-                    _videoImagesDataPath = Path.Combine(_appPaths.DataPath, "extracted-video-images");
-
-                    if (!Directory.Exists(_videoImagesDataPath))
-                    {
-                        Directory.CreateDirectory(_videoImagesDataPath);
-                    }
-                }
-
-                return _videoImagesDataPath;
+                return Path.Combine(_appPaths.DataPath, "extracted-video-images");
             }
         }
 
-        /// <summary>
-        /// The _audio images data path
-        /// </summary>
-        private string _audioImagesDataPath;
         /// <summary>
         /// Gets the audio images data path.
         /// </summary>
@@ -93,24 +75,10 @@ namespace MediaBrowser.Controller.MediaInfo
         {
             get
             {
-                if (_audioImagesDataPath == null)
-                {
-                    _audioImagesDataPath = Path.Combine(_appPaths.DataPath, "extracted-audio-images");
-
-                    if (!Directory.Exists(_audioImagesDataPath))
-                    {
-                        Directory.CreateDirectory(_audioImagesDataPath);
-                    }
-                }
-
-                return _audioImagesDataPath;
+                return Path.Combine(_appPaths.DataPath, "extracted-audio-images");
             }
         }
 
-        /// <summary>
-        /// The _subtitle cache path
-        /// </summary>
-        private string _subtitleCachePath;
         /// <summary>
         /// Gets the subtitle cache path.
         /// </summary>
@@ -119,17 +87,7 @@ namespace MediaBrowser.Controller.MediaInfo
         {
             get
             {
-                if (_subtitleCachePath == null)
-                {
-                    _subtitleCachePath = Path.Combine(_appPaths.CachePath, "subtitles");
-
-                    if (!Directory.Exists(_subtitleCachePath))
-                    {
-                        Directory.CreateDirectory(_subtitleCachePath);
-                    }
-                }
-
-                return _subtitleCachePath;
+                return Path.Combine(_appPaths.CachePath, "subtitles");
             }
         }
         
@@ -177,7 +135,7 @@ namespace MediaBrowser.Controller.MediaInfo
 
                 var path = VideoImageCache.GetResourcePath(filename, ".jpg");
 
-                if (!VideoImageCache.ContainsFilePath(path))
+                if (!File.Exists(path))
                 {
                     if (extractImages)
                     {
@@ -204,6 +162,13 @@ namespace MediaBrowser.Controller.MediaInfo
 
                         try
                         {
+                            var parentPath = Path.GetDirectoryName(path);
+
+                            if (!Directory.Exists(parentPath))
+                            {
+                                Directory.CreateDirectory(parentPath);
+                            }
+                            
                             await _encoder.ExtractImage(inputPath, type, time, path, cancellationToken).ConfigureAwait(false);
                             chapter.ImagePath = path;
                             changesMade = true;

+ 11 - 3
MediaBrowser.Controller/Providers/MediaInfo/AudioImageProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -156,7 +157,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
 
             var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg");
 
-            if (!ImageCache.ContainsFilePath(path))
+            if (!File.Exists(path))
             {
                 var semaphore = GetLock(path);
 
@@ -164,10 +165,17 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
                 await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
 
                 // Check again
-                if (!ImageCache.ContainsFilePath(path))
+                if (!File.Exists(path))
                 {
                     try
                     {
+                        var parentPath = Path.GetDirectoryName(path);
+
+                        if (!Directory.Exists(parentPath))
+                        {
+                            Directory.CreateDirectory(parentPath);
+                        }
+
                         await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.AudioFile, null, path, cancellationToken).ConfigureAwait(false);
                     }
                     finally

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -244,6 +244,9 @@
     <Compile Include="..\MediaBrowser.Model\Querying\PersonsQuery.cs">
       <Link>Querying\PersonsQuery.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Querying\SimilarItemsQuery.cs">
+      <Link>Querying\SimilarItemsQuery.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Querying\ThemeSongsResult.cs">
       <Link>Querying\ThemeSongsResult.cs</Link>
     </Compile>

+ 28 - 0
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -72,6 +72,34 @@ namespace MediaBrowser.Model.ApiClient
         /// <exception cref="ArgumentNullException">query</exception>
         Task<ItemsResult> GetItemsAsync(ItemQuery query);
 
+        /// <summary>
+        /// Gets the similar movies async.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>Task{ItemsResult}.</returns>
+        Task<ItemsResult> GetSimilarMoviesAsync(SimilarItemsQuery query);
+
+        /// <summary>
+        /// Gets the similar series async.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>Task{ItemsResult}.</returns>
+        Task<ItemsResult> GetSimilarSeriesAsync(SimilarItemsQuery query);
+
+        /// <summary>
+        /// Gets the similar albums async.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>Task{ItemsResult}.</returns>
+        Task<ItemsResult> GetSimilarAlbumsAsync(SimilarItemsQuery query);
+
+        /// <summary>
+        /// Gets the similar games async.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>Task{ItemsResult}.</returns>
+        Task<ItemsResult> GetSimilarGamesAsync(SimilarItemsQuery query);
+        
         /// <summary>
         /// Gets the people async.
         /// </summary>

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

@@ -60,6 +60,7 @@
     <Compile Include="Querying\ItemReviewsResult.cs" />
     <Compile Include="Querying\ItemsByNameQuery.cs" />
     <Compile Include="Entities\BaseItemInfo.cs" />
+    <Compile Include="Querying\SimilarItemsQuery.cs" />
     <Compile Include="Session\BrowseRequest.cs" />
     <Compile Include="Session\PlayRequest.cs" />
     <Compile Include="Session\PlaystateRequest.cs" />

+ 29 - 0
MediaBrowser.Model/Querying/SimilarItemsQuery.cs

@@ -0,0 +1,29 @@
+namespace MediaBrowser.Model.Querying
+{
+    public class SimilarItemsQuery
+    {
+        /// <summary>
+        /// The user to localize search results for
+        /// </summary>
+        /// <value>The user id.</value>
+        public string UserId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// The maximum number of items to return
+        /// </summary>
+        /// <value>The limit.</value>
+        public int? Limit { get; set; }
+
+        /// <summary>
+        /// Fields to return within the items, in addition to basic information
+        /// </summary>
+        /// <value>The fields.</value>
+        public ItemFields[] Fields { get; set; }
+    }
+}

+ 15 - 24
MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs

@@ -94,29 +94,20 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         }
 
         /// <summary>
-        /// The _media tools path
+        /// Gets the media tools path.
         /// </summary>
-        private string _mediaToolsPath;
-        /// <summary>
-        /// Gets the folder path to tools
-        /// </summary>
-        /// <value>The media tools path.</value>
-        private string MediaToolsPath
+        /// <param name="create">if set to <c>true</c> [create].</param>
+        /// <returns>System.String.</returns>
+        private string GetMediaToolsPath(bool create)
         {
-            get
-            {
-                if (_mediaToolsPath == null)
-                {
-                    _mediaToolsPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
-
-                    if (!Directory.Exists(_mediaToolsPath))
-                    {
-                        Directory.CreateDirectory(_mediaToolsPath);
-                    }
-                }
+            var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
 
-                return _mediaToolsPath;
+            if (create && !Directory.Exists(path))
+            {
+                Directory.CreateDirectory(path);
             }
+
+            return path;
         }
 
         /// <summary>
@@ -185,7 +176,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
             var filename = resource.Substring(resource.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) + prefix.Length);
 
-            var versionedDirectoryPath = Path.Combine(MediaToolsPath, Path.GetFileNameWithoutExtension(filename));
+            var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), Path.GetFileNameWithoutExtension(filename));
 
             if (!Directory.Exists(versionedDirectoryPath))
             {
@@ -570,14 +561,14 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
             }
 
             var offsetParam = offset.Ticks > 0 ? "-ss " + offset.TotalSeconds + " " : string.Empty;
-            
+
             var process = new Process
             {
                 StartInfo = new ProcessStartInfo
                 {
                     RedirectStandardOutput = false,
                     RedirectStandardError = true,
-                    
+
                     CreateNoWindow = true,
                     UseShellExecute = false,
                     FileName = FFMpegPath,
@@ -744,7 +735,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
                     RedirectStandardOutput = false,
                     RedirectStandardError = true,
-                    
+
                     FileName = FFMpegPath,
                     Arguments = string.Format("{0}-i {1} -map 0:{2} -an -vn -c:s ass \"{3}\"", offsetParam, inputPath, subtitleStreamIndex, outputPath),
                     WindowStyle = ProcessWindowStyle.Hidden,
@@ -759,7 +750,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
 
             var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
-            
+
             try
             {
                 process.Start();

+ 11 - 4
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -328,9 +328,7 @@ namespace MediaBrowser.Server.Implementations.Providers
         public async Task<string> SaveImage(BaseItem item, Stream source, string targetName, bool saveLocally, CancellationToken cancellationToken)
         {
             //download and save locally
-            var localPath = (saveLocally && item.MetaLocation != null) ?
-                Path.Combine(item.MetaLocation, targetName) :
-                _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Path.ToLower(), targetName);
+            var localPath = GetSavePath(item, targetName, saveLocally);
 
             if (saveLocally) // queue to media directories
             {
@@ -374,9 +372,18 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// <returns>System.String.</returns>
         public string GetSavePath(BaseItem item, string targetFileName, bool saveLocally)
         {
-            return (saveLocally && item.MetaLocation != null) ?
+            var path = (saveLocally && item.MetaLocation != null) ?
                 Path.Combine(item.MetaLocation, targetFileName) :
                 _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id.ToString(), targetFileName);
+
+            var parentPath = Path.GetDirectoryName(path);
+
+            if (!Directory.Exists(parentPath))
+            {
+                Directory.CreateDirectory(parentPath);
+            }
+
+            return path;
         }
 
         /// <summary>

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

@@ -168,6 +168,14 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 if (!success)
                 {
                     previouslyFailedImages.Add(key);
+
+                    var parentPath = Path.GetDirectoryName(failHistoryPath);
+
+                    if (!Directory.Exists(parentPath))
+                    {
+                        Directory.CreateDirectory(parentPath);
+                    }
+
                     _jsonSerializer.SerializeToFile(previouslyFailedImages, failHistoryPath);
                 }
 

+ 14 - 7
MediaBrowser.Server.Implementations/ScheduledTasks/ImageCleanupTask.cs

@@ -165,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 var specialFeattures = _itemRepo.GetItems(movie.SpecialFeatureIds).ToList();
                 images = specialFeattures.Aggregate(images, (current, subItem) => current.Concat(GetPathsInUse(subItem)));
             }
-            
+
             return images;
         }
 
@@ -176,13 +176,20 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
         /// <returns>IEnumerable{System.String}.</returns>
         private IEnumerable<string> GetFiles(string path)
         {
-            return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
-                .Where(i =>
-                {
-                    var ext = Path.GetExtension(i);
+            try
+            {
+                return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
+                    .Where(i =>
+                    {
+                        var ext = Path.GetExtension(i);
 
-                    return !string.IsNullOrEmpty(ext) && BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
-                });
+                        return !string.IsNullOrEmpty(ext) && BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
+                    });
+            }
+            catch (DirectoryNotFoundException)
+            {
+                return new string[] { };
+            }
         }
 
         /// <summary>

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

@@ -8,14 +8,15 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers.MediaInfo;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MoreLinq;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
-using MoreLinq;
 
 namespace MediaBrowser.Server.Implementations.ScheduledTasks
 {
@@ -263,7 +264,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
 
             var path = ImageCache.GetResourcePath(filename, ".jpg");
 
-            if (!ImageCache.ContainsFilePath(path))
+            if (!File.Exists(path))
             {
                 var semaphore = GetLock(path);
 
@@ -271,10 +272,17 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
                 await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
 
                 // Check again
-                if (!ImageCache.ContainsFilePath(path))
+                if (!File.Exists(path))
                 {
                     try
                     {
+                        var parentPath = Path.GetDirectoryName(path);
+
+                        if (!Directory.Exists(parentPath))
+                        {
+                            Directory.CreateDirectory(parentPath);
+                        }
+
                         await ExtractImageInternal(item, path, cancellationToken).ConfigureAwait(false);
                     }
                     finally

+ 15 - 13
MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs

@@ -471,20 +471,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// <summary>
         /// Gets the critic reviews path.
         /// </summary>
-        /// <value>The critic reviews path.</value>
-        private string CriticReviewsPath
+        /// <param name="create">if set to <c>true</c> [create].</param>
+        /// <returns>System.String.</returns>
+        private string GetCriticReviewsPath(bool create)
         {
-            get
-            {
-                var path = Path.Combine(_appPaths.DataPath, "critic-reviews");
-
-                if (!Directory.Exists(path))
-                {
-                    Directory.CreateDirectory(path);
-                }
+            var path = Path.Combine(_appPaths.DataPath, "critic-reviews");
 
-                return path;
+            if (create && !Directory.Exists(path))
+            {
+                Directory.CreateDirectory(path);
             }
+
+            return path;
         }
 
         /// <summary>
@@ -499,10 +497,14 @@ namespace MediaBrowser.Server.Implementations.Sqlite
 
                 try
                 {
-                    var path = Path.Combine(CriticReviewsPath, itemId + ".json");
+                    var path = Path.Combine(GetCriticReviewsPath(false), itemId + ".json");
 
                     return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path);
                 }
+                catch (DirectoryNotFoundException)
+                {
+                    return new List<ItemReview>();
+                }
                 catch (FileNotFoundException)
                 {
                     return new List<ItemReview>();
@@ -521,7 +523,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         {
             return Task.Run(() =>
             {
-                var path = Path.Combine(CriticReviewsPath, itemId + ".json");
+                var path = Path.Combine(GetCriticReviewsPath(true), itemId + ".json");
 
                 _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
             });

+ 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.112</version>
+        <version>3.0.113</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.112" />
+            <dependency id="MediaBrowser.Common" version="3.0.113" />
             <dependency id="NLog" version="2.0.1.2" />
             <dependency id="ServiceStack.Text" version="3.9.45" />
             <dependency id="SimpleInjector" version="2.2.3" />

+ 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.112</version>
+        <version>3.0.113</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.112</version>
+        <version>3.0.113</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.112" />
+            <dependency id="MediaBrowser.Common" version="3.0.113" />
         </dependencies>
     </metadata>
     <files>