diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 12aac0ccbd..a7bcbf821e 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Audio/{Id}/stream.flac", "GET")] [Route("/Audio/{Id}/stream.ogg", "GET")] [Route("/Audio/{Id}/stream.oga", "GET")] - [Route("/Audio/{Id}/stream.webma", "GET")] + [Route("/Audio/{Id}/stream.webm", "GET")] [Route("/Audio/{Id}/stream", "GET")] [Route("/Audio/{Id}/stream.mp3", "HEAD")] [Route("/Audio/{Id}/stream.wma", "HEAD")] @@ -23,7 +23,7 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Audio/{Id}/stream.flac", "HEAD")] [Route("/Audio/{Id}/stream.ogg", "HEAD")] [Route("/Audio/{Id}/stream.oga", "HEAD")] - [Route("/Audio/{Id}/stream.webma", "HEAD")] + [Route("/Audio/{Id}/stream.webm", "HEAD")] [Route("/Audio/{Id}/stream", "HEAD")] [Api(Description = "Gets an audio stream")] public class GetAudioStream : StreamRequest diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index f2a9ca24b5..a27e3cc325 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -167,11 +167,9 @@ namespace MediaBrowser.Api { var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userManager); - var tasks = _userManager.Users.OrderBy(u => u.Name).Select(dtoBuilder.GetUserDto).ToArray(); + var users = _userManager.Users.OrderBy(u => u.Name).Select(dtoBuilder.GetUserDto).ToArray(); - var task = Task.WhenAll(tasks); - - return ToOptimizedResult(task.Result); + return ToOptimizedResult(users); } /// @@ -188,7 +186,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException("User not found"); } - var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetUserDto(user).Result; + var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetUserDto(user); return ToOptimizedResult(result); } @@ -302,7 +300,7 @@ namespace MediaBrowser.Api newUser.UpdateConfiguration(dtoUser.Configuration, _xmlSerializer); - var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetUserDto(newUser).Result; + var result = new DtoBuilder(Logger, _libraryManager, _userManager).GetUserDto(newUser); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Common/Extensions/NamedLock.cs b/MediaBrowser.Common/Extensions/NamedLock.cs deleted file mode 100644 index d0164c70d0..0000000000 --- a/MediaBrowser.Common/Extensions/NamedLock.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Common.Extensions -{ - /// - /// Class NamedLock - /// - public class NamedLock : IDisposable - { - /// - /// The _locks - /// - private readonly Dictionary _locks = new Dictionary(); - - /// - /// Waits the async. - /// - /// The name. - /// Task. - public Task WaitAsync(string name) - { - return GetLock(name).WaitAsync(); - } - - /// - /// Releases the specified name. - /// - /// The name. - public void Release(string name) - { - SemaphoreSlim semaphore; - - if (_locks.TryGetValue(name, out semaphore)) - { - semaphore.Release(); - } - } - - /// - /// Gets the lock. - /// - /// The filename. - /// System.Object. - private SemaphoreSlim GetLock(string filename) - { - SemaphoreSlim fileLock; - lock (_locks) - { - if (!_locks.TryGetValue(filename, out fileLock)) - { - fileLock = new SemaphoreSlim(1,1); - _locks[filename] = fileLock; - } - } - return fileLock; - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - DisposeLocks(); - } - } - - /// - /// Disposes the locks. - /// - private void DisposeLocks() - { - lock (_locks) - { - foreach (var semaphore in _locks.Values) - { - semaphore.Dispose(); - } - - _locks.Clear(); - } - } - } -} diff --git a/MediaBrowser.Common/IO/FileSystemRepository.cs b/MediaBrowser.Common/IO/FileSystemRepository.cs index 24ad04a926..3a4987a944 100644 --- a/MediaBrowser.Common/IO/FileSystemRepository.cs +++ b/MediaBrowser.Common/IO/FileSystemRepository.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Concurrent; using System.IO; -using System.Threading.Tasks; namespace MediaBrowser.Common.IO { @@ -18,11 +17,6 @@ namespace MediaBrowser.Common.IO /// private readonly ConcurrentDictionary _subFolderPaths = new ConcurrentDictionary(); - /// - /// The _file locks - /// - private readonly NamedLock _fileLocks = new NamedLock(); - /// /// Gets or sets the path. /// @@ -170,24 +164,6 @@ namespace MediaBrowser.Common.IO return File.Exists(path); } - /// - /// Waits for lock. - /// - /// The resource path. - public Task WaitForLockAsync(string resourcePath) - { - return _fileLocks.WaitAsync(resourcePath); - } - - /// - /// Releases the lock. - /// - /// The resource path. - public void ReleaseLock(string resourcePath) - { - _fileLocks.Release(resourcePath); - } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -204,7 +180,6 @@ namespace MediaBrowser.Common.IO { if (dispose) { - _fileLocks.Dispose(); } } } diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index a8fc4564c7..e9dda6bb77 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -61,7 +61,6 @@ - diff --git a/MediaBrowser.Controller/Drawing/ImageManager.cs b/MediaBrowser.Controller/Drawing/ImageManager.cs index d78ff819bc..a5e36da329 100644 --- a/MediaBrowser.Controller/Drawing/ImageManager.cs +++ b/MediaBrowser.Controller/Drawing/ImageManager.cs @@ -15,6 +15,7 @@ using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Drawing @@ -50,7 +51,7 @@ namespace MediaBrowser.Controller.Drawing /// /// The cached imaged sizes /// - private readonly ConcurrentDictionary> _cachedImagedSizes = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary _cachedImagedSizes = new ConcurrentDictionary(); /// /// The _logger @@ -67,12 +68,18 @@ namespace MediaBrowser.Controller.Drawing /// private readonly Kernel _kernel; + /// + /// The _locks + /// + private readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); + /// /// Initializes a new instance of the class. /// /// The kernel. /// The protobuf serializer. /// The logger. + /// The app paths. public ImageManager(Kernel kernel, IProtobufSerializer protobufSerializer, ILogger logger, IServerApplicationPaths appPaths) { _protobufSerializer = protobufSerializer; @@ -117,15 +124,7 @@ namespace MediaBrowser.Controller.Drawing if (cropWhitespace) { - try - { - originalImagePath = await GetCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); - } - catch (Exception ex) - { - // We have to have a catch-all here because some of the .net image methods throw a plain old Exception - _logger.ErrorException("Error cropping image", ex); - } + originalImagePath = await GetCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); } try @@ -140,12 +139,12 @@ namespace MediaBrowser.Controller.Drawing originalImagePath = ehnancedImagePath; } } - catch + catch (Exception ex) { - _logger.Error("Error enhancing image"); + _logger.Error("Error enhancing image", ex); } - var originalImageSize = await GetImageSize(originalImagePath, dateModified).ConfigureAwait(false); + var originalImageSize = GetImageSize(originalImagePath, dateModified); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, width, height, maxWidth, maxHeight); @@ -158,67 +157,102 @@ namespace MediaBrowser.Controller.Drawing var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality.Value, dateModified); // Grab the cache file if it already exists - try + if (File.Exists(cacheFilePath)) { using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) { await fileStream.CopyToAsync(toStream).ConfigureAwait(false); + return; } - return; } - catch (FileNotFoundException) + + var semaphore = GetLock(cacheFilePath); + + await semaphore.WaitAsync().ConfigureAwait(false); + + // Check again in case of lock contention + if (File.Exists(cacheFilePath)) { - // Cache file doesn't exist. No biggie. + try + { + using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); + return; + } + } + finally + { + semaphore.Release(); + } } - using (var fileStream = File.OpenRead(originalImagePath)) + try { - using (var originalImage = Bitmap.FromStream(fileStream, true, false)) + using (var fileStream = File.OpenRead(originalImagePath)) { - var newWidth = Convert.ToInt32(newSize.Width); - var newHeight = Convert.ToInt32(newSize.Height); + using (var originalImage = Image.FromStream(fileStream, true, false)) + { + var newWidth = Convert.ToInt32(newSize.Width); + var newHeight = Convert.ToInt32(newSize.Height); - // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here - var thumbnail = !ImageExtensions.IsPixelFormatSupportedByGraphicsObject(originalImage.PixelFormat) ? new Bitmap(originalImage, newWidth, newHeight) : new Bitmap(newWidth, newHeight, originalImage.PixelFormat); + // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here + var thumbnail = !ImageExtensions.IsPixelFormatSupportedByGraphicsObject(originalImage.PixelFormat) ? new Bitmap(originalImage, newWidth, newHeight) : new Bitmap(newWidth, newHeight, originalImage.PixelFormat); - // Preserve the original resolution - thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution); + // Preserve the original resolution + thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution); - var thumbnailGraph = Graphics.FromImage(thumbnail); + var thumbnailGraph = Graphics.FromImage(thumbnail); - thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality; - thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; - thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; - thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; - thumbnailGraph.CompositingMode = CompositingMode.SourceOver; + thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality; + thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; + thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; + thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; + thumbnailGraph.CompositingMode = CompositingMode.SourceOver; - thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight); + thumbnailGraph.DrawImage(originalImage, 0, 0, newWidth, newHeight); - var outputFormat = originalImage.RawFormat; + var outputFormat = originalImage.RawFormat; - using (var memoryStream = new MemoryStream { }) - { - // Save to the memory stream - thumbnail.Save(outputFormat, memoryStream, quality.Value); + using (var memoryStream = new MemoryStream { }) + { + // Save to the memory stream + thumbnail.Save(outputFormat, memoryStream, quality.Value); - var bytes = memoryStream.ToArray(); + var bytes = memoryStream.ToArray(); - var outputTask = Task.Run(async () => await toStream.WriteAsync(bytes, 0, bytes.Length)); + var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length); - // Save to the cache location - using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - // Save to the filestream - await cacheFileStream.WriteAsync(bytes, 0, bytes.Length); + // kick off a task to cache the result + Task.Run(() => CacheResizedImage(cacheFilePath, bytes)); + + await outputTask.ConfigureAwait(false); } - await outputTask.ConfigureAwait(false); + thumbnailGraph.Dispose(); + thumbnail.Dispose(); } - - thumbnailGraph.Dispose(); - thumbnail.Dispose(); } } + finally + { + semaphore.Release(); + } + } + + /// + /// Caches the resized image. + /// + /// The cache file path. + /// The bytes. + private async void CacheResizedImage(string cacheFilePath, byte[] bytes) + { + // Save to the cache location + using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + // Save to the filestream + await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } } /// @@ -252,7 +286,7 @@ namespace MediaBrowser.Controller.Drawing /// The date modified. /// Task{ImageSize}. /// imagePath - public Task GetImageSize(string imagePath, DateTime dateModified) + public ImageSize GetImageSize(string imagePath, DateTime dateModified) { if (string.IsNullOrEmpty(imagePath)) { @@ -261,18 +295,7 @@ namespace MediaBrowser.Controller.Drawing var name = imagePath + "datemodified=" + dateModified.Ticks; - return _cachedImagedSizes.GetOrAdd(name, keyName => GetImageSizeTask(keyName, imagePath)); - } - - /// - /// Gets cached image dimensions, or results null if non-existant - /// - /// Name of the key. - /// The image path. - /// Task{ImageSize}. - private Task GetImageSizeTask(string keyName, string imagePath) - { - return Task.Run(() => GetImageSize(keyName, imagePath)); + return _cachedImagedSizes.GetOrAdd(name, keyName => GetImageSize(keyName, imagePath)); } /// @@ -297,27 +320,14 @@ namespace MediaBrowser.Controller.Drawing // Cache file doesn't exist no biggie } - var size = ImageHeader.GetDimensions(imagePath, _logger); + _logger.Debug("Getting image size for {0}", imagePath); - var imageSize = new ImageSize { Width = size.Width, Height = size.Height }; + var size = ImageHeader.GetDimensions(imagePath, _logger); // Update the file system cache - CacheImageSize(fullCachePath, size.Width, size.Height); - - return imageSize; - } + Task.Run(() => _protobufSerializer.SerializeToFile(new[] { size.Width, size.Height }, fullCachePath)); - /// - /// Caches image dimensions - /// - /// The cache path. - /// The width. - /// The height. - private void CacheImageSize(string cachePath, int width, int height) - { - var output = new[] { width, height }; - - _protobufSerializer.SerializeToFile(output, cachePath); + return new ImageSize { Width = size.Width, Height = size.Height }; } /// @@ -367,7 +377,7 @@ namespace MediaBrowser.Controller.Drawing return video.Chapters[imageIndex].ImagePath; } - + return item.GetImage(imageType); } @@ -409,7 +419,7 @@ namespace MediaBrowser.Controller.Drawing { throw new ArgumentNullException("imagePath"); } - + var metaFileEntry = item.ResolveArgs.GetMetaFileByPath(imagePath); // If we didn't the metafile entry, check the Season @@ -440,38 +450,53 @@ namespace MediaBrowser.Controller.Drawing var croppedImagePath = CroppedImageCache.GetResourcePath(name, Path.GetExtension(originalImagePath)); - if (!CroppedImageCache.ContainsFilePath(croppedImagePath)) + if (CroppedImageCache.ContainsFilePath(croppedImagePath)) + { + return croppedImagePath; + } + + var semaphore = GetLock(croppedImagePath); + + await semaphore.WaitAsync().ConfigureAwait(false); + + // Check again in case of contention + if (CroppedImageCache.ContainsFilePath(croppedImagePath)) + { + semaphore.Release(); + return croppedImagePath; + } + + try { using (var fileStream = File.OpenRead(originalImagePath)) { - using (var originalImage = (Bitmap)Bitmap.FromStream(fileStream, true, false)) + using (var originalImage = (Bitmap)Image.FromStream(fileStream, true, false)) { var outputFormat = originalImage.RawFormat; using (var croppedImage = originalImage.CropWhitespace()) { - await SaveImageToFile(croppedImage, outputFormat, croppedImagePath).ConfigureAwait(false); + using (var outputStream = new FileStream(croppedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + croppedImage.Save(outputFormat, outputStream, 100); + } } } } } - - return croppedImagePath; - } - - private async Task SaveImageToFile(Image image, ImageFormat outputFormat, string file) - { - using (var memoryStream = new MemoryStream()) + catch (Exception ex) { - image.Save(outputFormat, memoryStream, 100); + // We have to have a catch-all here because some of the .net image methods throw a plain old Exception + _logger.ErrorException("Error cropping image {0}", ex, originalImagePath); - memoryStream.Position = 0; - - using (var cacheFileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await memoryStream.CopyToAsync(cacheFileStream).ConfigureAwait(false); - } + return originalImagePath; + } + finally + { + semaphore.Release(); } + + return croppedImagePath; } /// @@ -509,7 +534,23 @@ namespace MediaBrowser.Controller.Drawing // All enhanced images are saved as png to allow transparency var enhancedImagePath = EnhancedImageCache.GetResourcePath(cacheGuid + ".png"); - if (!EnhancedImageCache.ContainsFilePath(enhancedImagePath)) + if (EnhancedImageCache.ContainsFilePath(enhancedImagePath)) + { + return enhancedImagePath; + } + + var semaphore = GetLock(enhancedImagePath); + + await semaphore.WaitAsync().ConfigureAwait(false); + + // Check again in case of contention + if (EnhancedImageCache.ContainsFilePath(enhancedImagePath)) + { + semaphore.Release(); + return enhancedImagePath; + } + + try { using (var fileStream = File.OpenRead(originalImagePath)) { @@ -519,11 +560,18 @@ namespace MediaBrowser.Controller.Drawing using (var newImage = await ExecuteImageEnhancers(supportedEnhancers, originalImage, item, imageType, imageIndex).ConfigureAwait(false)) { //And then save it in the cache - await SaveImageToFile(newImage, ImageFormat.Png, enhancedImagePath).ConfigureAwait(false); + using (var outputStream = new FileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + newImage.Save(ImageFormat.Png, outputStream, 100); + } } } } } + finally + { + semaphore.Release(); + } return enhancedImagePath; } @@ -547,7 +595,7 @@ namespace MediaBrowser.Controller.Drawing { throw new ArgumentNullException("imagePath"); } - + var dateModified = GetImageDateModified(item, imagePath); var supportedEnhancers = _kernel.ImageEnhancers.Where(i => i.Supports(item, imageType)); @@ -624,6 +672,19 @@ namespace MediaBrowser.Controller.Drawing return result; } + /// + /// Gets the lock. + /// + /// The filename. + /// System.Object. + private SemaphoreSlim GetLock(string filename) + { + return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// public void Dispose() { Dispose(true); diff --git a/MediaBrowser.Controller/Library/DtoBuilder.cs b/MediaBrowser.Controller/Library/DtoBuilder.cs index d9244c64c2..e87282c1fb 100644 --- a/MediaBrowser.Controller/Library/DtoBuilder.cs +++ b/MediaBrowser.Controller/Library/DtoBuilder.cs @@ -59,11 +59,21 @@ namespace MediaBrowser.Controller.Library var tasks = new List(); + if (fields.Contains(ItemFields.Studios)) + { + dto.Studios = item.Studios; + } + + if (fields.Contains(ItemFields.People)) + { + tasks.Add(AttachPeople(dto, item)); + } + if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) { try { - tasks.Add(AttachPrimaryImageAspectRatio(dto, item)); + AttachPrimaryImageAspectRatio(dto, item); } catch (Exception ex) { @@ -72,16 +82,6 @@ namespace MediaBrowser.Controller.Library } } - if (fields.Contains(ItemFields.Studios)) - { - dto.Studios = item.Studios; - } - - if (fields.Contains(ItemFields.People)) - { - tasks.Add(AttachPeople(dto, item)); - } - AttachBasicFields(dto, item, fields); // Make sure all the tasks we kicked off have completed. @@ -120,19 +120,6 @@ namespace MediaBrowser.Controller.Library var tasks = new List(); - if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) - { - try - { - tasks.Add(AttachPrimaryImageAspectRatio(dto, item)); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, item.Name); - } - } - if (fields.Contains(ItemFields.Studios)) { dto.Studios = item.Studios; @@ -145,6 +132,19 @@ namespace MediaBrowser.Controller.Library tasks.Add(AttachUserSpecificInfo(dto, item, user, fields)); + if (fields.Contains(ItemFields.PrimaryImageAspectRatio)) + { + try + { + AttachPrimaryImageAspectRatio(dto, item); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.ErrorException("Error generating PrimaryImageAspectRatio for {0}", ex, item.Name); + } + } + AttachBasicFields(dto, item, fields); // Make sure all the tasks we kicked off have completed. @@ -199,7 +199,7 @@ namespace MediaBrowser.Controller.Library /// The dto. /// The item. /// Task. - private async Task AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item) + private void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item) { var path = item.PrimaryImagePath; @@ -217,7 +217,7 @@ namespace MediaBrowser.Controller.Library try { - size = await Kernel.Instance.ImageManager.GetImageSize(path, dateModified).ConfigureAwait(false); + size = Kernel.Instance.ImageManager.GetImageSize(path, dateModified); } catch (FileNotFoundException) { @@ -771,7 +771,7 @@ namespace MediaBrowser.Controller.Library /// The user. /// DtoUser. /// user - public async Task GetUserDto(User user) + public UserDto GetUserDto(User user) { if (user == null) { @@ -796,7 +796,7 @@ namespace MediaBrowser.Controller.Library try { - await AttachPrimaryImageAspectRatio(dto, user).ConfigureAwait(false); + AttachPrimaryImageAspectRatio(dto, user); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index ca3c1fe7cf..c5e4de2bc1 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -300,10 +300,18 @@ namespace MediaBrowser.Server.Implementations.Library var activityDate = DateTime.UtcNow; + var lastActivityDate = user.LastActivityDate; + user.LastActivityDate = activityDate; LogConnection(user.Id, clientType, deviceId, deviceName, activityDate); + // Don't log in the db anymore frequently than 10 seconds + if (lastActivityDate.HasValue && (activityDate - lastActivityDate.Value).TotalSeconds < 10) + { + return Task.FromResult(true); + } + // Save this directly. No need to fire off all the events for this. return Kernel.UserRepository.SaveUser(user, CancellationToken.None); } diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs index 44674e125c..8a15d4028e 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs @@ -48,10 +48,10 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// /// The app paths. /// The protobuf serializer. - /// The logger. + /// The log manager. /// protobufSerializer - public SQLiteDisplayPreferencesRepository(IApplicationPaths appPaths, IProtobufSerializer protobufSerializer, ILogger logger) - : base(logger) + public SQLiteDisplayPreferencesRepository(IApplicationPaths appPaths, IProtobufSerializer protobufSerializer, ILogManager logManager) + : base(logManager) { if (protobufSerializer == null) { diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs index 6a9a9f6f5d..3403cf79ff 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs @@ -56,10 +56,10 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// /// The app paths. /// The json serializer. - /// The logger. + /// The log manager. /// appPaths - public SQLiteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogger logger) - : base(logger) + public SQLiteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) + : base(logManager) { if (appPaths == null) { diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs index 1173f3fc80..c5320a1f62 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs @@ -46,16 +46,16 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// /// Initializes a new instance of the class. /// - /// The logger. + /// The log manager. /// logger - protected SqliteRepository(ILogger logger) + protected SqliteRepository(ILogManager logManager) { - if (logger == null) + if (logManager == null) { - throw new ArgumentNullException("logger"); + throw new ArgumentNullException("logManager"); } - Logger = logger; + Logger = logManager.GetLogger(GetType().Name); } /// diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs index f3dede889d..2c8d7f437f 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs @@ -49,10 +49,10 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// /// The app paths. /// The protobuf serializer. - /// The logger. + /// The log manager. /// protobufSerializer - public SQLiteUserDataRepository(IApplicationPaths appPaths, IProtobufSerializer protobufSerializer, ILogger logger) - : base(logger) + public SQLiteUserDataRepository(IApplicationPaths appPaths, IProtobufSerializer protobufSerializer, ILogManager logManager) + : base(logManager) { if (protobufSerializer == null) { diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs index 43dffc5964..812c98789f 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs @@ -50,10 +50,10 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// /// The app paths. /// The json serializer. - /// The logger. + /// The log manager. /// appPaths - public SQLiteUserRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogger logger) - : base(logger) + public SQLiteUserRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager) + : base(logManager) { if (appPaths == null) { diff --git a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs index 9ddd14491e..98d7fb4775 100644 --- a/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs +++ b/MediaBrowser.ServerApplication/EntryPoints/WebSocketEvents.cs @@ -172,9 +172,9 @@ namespace MediaBrowser.ServerApplication.EntryPoints /// /// The sender. /// The e. - async void userManager_UserUpdated(object sender, GenericEventArgs e) + void userManager_UserUpdated(object sender, GenericEventArgs e) { - var dto = await new DtoBuilder(_logger, _libraryManager, _userManager).GetUserDto(e.Argument).ConfigureAwait(false); + var dto = new DtoBuilder(_logger, _libraryManager, _userManager).GetUserDto(e.Argument); _serverManager.SendWebSocketMessage("UserUpdated", dto); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs b/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs index 6109f73bbb..debe785996 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs @@ -40,9 +40,11 @@ namespace MediaBrowser.WebDashboard.Api /// /// Initializes a new instance of the class. /// + /// The app host. /// The logger. /// The task manager. /// The user manager. + /// The library manager. public DashboardInfoWebSocketListener(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager) : base(logger) { @@ -59,7 +61,7 @@ namespace MediaBrowser.WebDashboard.Api /// Task{IEnumerable{TaskInfo}}. protected override Task GetDataToSend(object state) { - return DashboardService.GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager); + return Task.FromResult(DashboardService.GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager)); } } } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index f6c64b49eb..15ccbfb3e4 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -147,7 +147,7 @@ namespace MediaBrowser.WebDashboard.Api /// System.Object. public object Get(GetDashboardInfo request) { - return GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager).Result; + return GetDashboardInfo(_appHost, Logger, _taskManager, _userManager, _libraryManager); } /// @@ -159,14 +159,13 @@ namespace MediaBrowser.WebDashboard.Api /// The user manager. /// The library manager. /// DashboardInfo. - public static async Task GetDashboardInfo(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager) + public static DashboardInfo GetDashboardInfo(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager) { var connections = userManager.RecentConnections.ToArray(); var dtoBuilder = new DtoBuilder(logger, libraryManager, userManager); - var tasks = userManager.Users.Where(u => connections.Any(c => c.UserId == u.Id)).Select(dtoBuilder.GetUserDto); - var users = await Task.WhenAll(tasks).ConfigureAwait(false); + var users = userManager.Users.Where(u => connections.Any(c => c.UserId == u.Id)).Select(dtoBuilder.GetUserDto); return new DashboardInfo { @@ -180,7 +179,7 @@ namespace MediaBrowser.WebDashboard.Api ActiveConnections = connections, - Users = users + Users = users.ToArray() }; }