diff options
| author | Eric Reed <ebr@mediabrowser3.com> | 2013-09-25 14:32:36 -0400 |
|---|---|---|
| committer | Eric Reed <ebr@mediabrowser3.com> | 2013-09-25 14:32:36 -0400 |
| commit | c02c0db35af078e1a78897aecdade2efe57d3f06 (patch) | |
| tree | af9ef64305efd2e353a202c27b188d2c44cd9b5b /MediaBrowser.Server.Implementations | |
| parent | c6e57c6448c04998bcae5a906e7a064300542e75 (diff) | |
| parent | 2d9b48d00fd31aaa96676c82a054b2794493fbf9 (diff) | |
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Server.Implementations')
19 files changed, 542 insertions, 178 deletions
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 1f7361d2f..305bede56 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Drawing } catch (IOException) { - // Cache file doesn't exist or is currently being written ro + // Cache file doesn't exist or is currently being written to } var semaphore = GetLock(cacheFilePath); @@ -129,21 +129,24 @@ namespace MediaBrowser.Server.Implementations.Drawing await semaphore.WaitAsync().ConfigureAwait(false); // Check again in case of lock contention - if (File.Exists(cacheFilePath)) + try { - try - { - using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - await fileStream.CopyToAsync(toStream).ConfigureAwait(false); - return; - } - } - finally + using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) { + await fileStream.CopyToAsync(toStream).ConfigureAwait(false); semaphore.Release(); + return; } } + catch (IOException) + { + // Cache file doesn't exist or is currently being written to + } + catch + { + semaphore.Release(); + throw; + } try { @@ -188,12 +191,10 @@ namespace MediaBrowser.Server.Implementations.Drawing var bytes = outputMemoryStream.ToArray(); - var outputTask = toStream.WriteAsync(bytes, 0, bytes.Length); + await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); // kick off a task to cache the result - var cacheTask = CacheResizedImage(cacheFilePath, bytes); - - await Task.WhenAll(outputTask, cacheTask).ConfigureAwait(false); + CacheResizedImage(cacheFilePath, bytes, semaphore); } } } @@ -202,13 +203,52 @@ namespace MediaBrowser.Server.Implementations.Drawing } } } - finally + catch { semaphore.Release(); + + throw; } } /// <summary> + /// Caches the resized image. + /// </summary> + /// <param name="cacheFilePath">The cache file path.</param> + /// <param name="bytes">The bytes.</param> + /// <param name="semaphore">The semaphore.</param> + private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore) + { + Task.Run(async () => + { + try + { + var parentPath = Path.GetDirectoryName(cacheFilePath); + + if (!Directory.Exists(parentPath)) + { + Directory.CreateDirectory(parentPath); + } + + // Save to the cache location + using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) + { + // Save to the filestream + await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath); + } + finally + { + semaphore.Release(); + } + }); + } + + /// <summary> /// Sets the color of the background. /// </summary> /// <param name="graphics">The graphics.</param> @@ -364,28 +404,6 @@ namespace MediaBrowser.Server.Implementations.Drawing } /// <summary> - /// Caches the resized image. - /// </summary> - /// <param name="cacheFilePath">The cache file path.</param> - /// <param name="bytes">The bytes.</param> - private async Task CacheResizedImage(string cacheFilePath, byte[] bytes) - { - var parentPath = Path.GetDirectoryName(cacheFilePath); - - if (!Directory.Exists(parentPath)) - { - Directory.CreateDirectory(parentPath); - } - - // Save to the cache location - using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) - { - // Save to the filestream - await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - } - } - - /// <summary> /// Gets the cache file path based on a set of parameters /// </summary> private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, ImageOverlay? overlay, int percentPlayed, string backgroundColor) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index e5260004a..a5f54b938 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -237,7 +237,9 @@ namespace MediaBrowser.Server.Implementations.Dto NowViewingItemId = session.NowViewingItemId, NowViewingItemName = session.NowViewingItemName, NowViewingItemType = session.NowViewingItemType, - ApplicationVersion = session.ApplicationVersion + ApplicationVersion = session.ApplicationVersion, + CanSeek = session.CanSeek, + QueueableMediaTypes = session.QueueableMediaTypes }; if (session.NowPlayingItem != null) diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs new file mode 100644 index 000000000..9c1a953b1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -0,0 +1,97 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.Udp; +using System.Net.Sockets; + +namespace MediaBrowser.Server.Implementations.EntryPoints +{ + /// <summary> + /// Class UdpServerEntryPoint + /// </summary> + public class UdpServerEntryPoint : IServerEntryPoint + { + /// <summary> + /// Gets or sets the UDP server. + /// </summary> + /// <value>The UDP server.</value> + private UdpServer UdpServer { get; set; } + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + /// <summary> + /// The _network manager + /// </summary> + private readonly INetworkManager _networkManager; + /// <summary> + /// The _server configuration manager + /// </summary> + private readonly IServerConfigurationManager _serverConfigurationManager; + /// <summary> + /// The _HTTP server + /// </summary> + private readonly IHttpServer _httpServer; + + public const int PortNumber = 7359; + + /// <summary> + /// Initializes a new instance of the <see cref="UdpServerEntryPoint"/> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="networkManager">The network manager.</param> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + /// <param name="httpServer">The HTTP server.</param> + public UdpServerEntryPoint(ILogger logger, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager, IHttpServer httpServer) + { + _logger = logger; + _networkManager = networkManager; + _serverConfigurationManager = serverConfigurationManager; + _httpServer = httpServer; + } + + /// <summary> + /// Runs this instance. + /// </summary> + public void Run() + { + var udpServer = new UdpServer(_logger, _networkManager, _serverConfigurationManager, _httpServer); + + try + { + udpServer.Start(PortNumber); + + UdpServer = udpServer; + } + catch (SocketException ex) + { + _logger.ErrorException("Failed to start UDP Server", ex); + } + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + Dispose(true); + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + if (UdpServer != null) + { + UdpServer.Dispose(); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs index f6547dec1..4f795fdd5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs @@ -314,6 +314,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="context">The CTX.</param> private async void ProcessHttpRequestAsync(HttpListenerContext context) { + var date = DateTime.Now; + LogHttpRequest(context); if (context.Request.IsWebSocketRequest) @@ -360,7 +362,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer var url = context.Request.Url.ToString(); var endPoint = context.Request.RemoteEndPoint; - LogResponse(context, url, endPoint); + var duration = DateTime.Now - date; + + LogResponse(context, url, endPoint, duration); } catch (Exception ex) @@ -461,14 +465,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <param name="ctx">The CTX.</param> /// <param name="url">The URL.</param> /// <param name="endPoint">The end point.</param> - private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint) + /// <param name="duration">The duration.</param> + private void LogResponse(HttpListenerContext ctx, string url, IPEndPoint endPoint, TimeSpan duration) { if (!EnableHttpRequestLogging) { return; } - var statusode = ctx.Response.StatusCode; + var statusCode = ctx.Response.StatusCode; var log = new StringBuilder(); @@ -476,7 +481,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k]))); - var msg = "Http Response Sent (" + statusode + ") to " + endPoint; + var responseTime = string.Format(". Response time: {0} ms", duration.TotalMilliseconds); + + var msg = "Response code " + statusCode + " sent to " + endPoint + responseTime; _logger.LogMultiline(msg, LogSeverity.Debug, log); } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index a5b792726..1bc3f1094 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -829,10 +829,6 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { - const int maxTasks = 3; - - var tasks = new List<Task>(); - var people = RootFolder.RecursiveChildren .SelectMany(c => c.People) .DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase) @@ -842,47 +838,27 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var person in people) { - if (tasks.Count > maxTasks) + cancellationToken.ThrowIfCancellationRequested(); + + try { - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); + var item = GetPerson(person.Name); - // Safe cancellation point, when there are no pending tasks - cancellationToken.ThrowIfCancellationRequested(); + await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); } - - // Avoid accessing the foreach variable within the closure - var currentPerson = person; - - tasks.Add(Task.Run(async () => + catch (IOException ex) { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var item = GetPerson(currentPerson.Name); - - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (IOException ex) - { - _logger.ErrorException("Error validating IBN entry {0}", ex, currentPerson.Name); - } + _logger.ErrorException("Error validating IBN entry {0}", ex, person.Name); + } - // Update progress - lock (progress) - { - numComplete++; - double percent = numComplete; - percent /= people.Count; + // Update progress + numComplete++; + double percent = numComplete; + percent /= people.Count; - progress.Report(100 * percent); - } - })); + progress.Report(100 * percent); } - await Task.WhenAll(tasks).ConfigureAwait(false); - progress.Report(100); _logger.Info("People validation complete"); @@ -956,7 +932,9 @@ namespace MediaBrowser.Server.Implementations.Library public Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken) { // Just run the scheduled task so that the user can see it - return Task.Run(() => _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>()); + _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(); + + return Task.FromResult(true); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index eb89210ff..b9e033d23 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren.OfType<Game>().ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple<Guid, List<Game>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<Game>().ToList())) .ToList(); @@ -79,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index 9a34dd1b0..e4d989c33 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren - .Where(i => !(i is IHasMusicGenres) && !(i is Game)) - .ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => !(m is IHasMusicGenres) && !(m is Game)).ToList())) .ToList(); - var allLibraryItems = allItems; - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); // Populate counts of items @@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 1b211d5f4..1edc24762 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -42,16 +42,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren - .Where(i => i is IHasMusicGenres) - .ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => m is IHasMusicGenres).ToList())) .ToList(); - var allLibraryItems = allItems; - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); // Populate counts of items @@ -84,6 +78,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index efefaeba3..dc96632f6 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -41,7 +41,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return Task.Run(() => RunInternal(progress, cancellationToken)); + RunInternal(progress, cancellationToken); + + return Task.FromResult(true); } private void RunInternal(IProgress<double> progress, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index a4d880329..05689f8e5 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -41,14 +41,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); - var userLibraries = _userManager.Users .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).ToList())) .ToList(); - var allLibraryItems = allItems; - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); // Populate counts of items @@ -81,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); } + catch (OperationCanceledException) + { + // Don't clutter the log + } catch (Exception ex) { _logger.ErrorException("Error updating counts for {0}", ex, name); diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9d2fc8c6b..f409b7205 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -13,6 +13,8 @@ <FileAlignment>512</FileAlignment> <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir> <RestorePackages>true</RestorePackages> + <ProductVersion>10.0.0</ProductVersion> + <SchemaVersion>2.0</SchemaVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -35,77 +37,65 @@ <Reference Include="Alchemy"> <HintPath>..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll</HintPath> </Reference> - <Reference Include="BdInfo, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll</HintPath> - </Reference> <Reference Include="ICSharpCode.SharpZipLib"> <HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath> </Reference> <Reference Include="Lucene.Net"> <HintPath>..\packages\Lucene.Net.3.0.3\lib\NET40\Lucene.Net.dll</HintPath> </Reference> - <Reference Include="MoreLinq, Version=1.0.16006.0, Culture=neutral, PublicKeyToken=384d532d7e88985d, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Reactive.Core"> + <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath> + </Reference> + <Reference Include="System.Reactive.Interfaces"> + <HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll</HintPath> + </Reference> + <Reference Include="System.Reactive.Linq"> + <HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath> + </Reference> + <Reference Include="System.Runtime.Serialization" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Web" /> + <Reference Include="System.Xml" /> + <Reference Include="BDInfo"> + <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.2\lib\net45\BdInfo.dll</HintPath> + </Reference> + <Reference Include="MoreLinq"> <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath> </Reference> - <Reference Include="ServiceStack, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack"> <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Api.Swagger, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.Api.Swagger"> <HintPath>..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Common, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.Common"> <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Interfaces, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.Interfaces"> <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath> </Reference> - <Reference Include="ServiceStack.OrmLite.SqlServer, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.OrmLite.SqlServer"> <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Redis, Version=3.9.43.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.Redis"> <HintPath>..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll</HintPath> </Reference> - <Reference Include="ServiceStack.ServiceInterface, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.ServiceInterface"> <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="ServiceStack.Text"> <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> </Reference> - <Reference Include="System" /> - <Reference Include="System.Core" /> - <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="System.Data.SQLite"> <HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.dll</HintPath> </Reference> - <Reference Include="System.Data.SQLite.Linq, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> - <SpecificVersion>False</SpecificVersion> + <Reference Include="System.Data.SQLite.Linq"> <HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath> </Reference> - <Reference Include="System.Drawing" /> - <Reference Include="System.Reactive.Core"> - <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath> - </Reference> - <Reference Include="System.Reactive.Interfaces"> - <HintPath>..\packages\Rx-Interfaces.2.1.30214.0\lib\Net45\System.Reactive.Interfaces.dll</HintPath> - </Reference> - <Reference Include="System.Reactive.Linq"> - <HintPath>..\packages\Rx-Linq.2.1.30214.0\lib\Net45\System.Reactive.Linq.dll</HintPath> - </Reference> - <Reference Include="System.Runtime.Serialization" /> - <Reference Include="Microsoft.CSharp" /> - <Reference Include="System.Data" /> - <Reference Include="System.Web" /> - <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> <Compile Include="..\SharedVersion.cs"> @@ -123,6 +113,7 @@ <Compile Include="EntryPoints\Notifications\RemoteNotifications.cs" /> <Compile Include="EntryPoints\Notifications\WebSocketNotifier.cs" /> <Compile Include="EntryPoints\RefreshUsersMetadata.cs" /> + <Compile Include="EntryPoints\UdpServerEntryPoint.cs" /> <Compile Include="EntryPoints\WebSocketEvents.cs" /> <Compile Include="HttpServer\HttpResultFactory.cs" /> <Compile Include="HttpServer\HttpServer.cs" /> @@ -183,6 +174,7 @@ <SubType>Code</SubType> </Compile> <Compile Include="Session\SessionWebSocketListener.cs" /> + <Compile Include="Session\WebSocketController.cs" /> <Compile Include="Sorting\AlbumArtistComparer.cs" /> <Compile Include="Sorting\AlbumComparer.cs" /> <Compile Include="Sorting\AlbumCountComparer.cs" /> @@ -222,19 +214,19 @@ </ItemGroup> <ItemGroup> <ProjectReference Include="..\MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj"> - <Project>{c4d2573a-3fd3-441f-81af-174ac4cd4e1d}</Project> + <Project>{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}</Project> <Name>MediaBrowser.Common.Implementations</Name> </ProjectReference> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> - <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> + <Project>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</Project> <Name>MediaBrowser.Common</Name> </ProjectReference> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> - <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> + <Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project> <Name>MediaBrowser.Controller</Name> </ProjectReference> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> - <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> + <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project> <Name>MediaBrowser.Model</Name> </ProjectReference> </ItemGroup> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index b24c9a5ca..2f353b8c0 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder /// <summary> /// The FF probe resource pool /// </summary> - private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(2, 2); + private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(1, 1); public string FFMpegPath { get; private set; } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index f4f5f08e4..9c5cf6f1c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -333,17 +333,16 @@ namespace MediaBrowser.Server.Implementations.Persistence /// <returns>Task.</returns> public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews) { - return Task.Run(() => + if (!Directory.Exists(_criticReviewsPath)) { - if (!Directory.Exists(_criticReviewsPath)) - { - Directory.CreateDirectory(_criticReviewsPath); - } + Directory.CreateDirectory(_criticReviewsPath); + } - var path = Path.Combine(_criticReviewsPath, itemId + ".json"); + var path = Path.Combine(_criticReviewsPath, itemId + ".json"); + + _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); - _jsonSerializer.SerializeToFile(criticReviews.ToList(), path); - }); + return Task.FromResult(true); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index 608738f7f..d8872f318 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -102,6 +102,18 @@ namespace MediaBrowser.Server.Implementations.Providers using (source) { + // If the file is currently hidden we'll have to remove that or the save will fail + var file = new FileInfo(path); + + // This will fail if the file is hidden + if (file.Exists) + { + if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + file.Attributes &= ~FileAttributes.Hidden; + } + } + using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous)) { await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index d9b88368b..4829dc405 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -159,6 +159,10 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { previouslyFailedImages = new List<string>(); } + catch (DirectoryNotFoundException) + { + previouslyFailedImages = new List<string>(); + } foreach (var video in videos) { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index ce757d142..79dfbc8a5 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Events; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -75,6 +77,12 @@ namespace MediaBrowser.Server.Implementations.Session _userRepository = userRepository; } + private List<ISessionRemoteController> _remoteControllers; + public void AddParts(IEnumerable<ISessionRemoteController> remoteControllers) + { + _remoteControllers = remoteControllers.ToList(); + } + /// <summary> /// Gets all connections. /// </summary> @@ -122,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Session var activityDate = DateTime.UtcNow; var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user); - + session.LastActivityDate = activityDate; if (user == null) @@ -207,25 +215,33 @@ namespace MediaBrowser.Server.Implementations.Session /// <summary> /// Used to report that playback has started for an item /// </summary> - /// <param name="item">The item.</param> - /// <param name="sessionId">The session id.</param> + /// <param name="info">The info.</param> /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException"></exception> - public async Task OnPlaybackStart(BaseItem item, Guid sessionId) + /// <exception cref="System.ArgumentNullException">info</exception> + public async Task OnPlaybackStart(PlaybackInfo info) { - if (item == null) + if (info == null) { - throw new ArgumentNullException(); + throw new ArgumentNullException("info"); + } + if (info.SessionId == Guid.Empty) + { + throw new ArgumentNullException("info"); } - var session = Sessions.First(i => i.Id.Equals(sessionId)); + var session = Sessions.First(i => i.Id.Equals(info.SessionId)); + + var item = info.Item; UpdateNowPlayingItem(session, item, false, false); + session.CanSeek = info.CanSeek; + session.QueueableMediaTypes = info.QueueableMediaTypes; + var key = item.GetUserDataKey(); var user = session.User; - + var data = _userDataRepository.GetUserData(user.Id, key); data.PlayCount++; @@ -313,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.Session { throw new ArgumentOutOfRangeException("positionTicks"); } - + var session = Sessions.First(i => i.Id.Equals(sessionId)); RemoveNowPlayingItem(session, item); @@ -321,7 +337,7 @@ namespace MediaBrowser.Server.Implementations.Session var key = item.GetUserDataKey(); var user = session.User; - + var data = _userDataRepository.GetUserData(user.Id, key); if (positionTicks.HasValue) @@ -400,5 +416,118 @@ namespace MediaBrowser.Server.Implementations.Session data.PlaybackPositionTicks = positionTicks; } + + /// <summary> + /// Gets the session for remote control. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <returns>SessionInfo.</returns> + /// <exception cref="ResourceNotFoundException"></exception> + private SessionInfo GetSessionForRemoteControl(Guid sessionId) + { + var session = Sessions.First(i => i.Id.Equals(sessionId)); + + if (session == null) + { + throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); + } + + if (!session.SupportsRemoteControl) + { + throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id)); + } + + return session; + } + + /// <summary> + /// Gets the controllers. + /// </summary> + /// <param name="session">The session.</param> + /// <returns>IEnumerable{ISessionRemoteController}.</returns> + private IEnumerable<ISessionRemoteController> GetControllers(SessionInfo session) + { + return _remoteControllers.Where(i => i.Supports(session)); + } + + /// <summary> + /// Sends the system command. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendSystemCommand(Guid sessionId, SystemCommand command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendSystemCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// <summary> + /// Sends the message command. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendMessageCommand(Guid sessionId, MessageCommand command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendMessageCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// <summary> + /// Sends the play command. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendPlayCommand(Guid sessionId, PlayRequest command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendPlayCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// <summary> + /// Sends the browse command. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendBrowseCommand(Guid sessionId, BrowseRequest command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendBrowseCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } + + /// <summary> + /// Sends the playstate command. + /// </summary> + /// <param name="sessionId">The session id.</param> + /// <param name="command">The command.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task SendPlaystateCommand(Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken) + { + var session = GetSessionForRemoteControl(sessionId); + + var tasks = GetControllers(session).Select(i => i.SendPlaystateCommand(session, command, cancellationToken)); + + return Task.WhenAll(tasks); + } } } diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index 2a4361e61..95eb5948f 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -101,16 +101,7 @@ namespace MediaBrowser.Server.Implementations.Session } else if (string.Equals(message.MessageType, "PlaybackStart", StringComparison.OrdinalIgnoreCase)) { - _logger.Debug("Received PlaybackStart message"); - - var session = _sessionManager.Sessions.FirstOrDefault(i => i.WebSockets.Contains(message.Connection)); - - if (session != null && session.User != null) - { - var item = _dtoService.GetItemByDtoId(message.Data); - - _sessionManager.OnPlaybackStart(item, session.Id); - } + ReportPlaybackStart(message); } else if (string.Equals(message.MessageType, "PlaybackProgress", StringComparison.OrdinalIgnoreCase)) { @@ -170,5 +161,46 @@ namespace MediaBrowser.Server.Implementations.Session return _trueTaskResult; } + + /// <summary> + /// Reports the playback start. + /// </summary> + /// <param name="message">The message.</param> + private void ReportPlaybackStart(WebSocketMessageInfo message) + { + _logger.Debug("Received PlaybackStart message"); + + var session = _sessionManager.Sessions + .FirstOrDefault(i => i.WebSockets.Contains(message.Connection)); + + if (session != null && session.User != null) + { + var vals = message.Data.Split('|'); + + var item = _dtoService.GetItemByDtoId(vals[0]); + + var queueableMediaTypes = string.Empty; + var canSeek = true; + + if (vals.Length > 1) + { + canSeek = string.Equals(vals[1], "true", StringComparison.OrdinalIgnoreCase); + } + if (vals.Length > 2) + { + queueableMediaTypes = vals[2]; + } + + var info = new PlaybackInfo + { + CanSeek = canSeek, + Item = item, + SessionId = session.Id, + QueueableMediaTypes = queueableMediaTypes.Split(',').ToList() + }; + + _sessionManager.OnPlaybackStart(info); + } + } } } diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs new file mode 100644 index 000000000..6915cfc64 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -0,0 +1,92 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Session +{ + public class WebSocketController : ISessionRemoteController + { + public bool Supports(SessionInfo session) + { + return session.WebSockets.Any(i => i.State == WebSocketState.Open); + } + + private IWebSocketConnection GetSocket(SessionInfo session) + { + var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open); + + + if (socket == null) + { + throw new InvalidOperationException("The requested session does not have an open web socket."); + } + + return socket; + } + + public Task SendSystemCommand(SessionInfo session, SystemCommand command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage<string> + { + MessageType = "SystemCommand", + Data = command.ToString() + + }, cancellationToken); + } + + public Task SendMessageCommand(SessionInfo session, MessageCommand command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage<MessageCommand> + { + MessageType = "MessageCommand", + Data = command + + }, cancellationToken); + } + + public Task SendPlayCommand(SessionInfo session, PlayRequest command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage<PlayRequest> + { + MessageType = "Play", + Data = command + + }, cancellationToken); + } + + public Task SendBrowseCommand(SessionInfo session, BrowseRequest command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage<BrowseRequest> + { + MessageType = "Browse", + Data = command + + }, cancellationToken); + } + + public Task SendPlaystateCommand(SessionInfo session, PlaystateRequest command, CancellationToken cancellationToken) + { + var socket = GetSocket(session); + + return socket.SendAsync(new WebSocketMessage<PlaystateRequest> + { + MessageType = "Playstate", + Data = command + + }, cancellationToken); + } + } +} diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs index 958201625..de998254c 100644 --- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs +++ b/MediaBrowser.Server.Implementations/WebSocket/AlchemyWebSocket.cs @@ -92,7 +92,9 @@ namespace MediaBrowser.Server.Implementations.WebSocket /// <returns>Task.</returns> public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken) { - return Task.Run(() => UserContext.Send(bytes)); + UserContext.Send(bytes); + + return Task.FromResult(true); } /// <summary> |
