fix duplicate connections on the dashboard
This commit is contained in:
parent
110ee00517
commit
bae89ee824
|
@ -252,30 +252,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
_logger.Info("HttpClientManager.GetTempFile url: {0}, temp file: {1}", options.Url, tempFile);
|
_logger.Info("HttpClientManager.GetTempFile url: {0}, temp file: {1}", options.Url, tempFile);
|
||||||
|
|
||||||
FileStream tempFileStream;
|
|
||||||
|
|
||||||
if (resumeCount > 0 && File.Exists(tempFile))
|
|
||||||
{
|
|
||||||
tempFileStream = new FileStream(tempFile, FileMode.Open, FileAccess.Write, FileShare.Read,
|
|
||||||
StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
|
|
||||||
|
|
||||||
var startPosition = tempFileStream.Length;
|
|
||||||
tempFileStream.Seek(startPosition, SeekOrigin.Current);
|
|
||||||
|
|
||||||
message.Headers.Range = new RangeHeaderValue(startPosition, null);
|
|
||||||
|
|
||||||
_logger.Info("Resuming from position {1} for {0}", options.Url, startPosition);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tempFileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read,
|
|
||||||
StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverSupportsRangeRequests = false;
|
|
||||||
|
|
||||||
Exception downloadException = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
options.CancellationToken.ThrowIfCancellationRequested();
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
@ -286,15 +262,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
options.CancellationToken.ThrowIfCancellationRequested();
|
options.CancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var rangeValue = string.Join(" ", response.Headers.AcceptRanges.ToArray());
|
|
||||||
serverSupportsRangeRequests = rangeValue.IndexOf("bytes", StringComparison.OrdinalIgnoreCase) != -1 || rangeValue.IndexOf("*", StringComparison.OrdinalIgnoreCase) != -1;
|
|
||||||
|
|
||||||
if (!serverSupportsRangeRequests && resumeCount > 0)
|
|
||||||
{
|
|
||||||
_logger.Info("Server does not support range requests for {0}", options.Url);
|
|
||||||
tempFileStream.Position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<string> lengthValues;
|
IEnumerable<string> lengthValues;
|
||||||
|
|
||||||
if (!response.Headers.TryGetValues("content-length", out lengthValues) &&
|
if (!response.Headers.TryGetValues("content-length", out lengthValues) &&
|
||||||
|
@ -303,7 +270,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
// We're not able to track progress
|
// We're not able to track progress
|
||||||
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(tempFileStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
|
||||||
|
{
|
||||||
|
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -312,7 +282,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, length))
|
using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, length))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(tempFileStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
|
||||||
|
{
|
||||||
|
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,23 +296,16 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
downloadException = ex;
|
HandleTempFileException(ex, options, tempFile);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
tempFileStream.Dispose();
|
|
||||||
|
|
||||||
if (options.ResourcePool != null)
|
if (options.ResourcePool != null)
|
||||||
{
|
{
|
||||||
options.ResourcePool.Release();
|
options.ResourcePool.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadException != null)
|
|
||||||
{
|
|
||||||
await HandleTempFileException(downloadException, options, tempFile, serverSupportsRangeRequests, resumeCount).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tempFile;
|
return tempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,11 +315,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
/// <param name="ex">The ex.</param>
|
/// <param name="ex">The ex.</param>
|
||||||
/// <param name="options">The options.</param>
|
/// <param name="options">The options.</param>
|
||||||
/// <param name="tempFile">The temp file.</param>
|
/// <param name="tempFile">The temp file.</param>
|
||||||
/// <param name="serverSupportsRangeRequests">if set to <c>true</c> [server supports range requests].</param>
|
|
||||||
/// <param name="resumeCount">The resume count.</param>
|
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="HttpException"></exception>
|
/// <exception cref="HttpException"></exception>
|
||||||
private Task HandleTempFileException(Exception ex, HttpRequestOptions options, string tempFile, bool serverSupportsRangeRequests, int resumeCount)
|
private void HandleTempFileException(Exception ex, HttpRequestOptions options, string tempFile)
|
||||||
{
|
{
|
||||||
var operationCanceledException = ex as OperationCanceledException;
|
var operationCanceledException = ex as OperationCanceledException;
|
||||||
|
|
||||||
|
@ -375,14 +339,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
// Cleanup
|
// Cleanup
|
||||||
if (File.Exists(tempFile))
|
if (File.Exists(tempFile))
|
||||||
{
|
{
|
||||||
// Try to resume
|
|
||||||
if (httpRequestException != null && serverSupportsRangeRequests && resumeCount < options.MaxResumeCount && new FileInfo(tempFile).Length > 0)
|
|
||||||
{
|
|
||||||
_logger.Info("Attempting to resume download from {0}", options.Url);
|
|
||||||
|
|
||||||
return GetTempFile(options, tempFile, resumeCount + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.Delete(tempFile);
|
File.Delete(tempFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||||
{
|
{
|
||||||
Url = package.sourceUrl,
|
Url = package.sourceUrl,
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
Progress = progress,
|
Progress = progress
|
||||||
MaxResumeCount = 3
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,6 @@ namespace MediaBrowser.Common.Net
|
||||||
/// <value>The user agent.</value>
|
/// <value>The user agent.</value>
|
||||||
public string UserAgent { get; set; }
|
public string UserAgent { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the max resume count.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The max resume count.</value>
|
|
||||||
public int MaxResumeCount { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the progress.
|
/// Gets or sets the progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -64,6 +64,39 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
return encoders.FirstOrDefault(i => i.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase)) ?? encoders.FirstOrDefault();
|
return encoders.FirstOrDefault(i => i.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase)) ?? encoders.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether [is pixel format supported by graphics object] [the specified format].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="format">The format.</param>
|
||||||
|
/// <returns><c>true</c> if [is pixel format supported by graphics object] [the specified format]; otherwise, <c>false</c>.</returns>
|
||||||
|
public static bool IsPixelFormatSupportedByGraphicsObject(PixelFormat format)
|
||||||
|
{
|
||||||
|
// http://msdn.microsoft.com/en-us/library/system.drawing.graphics.fromimage.aspx
|
||||||
|
|
||||||
|
if (format.HasFlag(PixelFormat.Indexed))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (format.HasFlag(PixelFormat.Undefined))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (format.HasFlag(PixelFormat.DontCare))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (format.HasFlag(PixelFormat.Format16bppArgb1555))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (format.HasFlag(PixelFormat.Format16bppGrayScale))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Crops an image by removing whitespace and transparency from the edges
|
/// Crops an image by removing whitespace and transparency from the edges
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -179,7 +179,7 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
var newHeight = Convert.ToInt32(newSize.Height);
|
var newHeight = Convert.ToInt32(newSize.Height);
|
||||||
|
|
||||||
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
|
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
|
||||||
var thumbnail = originalImage.PixelFormat.HasFlag(PixelFormat.Indexed) ? new Bitmap(originalImage, newWidth, newHeight) : new Bitmap(newWidth, newHeight, originalImage.PixelFormat);
|
var thumbnail = !ImageExtensions.IsPixelFormatSupportedByGraphicsObject(originalImage.PixelFormat) ? new Bitmap(originalImage, newWidth, newHeight) : new Bitmap(newWidth, newHeight, originalImage.PixelFormat);
|
||||||
|
|
||||||
// Preserve the original resolution
|
// Preserve the original resolution
|
||||||
thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
|
thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// Gets the active connections.
|
/// Gets the active connections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The active connections.</value>
|
/// <value>The active connections.</value>
|
||||||
IEnumerable<ClientConnectionInfo> ConnectedUsers { get; }
|
IEnumerable<ClientConnectionInfo> RecentConnections { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when [playback start].
|
/// Occurs when [playback start].
|
||||||
|
|
|
@ -25,8 +25,8 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _active connections
|
/// The _active connections
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentBag<ClientConnectionInfo> _activeConnections =
|
private readonly List<ClientConnectionInfo> _activeConnections =
|
||||||
new ConcurrentBag<ClientConnectionInfo>();
|
new List<ClientConnectionInfo>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _users
|
/// The _users
|
||||||
|
@ -67,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
/// Gets all connections.
|
/// Gets all connections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>All connections.</value>
|
/// <value>All connections.</value>
|
||||||
private IEnumerable<ClientConnectionInfo> AllConnections
|
public IEnumerable<ClientConnectionInfo> AllConnections
|
||||||
{
|
{
|
||||||
get { return _activeConnections.Where(c => GetUserById(c.UserId) != null).OrderByDescending(c => c.LastActivityDate); }
|
get { return _activeConnections.Where(c => GetUserById(c.UserId) != null).OrderByDescending(c => c.LastActivityDate); }
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
/// Gets the active connections.
|
/// Gets the active connections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The active connections.</value>
|
/// <value>The active connections.</value>
|
||||||
public IEnumerable<ClientConnectionInfo> ConnectedUsers
|
public IEnumerable<ClientConnectionInfo> RecentConnections
|
||||||
{
|
{
|
||||||
get { return AllConnections.Where(c => (DateTime.UtcNow - c.LastActivityDate).TotalMinutes <= 10); }
|
get { return AllConnections.Where(c => (DateTime.UtcNow - c.LastActivityDate).TotalMinutes <= 10); }
|
||||||
}
|
}
|
||||||
|
@ -303,22 +303,29 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
/// <returns>ClientConnectionInfo.</returns>
|
/// <returns>ClientConnectionInfo.</returns>
|
||||||
private ClientConnectionInfo GetConnection(Guid userId, ClientType clientType, string deviceId, string deviceName)
|
private ClientConnectionInfo GetConnection(Guid userId, ClientType clientType, string deviceId, string deviceName)
|
||||||
{
|
{
|
||||||
var conn = _activeConnections.FirstOrDefault(c => c.UserId == userId && c.ClientType == clientType && string.Equals(deviceId, c.DeviceId));
|
lock (_activeConnections)
|
||||||
|
|
||||||
if (conn == null)
|
|
||||||
{
|
{
|
||||||
conn = new ClientConnectionInfo
|
var conn = _activeConnections.FirstOrDefault(c => c.ClientType == clientType && string.Equals(deviceId, c.DeviceId));
|
||||||
|
|
||||||
|
if (conn == null)
|
||||||
{
|
{
|
||||||
UserId = userId,
|
conn = new ClientConnectionInfo
|
||||||
ClientType = clientType,
|
{
|
||||||
DeviceName = deviceName,
|
UserId = userId,
|
||||||
DeviceId = deviceId
|
ClientType = clientType,
|
||||||
};
|
DeviceName = deviceName,
|
||||||
|
DeviceId = deviceId
|
||||||
|
};
|
||||||
|
|
||||||
_activeConnections.Add(conn);
|
_activeConnections.Add(conn);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
conn.UserId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -375,8 +375,10 @@ namespace MediaBrowser.Server.Implementations.Updates
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.ErrorException("Package installation failed", ex);
|
||||||
|
|
||||||
lock (CurrentInstallations)
|
lock (CurrentInstallations)
|
||||||
{
|
{
|
||||||
CurrentInstallations.Remove(tuple);
|
CurrentInstallations.Remove(tuple);
|
||||||
|
|
|
@ -125,7 +125,7 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
/// <returns>DashboardInfo.</returns>
|
/// <returns>DashboardInfo.</returns>
|
||||||
public static async Task<DashboardInfo> GetDashboardInfo(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager)
|
public static async Task<DashboardInfo> GetDashboardInfo(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, IUserManager userManager, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
var connections = userManager.ConnectedUsers.ToArray();
|
var connections = userManager.RecentConnections.ToArray();
|
||||||
|
|
||||||
var dtoBuilder = new DtoBuilder(logger, libraryManager);
|
var dtoBuilder = new DtoBuilder(logger, libraryManager);
|
||||||
|
|
||||||
|
|
|
@ -1387,7 +1387,7 @@ $(document).ajaxSend(function (event, jqXHR) {
|
||||||
|
|
||||||
if (ApiClient.currentUserId) {
|
if (ApiClient.currentUserId) {
|
||||||
|
|
||||||
var auth = 'MediaBrowser UserId="' + ApiClient.currentUserId + '", Client="Dashboard", Device="' + ApiClient.getDeviceName() + '", DeviceId="' + ApiClient.getDeviceName() + '"';
|
var auth = 'MediaBrowser UserId="' + ApiClient.currentUserId + '", Client="Dashboard", Device="' + ApiClient.getDeviceName() + '", DeviceId="' + ApiClient.getDeviceId() + '"';
|
||||||
jqXHR.setRequestHeader("Authorization", auth);
|
jqXHR.setRequestHeader("Authorization", auth);
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common.Internal</id>
|
<id>MediaBrowser.Common.Internal</id>
|
||||||
<version>3.0.48</version>
|
<version>3.0.49</version>
|
||||||
<title>MediaBrowser.Common.Internal</title>
|
<title>MediaBrowser.Common.Internal</title>
|
||||||
<authors>Luke</authors>
|
<authors>Luke</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<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>
|
<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>
|
<copyright>Copyright © Media Browser 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.48" />
|
<dependency id="MediaBrowser.Common" version="3.0.49" />
|
||||||
<dependency id="NLog" version="2.0.0.2000" />
|
<dependency id="NLog" version="2.0.0.2000" />
|
||||||
<dependency id="ServiceStack.Text" version="3.9.38" />
|
<dependency id="ServiceStack.Text" version="3.9.38" />
|
||||||
<dependency id="protobuf-net" version="2.0.0.621" />
|
<dependency id="protobuf-net" version="2.0.0.621" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common</id>
|
<id>MediaBrowser.Common</id>
|
||||||
<version>3.0.48</version>
|
<version>3.0.49</version>
|
||||||
<title>MediaBrowser.Common</title>
|
<title>MediaBrowser.Common</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Server.Core</id>
|
<id>MediaBrowser.Server.Core</id>
|
||||||
<version>3.0.48</version>
|
<version>3.0.49</version>
|
||||||
<title>Media Browser.Server.Core</title>
|
<title>Media Browser.Server.Core</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Media Browser 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.48" />
|
<dependency id="MediaBrowser.Common" version="3.0.49" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user