mirror of https://github.com/jellyfin/jellyfin.git
Merge branch 'beta'
This commit is contained in:
commit
f57ce51d12
|
@ -163,7 +163,7 @@ namespace Emby.Drawing
|
||||||
return _imageEncoder.SupportedOutputFormats;
|
return _imageEncoder.SupportedOutputFormats;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options)
|
public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
|
@ -178,14 +178,13 @@ namespace Emby.Drawing
|
||||||
}
|
}
|
||||||
|
|
||||||
var originalImagePath = originalImage.Path;
|
var originalImagePath = originalImage.Path;
|
||||||
|
var dateModified = originalImage.DateModified;
|
||||||
|
|
||||||
if (!_imageEncoder.SupportsImageEncoding)
|
if (!_imageEncoder.SupportsImageEncoding)
|
||||||
{
|
{
|
||||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dateModified = originalImage.DateModified;
|
|
||||||
|
|
||||||
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
|
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
|
||||||
{
|
{
|
||||||
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||||
|
@ -211,7 +210,7 @@ namespace Emby.Drawing
|
||||||
if (options.HasDefaultOptions(originalImagePath))
|
if (options.HasDefaultOptions(originalImagePath))
|
||||||
{
|
{
|
||||||
// Just spit out the original file if all the options are default
|
// Just spit out the original file if all the options are default
|
||||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageSize? originalImageSize;
|
ImageSize? originalImageSize;
|
||||||
|
@ -221,7 +220,7 @@ namespace Emby.Drawing
|
||||||
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
|
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
|
||||||
{
|
{
|
||||||
// Just spit out the original file if all the options are default
|
// Just spit out the original file if all the options are default
|
||||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -235,10 +234,6 @@ namespace Emby.Drawing
|
||||||
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
|
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
|
||||||
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
|
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
|
||||||
|
|
||||||
var semaphore = GetLock(cacheFilePath);
|
|
||||||
|
|
||||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
var imageProcessingLockTaken = false;
|
var imageProcessingLockTaken = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -251,15 +246,20 @@ namespace Emby.Drawing
|
||||||
var newHeight = Convert.ToInt32(newSize.Height);
|
var newHeight = Convert.ToInt32(newSize.Height);
|
||||||
|
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||||
|
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||||
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
|
||||||
|
|
||||||
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
|
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
imageProcessingLockTaken = true;
|
imageProcessingLockTaken = true;
|
||||||
|
|
||||||
_imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
|
_imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
|
||||||
|
CopyFile(tmpPath, cacheFilePath);
|
||||||
|
|
||||||
|
return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath));
|
return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -267,7 +267,7 @@ namespace Emby.Drawing
|
||||||
_logger.ErrorException("Error encoding image", ex);
|
_logger.ErrorException("Error encoding image", ex);
|
||||||
|
|
||||||
// Just spit out the original file if all the options are default
|
// Just spit out the original file if all the options are default
|
||||||
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
|
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -275,8 +275,18 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
_imageProcessingSemaphore.Release();
|
_imageProcessingSemaphore.Release();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyFile(string src, string destination)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Copy(src, destination, true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,14 +422,9 @@ namespace Emby.Drawing
|
||||||
|
|
||||||
var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
|
var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
|
||||||
|
|
||||||
var semaphore = GetLock(croppedImagePath);
|
|
||||||
|
|
||||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Check again in case of contention
|
// Check again in case of contention
|
||||||
if (_fileSystem.FileExists(croppedImagePath))
|
if (_fileSystem.FileExists(croppedImagePath))
|
||||||
{
|
{
|
||||||
semaphore.Release();
|
|
||||||
return GetResult(croppedImagePath);
|
return GetResult(croppedImagePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,11 +433,15 @@ namespace Emby.Drawing
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
|
||||||
|
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||||
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
|
||||||
|
|
||||||
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
|
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
imageProcessingLockTaken = true;
|
imageProcessingLockTaken = true;
|
||||||
|
|
||||||
_imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
|
_imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
|
||||||
|
CopyFile(tmpPath, croppedImagePath);
|
||||||
|
return GetResult(tmpPath);
|
||||||
}
|
}
|
||||||
catch (NotImplementedException)
|
catch (NotImplementedException)
|
||||||
{
|
{
|
||||||
|
@ -452,11 +461,7 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
_imageProcessingSemaphore.Release();
|
_imageProcessingSemaphore.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
semaphore.Release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetResult(croppedImagePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tuple<string, DateTime> GetResult(string path)
|
private Tuple<string, DateTime> GetResult(string path)
|
||||||
|
|
|
@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
|
||||||
{
|
{
|
||||||
var list = new List<ImageInfo>();
|
var list = new List<ImageInfo>();
|
||||||
|
|
||||||
foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
|
var itemImages = item.ImageInfos;
|
||||||
|
|
||||||
|
foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
|
||||||
{
|
{
|
||||||
var info = GetImageInfo(item, image, null);
|
var info = GetImageInfo(item, image, null);
|
||||||
|
|
||||||
|
@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
|
foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
|
||||||
{
|
{
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
// Prevent implicitly captured closure
|
// Prevent implicitly captured closure
|
||||||
var currentImageType = imageType;
|
var currentImageType = imageType;
|
||||||
|
|
||||||
foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
|
foreach (var image in itemImages.Where(i => i.Type == currentImageType))
|
||||||
{
|
{
|
||||||
var info = GetImageInfo(item, image, index);
|
var info = GetImageInfo(item, image, index);
|
||||||
|
|
||||||
|
@ -636,6 +638,7 @@ namespace MediaBrowser.Api.Images
|
||||||
CacheDuration = cacheDuration,
|
CacheDuration = cacheDuration,
|
||||||
ResponseHeaders = headers,
|
ResponseHeaders = headers,
|
||||||
ContentType = imageResult.Item2,
|
ContentType = imageResult.Item2,
|
||||||
|
DateLastModified = imageResult.Item3,
|
||||||
IsHeadRequest = isHeadRequest,
|
IsHeadRequest = isHeadRequest,
|
||||||
Path = imageResult.Item1,
|
Path = imageResult.Item1,
|
||||||
|
|
||||||
|
|
|
@ -286,19 +286,25 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
protected string GetH264Encoder(StreamState state)
|
protected string GetH264Encoder(StreamState state)
|
||||||
{
|
{
|
||||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
// Only use alternative encoders for video files.
|
||||||
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||||
|
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
||||||
|
if (state.VideoType == VideoType.VideoFile)
|
||||||
{
|
{
|
||||||
return "h264_qsv";
|
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||||
}
|
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return "h264_qsv";
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "h264_nvenc";
|
return "h264_nvenc";
|
||||||
}
|
}
|
||||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "h264_omx";
|
return "h264_omx";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "libx264";
|
return "libx264";
|
||||||
|
@ -843,6 +849,14 @@ namespace MediaBrowser.Api.Playback
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only use alternative encoders for video files.
|
||||||
|
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||||
|
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
||||||
|
if (state.VideoType != VideoType.VideoFile)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
||||||
{
|
{
|
||||||
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
@ -13,6 +13,7 @@ using ServiceStack.Web;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
@ -336,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
|
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
result.Options["Content-Type"] = contentType;
|
outputHeaders["Content-Type"] = contentType;
|
||||||
|
|
||||||
// Add the response headers to the result object
|
// Add the response headers to the result object
|
||||||
foreach (var item in responseHeaders)
|
foreach (var item in responseHeaders)
|
||||||
{
|
{
|
||||||
result.Options[item.Key] = item.Value;
|
outputHeaders[item.Key] = item.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream);
|
||||||
|
|
||||||
|
return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
@ -48,21 +48,19 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
public void WriteTo(Stream responseStream)
|
public void WriteTo(Stream responseStream)
|
||||||
{
|
{
|
||||||
WriteToInternal(responseStream);
|
var task = WriteToAsync(responseStream);
|
||||||
|
Task.WaitAll(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes to async.
|
/// Writes to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
/// <returns>Task.</returns>
|
public async Task WriteToAsync(Stream responseStream)
|
||||||
private void WriteToInternal(Stream responseStream)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream);
|
await new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream).ConfigureAwait(false);
|
||||||
|
|
||||||
Task.WaitAll(task);
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
@ -110,11 +108,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
var eofCount = 0;
|
var eofCount = 0;
|
||||||
long position = 0;
|
long position = 0;
|
||||||
|
|
||||||
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false))
|
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||||
{
|
{
|
||||||
while (eofCount < 15)
|
while (eofCount < 15)
|
||||||
{
|
{
|
||||||
CopyToInternal(fs, outputStream, BufferSize);
|
await CopyToInternal(fs, outputStream, BufferSize).ConfigureAwait(false);
|
||||||
|
|
||||||
var fsPosition = fs.Position;
|
var fsPosition = fs.Position;
|
||||||
|
|
||||||
|
@ -140,11 +138,11 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyToInternal(Stream source, Stream destination, int bufferSize)
|
private async Task CopyToInternal(Stream source, Stream destination, int bufferSize)
|
||||||
{
|
{
|
||||||
var array = new byte[bufferSize];
|
var array = new byte[bufferSize];
|
||||||
int count;
|
int count;
|
||||||
while ((count = source.Read(array, 0, array.Length)) != 0)
|
while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
//if (_job != null)
|
//if (_job != null)
|
||||||
//{
|
//{
|
||||||
|
@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
destination.Write(array, 0, count);
|
await destination.WriteAsync(array, 0, count).ConfigureAwait(false);
|
||||||
|
|
||||||
_bytesWritten += count;
|
_bytesWritten += count;
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
|
||||||
|
|
||||||
/// <summary> Manager for library. </summary>
|
/// <summary> Manager for library. </summary>
|
||||||
private readonly ILibraryManager _libraryManager; ///< Manager for library
|
private readonly ILibraryManager _libraryManager; ///< Manager for library
|
||||||
/// <summary> The localization. </summary>
|
/// <summary> The localization. </summary>
|
||||||
|
|
||||||
private readonly ILocalizationManager _localization; ///< The localization
|
private readonly ILocalizationManager _localization; ///< The localization
|
||||||
|
|
||||||
|
@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
|
||||||
/// <summary> Gets the given request. </summary>
|
/// <summary> Gets the given request. </summary>
|
||||||
/// <param name="request"> The request. </param>
|
/// <param name="request"> The request. </param>
|
||||||
/// <returns> A Task<object> </returns>
|
/// <returns> A Task<object> </returns>
|
||||||
public async Task<object> Get(GetActivityLogs request)
|
public object Get(GetActivityLogs request)
|
||||||
{
|
{
|
||||||
request.DisplayType = "Screen";
|
request.DisplayType = "Screen";
|
||||||
ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
|
ReportResult result = GetReportActivities(request);
|
||||||
return ToOptimizedResult(result);
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
request.DisplayType = "Screen";
|
request.DisplayType = "Screen";
|
||||||
var reportResult = await GetReportResult(request);
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||||
|
var reportResult = await GetReportResult(request, user);
|
||||||
|
|
||||||
return ToOptimizedResult(reportResult);
|
return ToOptimizedResult(reportResult);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
|
||||||
if (string.IsNullOrEmpty(request.IncludeItemTypes))
|
if (string.IsNullOrEmpty(request.IncludeItemTypes))
|
||||||
return null;
|
return null;
|
||||||
request.DisplayType = "Screen";
|
request.DisplayType = "Screen";
|
||||||
var reportResult = await GetReportStatistic(request);
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||||
|
var reportResult = await GetReportStatistic(request, user);
|
||||||
|
|
||||||
return ToOptimizedResult(reportResult);
|
return ToOptimizedResult(reportResult);
|
||||||
}
|
}
|
||||||
|
@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
|
||||||
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
|
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
|
||||||
headers["Content-Encoding"] = "UTF-8";
|
headers["Content-Encoding"] = "UTF-8";
|
||||||
|
|
||||||
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||||
ReportResult result = null;
|
ReportResult result = null;
|
||||||
switch (reportViewType)
|
switch (reportViewType)
|
||||||
{
|
{
|
||||||
|
@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
|
||||||
case ReportViewType.ReportData:
|
case ReportViewType.ReportData:
|
||||||
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
|
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
|
||||||
ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
|
ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
|
||||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
|
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||||
result = dataBuilder.GetResult(queryResult.Items, request);
|
result = dataBuilder.GetResult(queryResult.Items, request);
|
||||||
result.TotalRecordCount = queryResult.TotalRecordCount;
|
result.TotalRecordCount = queryResult.TotalRecordCount;
|
||||||
break;
|
break;
|
||||||
case ReportViewType.ReportActivities:
|
case ReportViewType.ReportActivities:
|
||||||
result = await GetReportActivities(request).ConfigureAwait(false);
|
result = GetReportActivities(request);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
object ro = ResultFactory.GetResult(returnResult, contentType, headers);
|
return ResultFactory.GetResult(returnResult, contentType, headers);
|
||||||
return ro;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region [Private Methods]
|
|
||||||
|
|
||||||
/// <summary> Gets items query. </summary>
|
|
||||||
/// <param name="request"> The request. </param>
|
|
||||||
/// <param name="user"> The user. </param>
|
|
||||||
/// <returns> The items query. </returns>
|
|
||||||
private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
|
private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
|
||||||
{
|
{
|
||||||
var query = new InternalItemsQuery
|
var query = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
User = user,
|
|
||||||
IsPlayed = request.IsPlayed,
|
IsPlayed = request.IsPlayed,
|
||||||
MediaTypes = request.GetMediaTypes(),
|
MediaTypes = request.GetMediaTypes(),
|
||||||
IncludeItemTypes = request.GetIncludeItemTypes(),
|
IncludeItemTypes = request.GetIncludeItemTypes(),
|
||||||
|
@ -231,6 +226,7 @@ namespace MediaBrowser.Api.Reports
|
||||||
Tags = request.GetTags(),
|
Tags = request.GetTags(),
|
||||||
OfficialRatings = request.GetOfficialRatings(),
|
OfficialRatings = request.GetOfficialRatings(),
|
||||||
Genres = request.GetGenres(),
|
Genres = request.GetGenres(),
|
||||||
|
GenreIds = request.GetGenreIds(),
|
||||||
Studios = request.GetStudios(),
|
Studios = request.GetStudios(),
|
||||||
StudioIds = request.GetStudioIds(),
|
StudioIds = request.GetStudioIds(),
|
||||||
Person = request.Person,
|
Person = request.Person,
|
||||||
|
@ -245,9 +241,11 @@ namespace MediaBrowser.Api.Reports
|
||||||
MaxPlayers = request.MaxPlayers,
|
MaxPlayers = request.MaxPlayers,
|
||||||
MinCommunityRating = request.MinCommunityRating,
|
MinCommunityRating = request.MinCommunityRating,
|
||||||
MinCriticRating = request.MinCriticRating,
|
MinCriticRating = request.MinCriticRating,
|
||||||
|
ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
|
||||||
ParentIndexNumber = request.ParentIndexNumber,
|
ParentIndexNumber = request.ParentIndexNumber,
|
||||||
AiredDuringSeason = request.AiredDuringSeason,
|
AiredDuringSeason = request.AiredDuringSeason,
|
||||||
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
|
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
|
||||||
|
EnableTotalRecordCount = request.EnableTotalRecordCount
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.Ids))
|
if (!string.IsNullOrWhiteSpace(request.Ids))
|
||||||
|
@ -357,98 +355,111 @@ namespace MediaBrowser.Api.Reports
|
||||||
query.AlbumNames = request.Albums.Split('|');
|
query.AlbumNames = request.Albums.Split('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.HasQueryLimit == false)
|
|
||||||
{
|
|
||||||
query.StartIndex = null;
|
|
||||||
query.Limit = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets query result. </summary>
|
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
|
||||||
/// <param name="request"> The request. </param>
|
|
||||||
/// <returns> The query result. </returns>
|
|
||||||
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
|
|
||||||
{
|
{
|
||||||
// Placeholder in case needed later
|
// all report queries currently need this because it's not being specified
|
||||||
request.Recursive = true;
|
request.Recursive = true;
|
||||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
|
||||||
request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
|
|
||||||
|
|
||||||
var parentItem = string.IsNullOrEmpty(request.ParentId) ?
|
|
||||||
(user == null ? _libraryManager.RootFolder : user.RootFolder) :
|
|
||||||
_libraryManager.GetItemById(request.ParentId);
|
|
||||||
|
|
||||||
var item = string.IsNullOrEmpty(request.ParentId) ?
|
var item = string.IsNullOrEmpty(request.ParentId) ?
|
||||||
user == null ? _libraryManager.RootFolder : user.RootFolder :
|
user == null ? _libraryManager.RootFolder : user.RootFolder :
|
||||||
parentItem;
|
_libraryManager.GetItemById(request.ParentId);
|
||||||
|
|
||||||
IEnumerable<BaseItem> items;
|
if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
//item = user == null ? _libraryManager.RootFolder : user.RootFolder;
|
||||||
|
}
|
||||||
|
else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
item = user == null ? _libraryManager.RootFolder : user.RootFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default list type = children
|
||||||
|
|
||||||
|
var folder = item as Folder;
|
||||||
|
if (folder == null)
|
||||||
|
{
|
||||||
|
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.Ids))
|
||||||
|
{
|
||||||
|
request.Recursive = true;
|
||||||
|
var query = GetItemsQuery(request, user);
|
||||||
|
var result = await folder.GetItems(query).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.SortBy))
|
||||||
|
{
|
||||||
|
var ids = query.ItemIds.ToList();
|
||||||
|
|
||||||
|
// Try to preserve order
|
||||||
|
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.Recursive)
|
if (request.Recursive)
|
||||||
{
|
{
|
||||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (user == null)
|
||||||
{
|
{
|
||||||
if (user == null)
|
return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
||||||
{
|
|
||||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
var userRoot = item as UserRootFolder;
|
|
||||||
|
|
||||||
if (userRoot == null)
|
|
||||||
{
|
|
||||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
items = ((Folder)item).GetChildren(user, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<BaseItem> { Items = items.ToArray() };
|
var userRoot = item as UserRootFolder;
|
||||||
|
|
||||||
|
if (userRoot == null)
|
||||||
|
{
|
||||||
|
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<BaseItem> items = folder.GetChildren(user, true);
|
||||||
|
|
||||||
|
var itemsArray = items.ToArray();
|
||||||
|
|
||||||
|
return new QueryResult<BaseItem>
|
||||||
|
{
|
||||||
|
Items = itemsArray,
|
||||||
|
TotalRecordCount = itemsArray.Length
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region [Private Methods]
|
||||||
|
|
||||||
/// <summary> Gets report activities. </summary>
|
/// <summary> Gets report activities. </summary>
|
||||||
/// <param name="request"> The request. </param>
|
/// <param name="request"> The request. </param>
|
||||||
/// <returns> The report activities. </returns>
|
/// <returns> The report activities. </returns>
|
||||||
private Task<ReportResult> GetReportActivities(IReportsDownload request)
|
private ReportResult GetReportActivities(IReportsDownload request)
|
||||||
{
|
{
|
||||||
return Task<ReportResult>.Run(() =>
|
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
|
||||||
{
|
(DateTime?)null :
|
||||||
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
|
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
||||||
(DateTime?)null :
|
|
||||||
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
|
||||||
|
|
||||||
QueryResult<ActivityLogEntry> queryResult;
|
QueryResult<ActivityLogEntry> queryResult;
|
||||||
if (request.HasQueryLimit)
|
if (request.HasQueryLimit)
|
||||||
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||||
else
|
else
|
||||||
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
|
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
|
||||||
//var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
//var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
|
||||||
|
|
||||||
ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
|
|
||||||
var result = builder.GetResult(queryResult, request);
|
|
||||||
result.TotalRecordCount = queryResult.TotalRecordCount;
|
|
||||||
return result;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
|
||||||
|
var result = builder.GetResult(queryResult, request);
|
||||||
|
result.TotalRecordCount = queryResult.TotalRecordCount;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Gets report result. </summary>
|
/// <summary> Gets report result. </summary>
|
||||||
/// <param name="request"> The request. </param>
|
/// <param name="request"> The request. </param>
|
||||||
/// <returns> The report result. </returns>
|
/// <returns> The report result. </returns>
|
||||||
private async Task<ReportResult> GetReportResult(GetItemReport request)
|
private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
|
||||||
{
|
{
|
||||||
ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
|
ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
|
||||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
|
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||||
ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
|
ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
|
||||||
reportResult.TotalRecordCount = queryResult.TotalRecordCount;
|
reportResult.TotalRecordCount = queryResult.TotalRecordCount;
|
||||||
|
|
||||||
|
@ -458,10 +469,10 @@ namespace MediaBrowser.Api.Reports
|
||||||
/// <summary> Gets report statistic. </summary>
|
/// <summary> Gets report statistic. </summary>
|
||||||
/// <param name="request"> The request. </param>
|
/// <param name="request"> The request. </param>
|
||||||
/// <returns> The report statistic. </returns>
|
/// <returns> The report statistic. </returns>
|
||||||
private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
|
private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
|
||||||
{
|
{
|
||||||
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
|
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
|
||||||
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
|
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||||
|
|
||||||
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
|
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
|
||||||
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
|
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
|
||||||
|
|
|
@ -114,11 +114,10 @@ namespace MediaBrowser.Api
|
||||||
private void SetWizardFinishValues(ServerConfiguration config)
|
private void SetWizardFinishValues(ServerConfiguration config)
|
||||||
{
|
{
|
||||||
config.EnableLocalizedGuids = true;
|
config.EnableLocalizedGuids = true;
|
||||||
config.EnableCustomPathSubFolders = true;
|
|
||||||
config.EnableStandaloneMusicKeys = true;
|
config.EnableStandaloneMusicKeys = true;
|
||||||
config.EnableCaseSensitiveItemIds = true;
|
config.EnableCaseSensitiveItemIds = true;
|
||||||
//config.EnableFolderView = true;
|
//config.EnableFolderView = true;
|
||||||
config.SchemaVersion = 97;
|
config.SchemaVersion = 108;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Post(UpdateStartupConfiguration request)
|
public void Post(UpdateStartupConfiguration request)
|
||||||
|
|
|
@ -103,6 +103,8 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
[ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
public bool? IsInBoxSet { get; set; }
|
public bool? IsInBoxSet { get; set; }
|
||||||
|
|
||||||
|
public string ExcludeItemIds { get; set; }
|
||||||
|
|
||||||
public bool EnableTotalRecordCount { get; set; }
|
public bool EnableTotalRecordCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -367,6 +369,11 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string[] GetExcludeItemIds()
|
||||||
|
{
|
||||||
|
return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
|
||||||
public string[] GetExcludeItemTypes()
|
public string[] GetExcludeItemTypes()
|
||||||
{
|
{
|
||||||
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
|
@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||||
|
|
||||||
var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
|
var result = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
|
@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <param name="user">The user.</param>
|
/// <param name="user">The user.</param>
|
||||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||||
private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user)
|
private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
|
||||||
{
|
{
|
||||||
var item = string.IsNullOrEmpty(request.ParentId) ?
|
var item = string.IsNullOrEmpty(request.ParentId) ?
|
||||||
user == null ? _libraryManager.RootFolder : user.RootFolder :
|
user == null ? _libraryManager.RootFolder : user.RootFolder :
|
||||||
|
@ -263,7 +263,8 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
ParentIndexNumber = request.ParentIndexNumber,
|
ParentIndexNumber = request.ParentIndexNumber,
|
||||||
AiredDuringSeason = request.AiredDuringSeason,
|
AiredDuringSeason = request.AiredDuringSeason,
|
||||||
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
|
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
|
||||||
EnableTotalRecordCount = request.EnableTotalRecordCount
|
EnableTotalRecordCount = request.EnableTotalRecordCount,
|
||||||
|
ExcludeItemIds = request.GetExcludeItemIds()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(request.Ids))
|
if (!string.IsNullOrWhiteSpace(request.Ids))
|
||||||
|
|
|
@ -140,7 +140,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
private WebRequest GetRequest(HttpRequestOptions options, string method)
|
private WebRequest GetRequest(HttpRequestOptions options, string method)
|
||||||
{
|
{
|
||||||
var request = CreateWebRequest(options.Url);
|
var url = options.Url;
|
||||||
|
|
||||||
|
var uriAddress = new Uri(url);
|
||||||
|
var userInfo = uriAddress.UserInfo;
|
||||||
|
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||||
|
{
|
||||||
|
_logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
|
||||||
|
url = url.Replace(userInfo + "@", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = CreateWebRequest(url);
|
||||||
var httpWebRequest = request as HttpWebRequest;
|
var httpWebRequest = request as HttpWebRequest;
|
||||||
|
|
||||||
if (httpWebRequest != null)
|
if (httpWebRequest != null)
|
||||||
|
@ -183,9 +193,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(userInfo))
|
||||||
|
{
|
||||||
|
var parts = userInfo.Split(':');
|
||||||
|
if (parts.Length == 2)
|
||||||
|
{
|
||||||
|
request.Credentials = GetCredential(url, parts[0], parts[1]);
|
||||||
|
request.PreAuthenticate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CredentialCache GetCredential(string url, string username, string password)
|
||||||
|
{
|
||||||
|
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
|
||||||
|
CredentialCache credentialCache = new CredentialCache();
|
||||||
|
credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
|
||||||
|
return credentialCache;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
|
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
|
||||||
{
|
{
|
||||||
foreach (var header in options.RequestHeaders.ToList())
|
foreach (var header in options.RequestHeaders.ToList())
|
||||||
|
|
|
@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||||
{
|
{
|
||||||
if (updateLevel == PackageVersionClass.Release)
|
if (updateLevel == PackageVersionClass.Release)
|
||||||
{
|
{
|
||||||
obj = obj.Where(i => !i.prerelease).ToArray();
|
// Technically all we need to do is check that it's not pre-release
|
||||||
|
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
|
||||||
|
obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||||
}
|
}
|
||||||
else if (updateLevel == PackageVersionClass.Beta)
|
else if (updateLevel == PackageVersionClass.Beta)
|
||||||
{
|
{
|
||||||
|
@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
|
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
return availableUpdate ?? new CheckForUpdateResult
|
return availableUpdate ?? new CheckForUpdateResult
|
||||||
{
|
{
|
||||||
IsUpdateAvailable = false
|
IsUpdateAvailable = false
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The options.</param>
|
/// <param name="options">The options.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
|
Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the enhanced image.
|
/// Gets the enhanced image.
|
||||||
|
|
|
@ -1878,7 +1878,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return new ItemImageInfo
|
return new ItemImageInfo
|
||||||
{
|
{
|
||||||
Path = path,
|
Path = path,
|
||||||
DateModified = FileSystem.GetLastWriteTimeUtc(path),
|
DateModified = chapter.ImageDateModified,
|
||||||
Type = imageType
|
Type = imageType
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Controller.Providers;
|
using System;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
@ -17,7 +18,26 @@ namespace MediaBrowser.Controller.Entities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
public string SeriesName { get; set; }
|
public string SeriesName { get; set; }
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public Guid? SeriesId { get; set; }
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public string SeriesSortName { get; set; }
|
||||||
|
|
||||||
|
public string FindSeriesSortName()
|
||||||
|
{
|
||||||
|
return SeriesSortName;
|
||||||
|
}
|
||||||
|
public string FindSeriesName()
|
||||||
|
{
|
||||||
|
return SeriesName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid? FindSeriesId()
|
||||||
|
{
|
||||||
|
return SeriesId;
|
||||||
|
}
|
||||||
|
|
||||||
public override bool CanDownload()
|
public override bool CanDownload()
|
||||||
{
|
{
|
||||||
|
|
|
@ -760,11 +760,6 @@ namespace MediaBrowser.Controller.Entities
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
|
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (query.SortBy.Contains(ItemSortBy.SeriesSortName, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
|
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
|
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
|
||||||
|
@ -859,24 +854,6 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsMissing.HasValue)
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to IsMissing");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.IsUnaired.HasValue)
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to IsUnaired");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.IsVirtualUnaired.HasValue)
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to IsVirtualUnaired");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
|
if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
|
||||||
{
|
{
|
||||||
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
|
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
public interface IHasSeries
|
public interface IHasSeries
|
||||||
|
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Entities
|
||||||
/// Gets the name of the series.
|
/// Gets the name of the series.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name of the series.</value>
|
/// <value>The name of the series.</value>
|
||||||
string SeriesName { get; }
|
string SeriesName { get; set; }
|
||||||
|
string FindSeriesName();
|
||||||
|
string SeriesSortName { get; set; }
|
||||||
|
string FindSeriesSortName();
|
||||||
|
Guid? SeriesId { get; set; }
|
||||||
|
Guid? FindSeriesId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
|
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
|
||||||
{
|
{
|
||||||
|
|
||||||
public Episode()
|
public Episode()
|
||||||
{
|
{
|
||||||
RemoteTrailers = new List<MediaUrl>();
|
RemoteTrailers = new List<MediaUrl>();
|
||||||
|
@ -25,11 +24,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
public List<Guid> RemoteTrailerIds { get; set; }
|
public List<Guid> RemoteTrailerIds { get; set; }
|
||||||
public List<MediaUrl> RemoteTrailers { get; set; }
|
public List<MediaUrl> RemoteTrailers { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the season in which it aired.
|
/// Gets the season in which it aired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The aired season.</value>
|
/// <value>The aired season.</value>
|
||||||
public int? AirsBeforeSeasonNumber { get; set; }
|
public int? AirsBeforeSeasonNumber { get; set; }
|
||||||
public int? AirsAfterSeasonNumber { get; set; }
|
public int? AirsAfterSeasonNumber { get; set; }
|
||||||
public int? AirsBeforeEpisodeNumber { get; set; }
|
public int? AirsBeforeEpisodeNumber { get; set; }
|
||||||
|
|
||||||
|
@ -54,7 +53,16 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
/// This is the ending episode number for double episodes.
|
/// This is the ending episode number for double episodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The index number.</value>
|
/// <value>The index number.</value>
|
||||||
public int? IndexNumberEnd { get; set; }
|
public int? IndexNumberEnd { get; set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public string SeriesSortName { get; set; }
|
||||||
|
|
||||||
|
public string FindSeriesSortName()
|
||||||
|
{
|
||||||
|
var series = Series;
|
||||||
|
return series == null ? SeriesSortName : series.SortName;
|
||||||
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
protected override bool SupportsOwnedItems
|
protected override bool SupportsOwnedItems
|
||||||
|
@ -166,13 +174,27 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public string SeriesName
|
public string SeriesName { get; set; }
|
||||||
{
|
|
||||||
get
|
[IgnoreDataMember]
|
||||||
{
|
public string SeasonName { get; set; }
|
||||||
var series = Series;
|
|
||||||
return series == null ? null : series.Name;
|
public string FindSeasonName()
|
||||||
}
|
{
|
||||||
|
var season = Season;
|
||||||
|
return season == null ? SeasonName : season.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FindSeriesName()
|
||||||
|
{
|
||||||
|
var series = Series;
|
||||||
|
return series == null ? SeriesName : series.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid? FindSeasonId()
|
||||||
|
{
|
||||||
|
var season = Season;
|
||||||
|
return season == null ? (Guid?)null : season.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -235,20 +257,14 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public Guid? SeasonId
|
public Guid? SeasonId { get; set; }
|
||||||
{
|
[IgnoreDataMember]
|
||||||
get
|
public Guid? SeriesId { get; set; }
|
||||||
{
|
|
||||||
// First see if the parent is a Season
|
public Guid? FindSeriesId()
|
||||||
var season = Season;
|
{
|
||||||
|
var series = Series;
|
||||||
if (season != null)
|
return series == null ? (Guid?)null : series.Id;
|
||||||
{
|
|
||||||
return season.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Guid> GetAncestorIds()
|
public override IEnumerable<Guid> GetAncestorIds()
|
||||||
|
|
|
@ -51,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public string SeriesSortName { get; set; }
|
||||||
|
|
||||||
|
public string FindSeriesSortName()
|
||||||
|
{
|
||||||
|
var series = Series;
|
||||||
|
return series == null ? SeriesSortName : series.SortName;
|
||||||
|
}
|
||||||
|
|
||||||
// Genre, Rating and Stuido will all be the same
|
// Genre, Rating and Stuido will all be the same
|
||||||
protected override IEnumerable<string> GetIndexByOptions()
|
protected override IEnumerable<string> GetIndexByOptions()
|
||||||
{
|
{
|
||||||
|
@ -235,13 +244,21 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public string SeriesName
|
public string SeriesName { get; set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public Guid? SeriesId { get; set; }
|
||||||
|
|
||||||
|
public string FindSeriesName()
|
||||||
{
|
{
|
||||||
get
|
var series = Series;
|
||||||
{
|
return series == null ? SeriesName : series.Name;
|
||||||
var series = Series;
|
}
|
||||||
return series == null ? null : series.Name;
|
|
||||||
}
|
public Guid? FindSeriesId()
|
||||||
|
{
|
||||||
|
var series = Series;
|
||||||
|
return series == null ? (Guid?)null : series.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -28,6 +28,8 @@ namespace MediaBrowser.Controller.Net
|
||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
|
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
|
||||||
|
|
||||||
|
object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the optimized result.
|
/// Gets the optimized result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -366,6 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only use alternative encoders for video files.
|
||||||
|
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||||
|
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
||||||
|
if (state.VideoType != VideoType.VideoFile)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
||||||
{
|
{
|
||||||
if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
@ -120,6 +120,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
bool preserveOriginalTimestamps,
|
bool preserveOriginalTimestamps,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(itemId))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("itemId");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaSourceId))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("mediaSourceId");
|
||||||
|
}
|
||||||
|
|
||||||
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -141,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
int subtitleStreamIndex,
|
int subtitleStreamIndex,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(itemId))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("itemId");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaSourceId))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("mediaSourceId");
|
||||||
|
}
|
||||||
|
|
||||||
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
|
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var mediaSource = mediaSources
|
var mediaSource = mediaSources
|
||||||
.First(i => string.Equals(i.Id, mediaSourceId));
|
.First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var subtitleStream = mediaSource.MediaStreams
|
var subtitleStream = mediaSource.MediaStreams
|
||||||
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
|
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
|
||||||
|
@ -609,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.StandardError.BaseStream.CopyToAsync(logFileStream);
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
|
Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream));
|
||||||
|
|
||||||
var ranToCompletion = process.WaitForExit(300000);
|
var ranToCompletion = process.WaitForExit(300000);
|
||||||
|
|
||||||
|
@ -686,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task StartStreamingLog(Stream source, Stream target)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(source))
|
||||||
|
{
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
|
||||||
|
|
||||||
|
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||||
|
await target.FlushAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error reading ffmpeg log", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the ass font.
|
/// Sets the ass font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Weavers>
|
<Weavers>
|
||||||
<PropertyChanged />
|
|
||||||
</Weavers>
|
</Weavers>
|
|
@ -36,6 +36,8 @@
|
||||||
<UseApplicationTrust>false</UseApplicationTrust>
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||||
<RestorePackages>true</RestorePackages>
|
<RestorePackages>true</RestorePackages>
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
@ -617,9 +619,6 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
|
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
|
||||||
<Link>Extensions\FloatHelper.cs</Link>
|
<Link>Extensions\FloatHelper.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
|
|
||||||
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
|
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
|
||||||
<Link>Extensions\IntHelper.cs</Link>
|
<Link>Extensions\IntHelper.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -1233,13 +1232,6 @@
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
|
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
|
||||||
<PropertyGroup>
|
|
||||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
|
||||||
</PropertyGroup>
|
|
||||||
<Error Condition="!Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
|
|
||||||
</Target>
|
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="Fody" version="1.29.2" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
|
|
||||||
<package id="PropertyChanged.Fody" version="1.50.4" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
|
|
||||||
</packages>
|
|
|
@ -591,9 +591,6 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
|
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
|
||||||
<Link>Extensions\FloatHelper.cs</Link>
|
<Link>Extensions\FloatHelper.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
|
|
||||||
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
|
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
|
||||||
<Link>Extensions\IntHelper.cs</Link>
|
<Link>Extensions\IntHelper.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The cache path.</value>
|
/// <value>The cache path.</value>
|
||||||
public string CachePath { get; set; }
|
public string CachePath { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether [enable custom path sub folders].
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if [enable custom path sub folders]; otherwise, <c>false</c>.</value>
|
|
||||||
public bool EnableCustomPathSubFolders { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
|
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
|
||||||
|
|
|
@ -214,7 +214,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
Migrations = new string[] { };
|
Migrations = new string[] { };
|
||||||
SqliteCacheSize = 0;
|
SqliteCacheSize = 0;
|
||||||
|
|
||||||
EnableCustomPathSubFolders = true;
|
|
||||||
EnableLocalizedGuids = true;
|
EnableLocalizedGuids = true;
|
||||||
DisplaySpecialsWithinSeasons = true;
|
DisplaySpecialsWithinSeasons = true;
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,14 @@ namespace MediaBrowser.Model.Dlna
|
||||||
public AudioOptions()
|
public AudioOptions()
|
||||||
{
|
{
|
||||||
Context = EncodingContext.Streaming;
|
Context = EncodingContext.Streaming;
|
||||||
|
|
||||||
|
EnableDirectPlay = true;
|
||||||
|
EnableDirectStream = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool EnableDirectPlay { get; set; }
|
||||||
|
public bool EnableDirectStream { get; set; }
|
||||||
|
|
||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
public List<MediaSourceInfo> MediaSources { get; set; }
|
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||||
public DeviceProfile Profile { get; set; }
|
public DeviceProfile Profile { get; set; }
|
||||||
|
|
|
@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
public ProfileCondition[] Conditions { get; set; }
|
public ProfileCondition[] Conditions { get; set; }
|
||||||
|
|
||||||
|
public ProfileCondition[] ApplyConditions { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("codec")]
|
[XmlAttribute("codec")]
|
||||||
public string Codec { get; set; }
|
public string Codec { get; set; }
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
public CodecProfile()
|
public CodecProfile()
|
||||||
{
|
{
|
||||||
Conditions = new ProfileCondition[] {};
|
Conditions = new ProfileCondition[] {};
|
||||||
|
ApplyConditions = new ProfileCondition[] { };
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> GetCodecs()
|
public List<string> GetCodecs()
|
||||||
|
|
|
@ -131,6 +131,11 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options);
|
List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options);
|
||||||
|
|
||||||
|
ConditionProcessor conditionProcessor = new ConditionProcessor();
|
||||||
|
|
||||||
|
int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
|
||||||
|
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
|
||||||
|
|
||||||
if (directPlayMethods.Count > 0)
|
if (directPlayMethods.Count > 0)
|
||||||
{
|
{
|
||||||
string audioCodec = audioStream == null ? null : audioStream.Codec;
|
string audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||||
|
@ -138,27 +143,36 @@ namespace MediaBrowser.Model.Dlna
|
||||||
// Make sure audio codec profiles are satisfied
|
// Make sure audio codec profiles are satisfied
|
||||||
if (!string.IsNullOrEmpty(audioCodec))
|
if (!string.IsNullOrEmpty(audioCodec))
|
||||||
{
|
{
|
||||||
ConditionProcessor conditionProcessor = new ConditionProcessor();
|
|
||||||
|
|
||||||
List<ProfileCondition> conditions = new List<ProfileCondition>();
|
List<ProfileCondition> conditions = new List<ProfileCondition>();
|
||||||
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container))
|
if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container))
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
bool applyConditions = true;
|
||||||
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
conditions.Add(c);
|
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
|
||||||
|
{
|
||||||
|
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||||
|
applyConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyConditions)
|
||||||
|
{
|
||||||
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
conditions.Add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int? audioChannels = audioStream.Channels;
|
|
||||||
int? audioBitrate = audioStream.BitRate;
|
|
||||||
|
|
||||||
bool all = true;
|
bool all = true;
|
||||||
foreach (ProfileCondition c in conditions)
|
foreach (ProfileCondition c in conditions)
|
||||||
{
|
{
|
||||||
if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate))
|
if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate))
|
||||||
{
|
{
|
||||||
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
|
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
|
||||||
all = false;
|
all = false;
|
||||||
|
@ -241,9 +255,23 @@ namespace MediaBrowser.Model.Dlna
|
||||||
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
|
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
|
||||||
foreach (CodecProfile i in audioCodecProfiles)
|
foreach (CodecProfile i in audioCodecProfiles)
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
bool applyConditions = true;
|
||||||
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
audioTranscodingConditions.Add(c);
|
if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
|
||||||
|
{
|
||||||
|
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||||
|
applyConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyConditions)
|
||||||
|
{
|
||||||
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
audioTranscodingConditions.Add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +322,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
if (directPlayProfile != null)
|
if (directPlayProfile != null)
|
||||||
{
|
{
|
||||||
// While options takes the network and other factors into account. Only applies to direct stream
|
// While options takes the network and other factors into account. Only applies to direct stream
|
||||||
if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()))
|
if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream)
|
||||||
{
|
{
|
||||||
playMethods.Add(PlayMethod.DirectStream);
|
playMethods.Add(PlayMethod.DirectStream);
|
||||||
}
|
}
|
||||||
|
@ -302,7 +330,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
// The profile describes what the device supports
|
// The profile describes what the device supports
|
||||||
// If device requirements are satisfied then allow both direct stream and direct play
|
// If device requirements are satisfied then allow both direct stream and direct play
|
||||||
if (item.SupportsDirectPlay &&
|
if (item.SupportsDirectPlay &&
|
||||||
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
|
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
|
||||||
{
|
{
|
||||||
playMethods.Add(PlayMethod.DirectPlay);
|
playMethods.Add(PlayMethod.DirectPlay);
|
||||||
}
|
}
|
||||||
|
@ -385,8 +413,8 @@ namespace MediaBrowser.Model.Dlna
|
||||||
MediaStream videoStream = item.VideoStream;
|
MediaStream videoStream = item.VideoStream;
|
||||||
|
|
||||||
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
||||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
|
bool isEligibleForDirectPlay = options.EnableDirectPlay && IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
|
||||||
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
|
bool isEligibleForDirectStream = options.EnableDirectStream && IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
|
||||||
|
|
||||||
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
||||||
options.Profile.Name ?? "Unknown Profile",
|
options.Profile.Name ?? "Unknown Profile",
|
||||||
|
@ -464,17 +492,37 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||||
playlistItem.AudioStreamIndex = audioStreamIndex;
|
playlistItem.AudioStreamIndex = audioStreamIndex;
|
||||||
|
ConditionProcessor conditionProcessor = new ConditionProcessor();
|
||||||
|
|
||||||
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
|
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
|
||||||
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
|
if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
bool applyConditions = true;
|
||||||
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
videoTranscodingConditions.Add(c);
|
bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream);
|
||||||
|
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate;
|
||||||
|
int? audioChannels = audioStream == null ? null : audioStream.Channels;
|
||||||
|
string audioProfile = audioStream == null ? null : audioStream.Profile;
|
||||||
|
|
||||||
|
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, audioProfile, isSecondaryAudio))
|
||||||
|
{
|
||||||
|
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
|
||||||
|
applyConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyConditions)
|
||||||
|
{
|
||||||
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
videoTranscodingConditions.Add(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
|
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
|
||||||
|
@ -484,11 +532,42 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container))
|
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container))
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
bool applyConditions = true;
|
||||||
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
audioTranscodingConditions.Add(c);
|
int? width = videoStream == null ? null : videoStream.Width;
|
||||||
|
int? height = videoStream == null ? null : videoStream.Height;
|
||||||
|
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
|
||||||
|
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
|
||||||
|
double? videoLevel = videoStream == null ? null : videoStream.Level;
|
||||||
|
string videoProfile = videoStream == null ? null : videoStream.Profile;
|
||||||
|
float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
|
||||||
|
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
|
||||||
|
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
|
||||||
|
|
||||||
|
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
|
||||||
|
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
|
||||||
|
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
|
||||||
|
|
||||||
|
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
|
||||||
|
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
|
||||||
|
|
||||||
|
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
|
||||||
|
{
|
||||||
|
LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
|
||||||
|
applyConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyConditions)
|
||||||
|
{
|
||||||
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
audioTranscodingConditions.Add(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||||
|
@ -667,9 +746,23 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container))
|
if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container))
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
bool applyConditions = true;
|
||||||
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
conditions.Add(c);
|
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
|
||||||
|
{
|
||||||
|
LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
|
||||||
|
applyConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyConditions)
|
||||||
|
{
|
||||||
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
conditions.Add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -698,20 +791,35 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions = new List<ProfileCondition>();
|
conditions = new List<ProfileCondition>();
|
||||||
|
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
||||||
|
|
||||||
foreach (CodecProfile i in profile.CodecProfiles)
|
foreach (CodecProfile i in profile.CodecProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container))
|
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container))
|
||||||
{
|
{
|
||||||
foreach (ProfileCondition c in i.Conditions)
|
bool applyConditions = true;
|
||||||
|
foreach (ProfileCondition applyCondition in i.ApplyConditions)
|
||||||
{
|
{
|
||||||
conditions.Add(c);
|
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
||||||
|
{
|
||||||
|
LogConditionFailure(profile, "VideoAudioCodecProfile", applyCondition, mediaSource);
|
||||||
|
applyConditions = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyConditions)
|
||||||
|
{
|
||||||
|
foreach (ProfileCondition c in i.Conditions)
|
||||||
|
{
|
||||||
|
conditions.Add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (ProfileCondition i in conditions)
|
foreach (ProfileCondition i in conditions)
|
||||||
{
|
{
|
||||||
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
|
||||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
||||||
{
|
{
|
||||||
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
|
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// This holds information about a BaseItem in a format that is convenient for the client.
|
/// This holds information about a BaseItem in a format that is convenient for the client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
|
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
|
||||||
public class BaseItemDto : IHasProviderIds, IHasPropertyChangedEvent, IItemDto, IHasServerId, IHasSyncInfo
|
public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId, IHasSyncInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
|
@ -114,6 +114,8 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// <value>The synchronize percent.</value>
|
/// <value>The synchronize percent.</value>
|
||||||
public double? SyncPercent { get; set; }
|
public double? SyncPercent { get; set; }
|
||||||
|
|
||||||
|
public string Container { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the DVD season number.
|
/// Gets or sets the DVD season number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -954,6 +956,16 @@ namespace MediaBrowser.Model.Dto
|
||||||
get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Thumb); }
|
get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Thumb); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance has thumb.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance has thumb; otherwise, <c>false</c>.</value>
|
||||||
|
[IgnoreDataMember]
|
||||||
|
public bool HasBackdrop
|
||||||
|
{
|
||||||
|
get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance has primary image.
|
/// Gets a value indicating whether this instance has primary image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1099,11 +1111,6 @@ namespace MediaBrowser.Model.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [property changed].
|
|
||||||
/// </summary>
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the program identifier.
|
/// Gets or sets the program identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Model.Extensions;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
@ -9,7 +8,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// This is used by the api to get information about a Person within a BaseItem
|
/// This is used by the api to get information about a Person within a BaseItem
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
|
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
|
||||||
public class BaseItemPerson : IHasPropertyChangedEvent
|
public class BaseItemPerson
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
|
@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
|
||||||
return PrimaryImageTag != null;
|
return PrimaryImageTag != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [property changed].
|
|
||||||
/// </summary>
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
using System.ComponentModel;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dto
|
namespace MediaBrowser.Model.Dto
|
||||||
{
|
{
|
||||||
|
@ -9,7 +7,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// Class ChapterInfo
|
/// Class ChapterInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("Name = {Name}")]
|
[DebuggerDisplay("Name = {Name}")]
|
||||||
public class ChapterInfoDto : IHasPropertyChangedEvent
|
public class ChapterInfoDto
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the start position ticks.
|
/// Gets or sets the start position ticks.
|
||||||
|
@ -38,7 +36,5 @@ namespace MediaBrowser.Model.Dto
|
||||||
{
|
{
|
||||||
get { return ImageTag != null; }
|
get { return ImageTag != null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Connect;
|
using MediaBrowser.Model.Connect;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// Class UserDto
|
/// Class UserDto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
|
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
|
||||||
public class UserDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
|
public class UserDto : IItemDto, IHasServerId
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
|
@ -141,11 +140,6 @@ namespace MediaBrowser.Model.Dto
|
||||||
Policy = new UserPolicy();
|
Policy = new UserPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [property changed].
|
|
||||||
/// </summary>
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Name ?? base.ToString();
|
return Name ?? base.ToString();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Model.Extensions;
|
using System;
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dto
|
namespace MediaBrowser.Model.Dto
|
||||||
|
@ -7,7 +6,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class UserItemDataDto
|
/// Class UserItemDataDto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserItemDataDto : IHasPropertyChangedEvent
|
public class UserItemDataDto
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the rating.
|
/// Gets or sets the rating.
|
||||||
|
@ -74,7 +73,5 @@ namespace MediaBrowser.Model.Dto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The item identifier.</value>
|
/// <value>The item identifier.</value>
|
||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Entities
|
namespace MediaBrowser.Model.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The image path.</value>
|
/// <value>The image path.</value>
|
||||||
public string ImagePath { get; set; }
|
public string ImagePath { get; set; }
|
||||||
|
public DateTime ImageDateModified { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,14 @@
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Entities
|
namespace MediaBrowser.Model.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the display preferences for any item that supports them (usually Folders)
|
/// Defines the display preferences for any item that supports them (usually Folders)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DisplayPreferences : IHasPropertyChangedEvent
|
public class DisplayPreferences
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [property changed].
|
|
||||||
/// </summary>
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The image scale
|
/// The image scale
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Extensions
|
|
||||||
{
|
|
||||||
public interface IHasPropertyChangedEvent : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +1,10 @@
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.LiveTv
|
namespace MediaBrowser.Model.LiveTv
|
||||||
{
|
{
|
||||||
public class BaseTimerInfoDto : IHasPropertyChangedEvent, IHasServerId
|
public class BaseTimerInfoDto : IHasServerId
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Occurs when a property value changes.
|
|
||||||
/// </summary>
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Id of the recording.
|
/// Id of the recording.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Library;
|
using MediaBrowser.Model.Library;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
@ -13,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
/// Class ChannelInfoDto
|
/// Class ChannelInfoDto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DebuggerDisplay("Name = {Name}, Number = {Number}")]
|
[DebuggerDisplay("Name = {Name}, Number = {Number}")]
|
||||||
public class ChannelInfoDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
|
public class ChannelInfoDto : IItemDto, IHasServerId
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
|
@ -120,7 +119,5 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
ImageTags = new Dictionary<ImageType, string>();
|
ImageTags = new Dictionary<ImageType, string>();
|
||||||
MediaSources = new List<MediaSourceInfo>();
|
MediaSources = new List<MediaSourceInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,11 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
{
|
{
|
||||||
public class TimerInfoDto : BaseTimerInfoDto
|
public class TimerInfoDto : BaseTimerInfoDto
|
||||||
{
|
{
|
||||||
|
public TimerInfoDto()
|
||||||
|
{
|
||||||
|
Type = "Timer";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the status.
|
/// Gets or sets the status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,6 +27,8 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
/// <value>The external series timer identifier.</value>
|
/// <value>The external series timer identifier.</value>
|
||||||
public string ExternalSeriesTimerId { get; set; }
|
public string ExternalSeriesTimerId { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the run time ticks.
|
/// Gets or sets the run time ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -229,7 +229,6 @@
|
||||||
<Compile Include="Entities\SortOrder.cs" />
|
<Compile Include="Entities\SortOrder.cs" />
|
||||||
<Compile Include="Events\GenericEventArgs.cs" />
|
<Compile Include="Events\GenericEventArgs.cs" />
|
||||||
<Compile Include="Extensions\DoubleHelper.cs" />
|
<Compile Include="Extensions\DoubleHelper.cs" />
|
||||||
<Compile Include="Extensions\IHasPropertyChangedEvent.cs" />
|
|
||||||
<Compile Include="Extensions\IntHelper.cs" />
|
<Compile Include="Extensions\IntHelper.cs" />
|
||||||
<Compile Include="Extensions\ListHelper.cs" />
|
<Compile Include="Extensions\ListHelper.cs" />
|
||||||
<Compile Include="Extensions\StringHelper.cs" />
|
<Compile Include="Extensions\StringHelper.cs" />
|
||||||
|
|
|
@ -197,6 +197,8 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SeriesGenres,
|
SeriesGenres,
|
||||||
|
|
||||||
|
SeriesPrimaryImage,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The series studio
|
/// The series studio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
@ -8,7 +7,7 @@ using System.Diagnostics;
|
||||||
namespace MediaBrowser.Model.Session
|
namespace MediaBrowser.Model.Session
|
||||||
{
|
{
|
||||||
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
|
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
|
||||||
public class SessionInfoDto : IHasPropertyChangedEvent
|
public class SessionInfoDto
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the supported commands.
|
/// Gets or sets the supported commands.
|
||||||
|
@ -116,8 +115,6 @@ namespace MediaBrowser.Model.Session
|
||||||
|
|
||||||
public TranscodingInfo TranscodingInfo { get; set; }
|
public TranscodingInfo TranscodingInfo { get; set; }
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
public SessionInfoDto()
|
public SessionInfoDto()
|
||||||
{
|
{
|
||||||
AdditionalUsers = new List<SessionUserInfo>();
|
AdditionalUsers = new List<SessionUserInfo>();
|
||||||
|
|
|
@ -72,7 +72,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
// Try to translate to three character code
|
// Try to translate to three character code
|
||||||
// Be flexible and check against both the full and three character versions
|
// Be flexible and check against both the full and three character versions
|
||||||
var culture = _localization.GetCultures()
|
var culture = _localization.GetCultures()
|
||||||
.FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (culture != null)
|
if (culture != null)
|
||||||
{
|
{
|
||||||
|
@ -99,10 +102,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
private string NormalizeFilenameForSubtitleComparison(string filename)
|
private string NormalizeFilenameForSubtitleComparison(string filename)
|
||||||
{
|
{
|
||||||
// Try to account for sloppy file naming
|
// Try to account for sloppy file naming
|
||||||
filename = filename.Replace("-", string.Empty);
|
|
||||||
filename = filename.Replace("_", string.Empty);
|
filename = filename.Replace("_", string.Empty);
|
||||||
filename = filename.Replace(" ", string.Empty);
|
filename = filename.Replace(" ", string.Empty);
|
||||||
|
|
||||||
|
// can't normalize this due to languages such as pt-br
|
||||||
|
//filename = filename.Replace("-", string.Empty);
|
||||||
|
|
||||||
//filename = filename.Replace(".", string.Empty);
|
//filename = filename.Replace(".", string.Empty);
|
||||||
|
|
||||||
return filename;
|
return filename;
|
||||||
|
|
|
@ -111,7 +111,8 @@ namespace MediaBrowser.Providers.TV
|
||||||
Name = seasonName,
|
Name = seasonName,
|
||||||
IndexNumber = seasonNumber,
|
IndexNumber = seasonNumber,
|
||||||
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
|
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
|
||||||
IsVirtualItem = isVirtualItem
|
IsVirtualItem = isVirtualItem,
|
||||||
|
SeriesId = series.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
season.SetParent(series);
|
season.SetParent(series);
|
||||||
|
|
|
@ -429,7 +429,9 @@ namespace MediaBrowser.Providers.TV
|
||||||
IndexNumber = episodeNumber,
|
IndexNumber = episodeNumber,
|
||||||
ParentIndexNumber = seasonNumber,
|
ParentIndexNumber = seasonNumber,
|
||||||
Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
|
Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
|
||||||
IsVirtualItem = true
|
IsVirtualItem = true,
|
||||||
|
SeasonId = season == null ? (Guid?)null : season.Id,
|
||||||
|
SeriesId = series.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
episode.SetParent(season);
|
episode.SetParent(season);
|
||||||
|
|
|
@ -95,13 +95,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
|
||||||
{
|
{
|
||||||
metadataPath = GetInternalMetadataPath();
|
metadataPath = GetInternalMetadataPath();
|
||||||
}
|
}
|
||||||
else if (Configuration.EnableCustomPathSubFolders)
|
|
||||||
{
|
|
||||||
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
metadataPath = Configuration.MetadataPath;
|
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
|
||||||
}
|
}
|
||||||
|
|
||||||
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
|
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
|
||||||
|
|
|
@ -663,29 +663,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
dto.GameSystem = item.GameSystemName;
|
dto.GameSystem = item.GameSystemName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetBackdropImageTags(BaseItem item, int limit)
|
private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
|
||||||
{
|
{
|
||||||
return GetCacheTags(item, ImageType.Backdrop, limit).ToList();
|
return images
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> GetScreenshotImageTags(BaseItem item, int limit)
|
|
||||||
{
|
|
||||||
var hasScreenshots = item as IHasScreenshots;
|
|
||||||
if (hasScreenshots == null)
|
|
||||||
{
|
|
||||||
return new List<string>();
|
|
||||||
}
|
|
||||||
return GetCacheTags(item, ImageType.Screenshot, limit).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
|
|
||||||
{
|
|
||||||
return item.GetImages(type)
|
|
||||||
// Convert to a list now in case GetImageCacheTag is slow
|
|
||||||
.ToList()
|
|
||||||
.Select(p => GetImageCacheTag(item, p))
|
.Select(p => GetImageCacheTag(item, p))
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.Take(limit)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,53 +832,6 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If an item does not any backdrops, this can be used to find the first parent that does have one
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="owner">The owner.</param>
|
|
||||||
/// <returns>BaseItem.</returns>
|
|
||||||
private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
|
|
||||||
{
|
|
||||||
var parent = item.GetParent() ?? owner;
|
|
||||||
|
|
||||||
while (parent != null)
|
|
||||||
{
|
|
||||||
if (parent.GetImages(ImageType.Backdrop).Any())
|
|
||||||
{
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = parent.GetParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If an item does not have a logo, this can be used to find the first parent that does have one
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <param name="owner">The owner.</param>
|
|
||||||
/// <returns>BaseItem.</returns>
|
|
||||||
private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
|
|
||||||
{
|
|
||||||
var parent = item.GetParent() ?? owner;
|
|
||||||
|
|
||||||
while (parent != null)
|
|
||||||
{
|
|
||||||
if (parent.HasImage(type))
|
|
||||||
{
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = parent.GetParent();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the chapter info dto.
|
/// Gets the chapter info dto.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -917,7 +852,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
Path = chapterInfo.ImagePath,
|
Path = chapterInfo.ImagePath,
|
||||||
Type = ImageType.Chapter,
|
Type = ImageType.Chapter,
|
||||||
DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath)
|
DateModified = chapterInfo.ImageDateModified
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,6 +893,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
dto.LockData = item.IsLocked;
|
dto.LockData = item.IsLocked;
|
||||||
dto.ForcedSortName = item.ForcedSortName;
|
dto.ForcedSortName = item.ForcedSortName;
|
||||||
}
|
}
|
||||||
|
dto.Container = item.Container;
|
||||||
|
|
||||||
var hasBudget = item as IHasBudget;
|
var hasBudget = item as IHasBudget;
|
||||||
if (hasBudget != null)
|
if (hasBudget != null)
|
||||||
|
@ -1027,7 +963,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||||
if (backdropLimit > 0)
|
if (backdropLimit > 0)
|
||||||
{
|
{
|
||||||
dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit);
|
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.ScreenshotImageTags))
|
if (fields.Contains(ItemFields.ScreenshotImageTags))
|
||||||
|
@ -1035,7 +971,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
|
||||||
if (screenshotLimit > 0)
|
if (screenshotLimit > 0)
|
||||||
{
|
{
|
||||||
dto.ScreenshotImageTags = GetScreenshotImageTags(item, screenshotLimit);
|
dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1064,6 +1000,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
dto.Id = GetDtoId(item);
|
dto.Id = GetDtoId(item);
|
||||||
dto.IndexNumber = item.IndexNumber;
|
dto.IndexNumber = item.IndexNumber;
|
||||||
|
dto.ParentIndexNumber = item.ParentIndexNumber;
|
||||||
dto.IsFolder = item.IsFolder;
|
dto.IsFolder = item.IsFolder;
|
||||||
dto.MediaType = item.MediaType;
|
dto.MediaType = item.MediaType;
|
||||||
dto.LocationType = item.LocationType;
|
dto.LocationType = item.LocationType;
|
||||||
|
@ -1076,15 +1013,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
|
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
|
||||||
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
|
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
|
||||||
|
|
||||||
var hasCriticRating = item as IHasCriticRating;
|
dto.CriticRating = item.CriticRating;
|
||||||
if (hasCriticRating != null)
|
|
||||||
{
|
|
||||||
dto.CriticRating = hasCriticRating.CriticRating;
|
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.CriticRatingSummary))
|
if (fields.Contains(ItemFields.CriticRatingSummary))
|
||||||
{
|
{
|
||||||
dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary;
|
dto.CriticRatingSummary = item.CriticRatingSummary;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasTrailers = item as IHasTrailers;
|
var hasTrailers = item as IHasTrailers;
|
||||||
|
@ -1127,23 +1060,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.ShortOverview))
|
if (fields.Contains(ItemFields.ShortOverview))
|
||||||
{
|
{
|
||||||
var hasShortOverview = item as IHasShortOverview;
|
dto.ShortOverview = item.ShortOverview;
|
||||||
if (hasShortOverview != null)
|
|
||||||
{
|
|
||||||
dto.ShortOverview = hasShortOverview.ShortOverview;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
|
|
||||||
if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
|
|
||||||
{
|
|
||||||
var parentWithBackdrop = GetParentBackdropItem(item, owner);
|
|
||||||
|
|
||||||
if (parentWithBackdrop != null)
|
|
||||||
{
|
|
||||||
dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
|
|
||||||
dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.ParentId))
|
if (fields.Contains(ItemFields.ParentId))
|
||||||
|
@ -1155,46 +1072,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.ParentIndexNumber = item.ParentIndexNumber;
|
AddInheritedImages(dto, item, options, owner);
|
||||||
|
|
||||||
// If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
|
|
||||||
if (!dto.HasLogo && options.GetImageLimit(ImageType.Logo) > 0)
|
|
||||||
{
|
|
||||||
var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
|
|
||||||
|
|
||||||
if (parentWithLogo != null)
|
|
||||||
{
|
|
||||||
dto.ParentLogoItemId = GetDtoId(parentWithLogo);
|
|
||||||
|
|
||||||
dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
|
|
||||||
if (!dto.HasArtImage && options.GetImageLimit(ImageType.Art) > 0)
|
|
||||||
{
|
|
||||||
var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
|
|
||||||
|
|
||||||
if (parentWithImage != null)
|
|
||||||
{
|
|
||||||
dto.ParentArtItemId = GetDtoId(parentWithImage);
|
|
||||||
|
|
||||||
dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is no thumb, indicate what parent has one in case the Ui wants to allow inheritance
|
|
||||||
if (!dto.HasThumb && options.GetImageLimit(ImageType.Thumb) > 0)
|
|
||||||
{
|
|
||||||
var parentWithImage = GetParentImageItem(item, ImageType.Thumb, owner);
|
|
||||||
|
|
||||||
if (parentWithImage != null)
|
|
||||||
{
|
|
||||||
dto.ParentThumbItemId = GetDtoId(parentWithImage);
|
|
||||||
|
|
||||||
dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.Path))
|
if (fields.Contains(ItemFields.Path))
|
||||||
{
|
{
|
||||||
|
@ -1426,42 +1304,38 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
dto.SeasonId = seasonId.Value.ToString("N");
|
dto.SeasonId = seasonId.Value.ToString("N");
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeSeason = episode.Season;
|
dto.SeasonName = episode.SeasonName;
|
||||||
if (episodeSeason != null)
|
|
||||||
|
var seriesId = episode.SeriesId;
|
||||||
|
if (seriesId.HasValue)
|
||||||
{
|
{
|
||||||
if (fields.Contains(ItemFields.SeasonName))
|
dto.SeriesId = seriesId.Value.ToString("N");
|
||||||
{
|
|
||||||
dto.SeasonName = episodeSeason.Name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeSeries = episode.Series;
|
Series episodeSeries = null;
|
||||||
|
|
||||||
if (episodeSeries != null)
|
if (fields.Contains(ItemFields.SeriesGenres))
|
||||||
{
|
{
|
||||||
if (fields.Contains(ItemFields.SeriesGenres))
|
episodeSeries = episodeSeries ?? episode.Series;
|
||||||
|
if (episodeSeries != null)
|
||||||
{
|
{
|
||||||
dto.SeriesGenres = episodeSeries.Genres.ToList();
|
dto.SeriesGenres = episodeSeries.Genres.ToList();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dto.SeriesId = GetDtoId(episodeSeries);
|
//if (fields.Contains(ItemFields.SeriesPrimaryImage))
|
||||||
|
{
|
||||||
if (fields.Contains(ItemFields.AirTime))
|
episodeSeries = episodeSeries ?? episode.Series;
|
||||||
{
|
if (episodeSeries != null)
|
||||||
dto.AirTime = episodeSeries.AirTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.GetImageLimit(ImageType.Thumb) > 0)
|
|
||||||
{
|
|
||||||
dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.GetImageLimit(ImageType.Primary) > 0)
|
|
||||||
{
|
{
|
||||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
|
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.SeriesStudio))
|
if (fields.Contains(ItemFields.SeriesStudio))
|
||||||
|
{
|
||||||
|
episodeSeries = episodeSeries ?? episode.Series;
|
||||||
|
if (episodeSeries != null)
|
||||||
{
|
{
|
||||||
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
|
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
@ -1483,16 +1357,29 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
var season = item as Season;
|
var season = item as Season;
|
||||||
if (season != null)
|
if (season != null)
|
||||||
{
|
{
|
||||||
series = season.Series;
|
dto.SeriesName = season.SeriesName;
|
||||||
|
|
||||||
if (series != null)
|
var seriesId = season.SeriesId;
|
||||||
|
if (seriesId.HasValue)
|
||||||
{
|
{
|
||||||
dto.SeriesId = GetDtoId(series);
|
dto.SeriesId = seriesId.Value.ToString("N");
|
||||||
dto.SeriesName = series.Name;
|
}
|
||||||
dto.AirTime = series.AirTime;
|
|
||||||
dto.SeriesStudio = series.Studios.FirstOrDefault();
|
|
||||||
|
|
||||||
if (options.GetImageLimit(ImageType.Primary) > 0)
|
series = null;
|
||||||
|
|
||||||
|
if (fields.Contains(ItemFields.SeriesStudio))
|
||||||
|
{
|
||||||
|
series = series ?? season.Series;
|
||||||
|
if (series != null)
|
||||||
|
{
|
||||||
|
dto.SeriesStudio = series.Studios.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.Contains(ItemFields.SeriesPrimaryImage))
|
||||||
|
{
|
||||||
|
series = series ?? season.Series;
|
||||||
|
if (series != null)
|
||||||
{
|
{
|
||||||
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
|
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
@ -1543,6 +1430,77 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner)
|
||||||
|
{
|
||||||
|
var logoLimit = options.GetImageLimit(ImageType.Logo);
|
||||||
|
var artLimit = options.GetImageLimit(ImageType.Art);
|
||||||
|
var thumbLimit = options.GetImageLimit(ImageType.Thumb);
|
||||||
|
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
|
||||||
|
|
||||||
|
if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseItem parent = null;
|
||||||
|
var isFirst = true;
|
||||||
|
|
||||||
|
while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) &&
|
||||||
|
(parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null)
|
||||||
|
{
|
||||||
|
if (parent == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allImages = parent.ImageInfos;
|
||||||
|
|
||||||
|
if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null)
|
||||||
|
{
|
||||||
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
dto.ParentLogoItemId = GetDtoId(parent);
|
||||||
|
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null)
|
||||||
|
{
|
||||||
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
dto.ParentArtItemId = GetDtoId(parent);
|
||||||
|
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series))
|
||||||
|
{
|
||||||
|
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
dto.ParentThumbItemId = GetDtoId(parent);
|
||||||
|
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (backdropLimit > 0 && !dto.HasBackdrop)
|
||||||
|
{
|
||||||
|
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
|
||||||
|
|
||||||
|
if (images.Count > 0)
|
||||||
|
{
|
||||||
|
dto.ParentBackdropItemId = GetDtoId(parent);
|
||||||
|
dto.ParentBackdropImageTags = GetImageTags(parent, images);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirst = false;
|
||||||
|
parent = parent.GetParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetMappedPath(IHasMetadata item)
|
private string GetMappedPath(IHasMetadata item)
|
||||||
{
|
{
|
||||||
var path = item.Path;
|
var path = item.Path;
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ServiceStack;
|
||||||
|
using ServiceStack.Web;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
|
{
|
||||||
|
public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the source stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The source stream.</value>
|
||||||
|
private Func<Stream, Task> Writer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the options.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The options.</value>
|
||||||
|
public IDictionary<string, string> Options { get; private set; }
|
||||||
|
|
||||||
|
public Action OnComplete { get; set; }
|
||||||
|
public Action OnError { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="StreamWriter" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers)
|
||||||
|
{
|
||||||
|
Writer = writer;
|
||||||
|
|
||||||
|
if (headers == null)
|
||||||
|
{
|
||||||
|
headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
Options = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes to.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="responseStream">The response stream.</param>
|
||||||
|
public void WriteTo(Stream responseStream)
|
||||||
|
{
|
||||||
|
var task = Writer(responseStream);
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteToAsync(Stream responseStream)
|
||||||
|
{
|
||||||
|
await Writer(responseStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -335,7 +335,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// <param name="httpReq">The HTTP req.</param>
|
/// <param name="httpReq">The HTTP req.</param>
|
||||||
/// <param name="url">The URL.</param>
|
/// <param name="url">The URL.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
protected Task RequestHandler(IHttpRequest httpReq, Uri url)
|
protected async Task RequestHandler(IHttpRequest httpReq, Uri url)
|
||||||
{
|
{
|
||||||
var date = DateTime.Now;
|
var date = DateTime.Now;
|
||||||
|
|
||||||
|
@ -345,7 +345,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
httpRes.StatusCode = 503;
|
httpRes.StatusCode = 503;
|
||||||
httpRes.Close();
|
httpRes.Close();
|
||||||
return Task.FromResult(true);
|
return ;
|
||||||
}
|
}
|
||||||
|
|
||||||
var operationName = httpReq.OperationName;
|
var operationName = httpReq.OperationName;
|
||||||
|
@ -365,13 +365,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl(DefaultRedirectPath);
|
httpRes.RedirectToUrl(DefaultRedirectPath);
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
|
httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
@ -389,35 +389,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>");
|
httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>");
|
||||||
|
|
||||||
httpRes.Close();
|
httpRes.Close();
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl(DefaultRedirectPath);
|
httpRes.RedirectToUrl(DefaultRedirectPath);
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl("../" + DefaultRedirectPath);
|
httpRes.RedirectToUrl("../" + DefaultRedirectPath);
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl(DefaultRedirectPath);
|
httpRes.RedirectToUrl(DefaultRedirectPath);
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(localPath))
|
if (string.IsNullOrEmpty(localPath))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl("/" + DefaultRedirectPath);
|
httpRes.RedirectToUrl("/" + DefaultRedirectPath);
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
httpRes.RedirectToUrl("web/pin.html");
|
httpRes.RedirectToUrl("web/pin.html");
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(GlobalResponse))
|
if (!string.IsNullOrWhiteSpace(GlobalResponse))
|
||||||
|
@ -427,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
httpRes.Write(GlobalResponse);
|
httpRes.Write(GlobalResponse);
|
||||||
|
|
||||||
httpRes.Close();
|
httpRes.Close();
|
||||||
return Task.FromResult(true);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var handler = HttpHandlerFactory.GetHandler(httpReq);
|
var handler = HttpHandlerFactory.GetHandler(httpReq);
|
||||||
|
@ -443,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
|
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
|
||||||
}
|
}
|
||||||
|
|
||||||
var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName);
|
try
|
||||||
|
|
||||||
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
|
|
||||||
//Matches Exceptions handled in HttpListenerBase.InitTask()
|
|
||||||
|
|
||||||
task.ContinueWith(x =>
|
|
||||||
{
|
{
|
||||||
|
await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
httpRes.Close();
|
||||||
var statusCode = httpRes.StatusCode;
|
var statusCode = httpRes.StatusCode;
|
||||||
|
|
||||||
var duration = DateTime.Now - date;
|
var duration = DateTime.Now - date;
|
||||||
|
@ -458,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
|
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, TaskContinuationOptions.None);
|
|
||||||
return task;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo)
|
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
|
||||||
.AsTaskException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
options.ContentType = MimeTypes.GetMimeType(path);
|
options.ContentType = MimeTypes.GetMimeType(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
|
if (!options.DateLastModified.HasValue)
|
||||||
|
{
|
||||||
|
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||||
|
}
|
||||||
|
|
||||||
var cacheKey = path + options.DateLastModified.Value.Ticks;
|
var cacheKey = path + options.DateLastModified.Value.Ticks;
|
||||||
|
|
||||||
options.CacheKey = cacheKey.GetMD5();
|
options.CacheKey = cacheKey.GetMD5();
|
||||||
|
@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
|
||||||
|
{
|
||||||
|
return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,285 +0,0 @@
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using ServiceStack;
|
|
||||||
using ServiceStack.Host.HttpListener;
|
|
||||||
using ServiceStack.Web;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.HttpServer.NetListener
|
|
||||||
{
|
|
||||||
public class HttpListenerServer : IHttpListener
|
|
||||||
{
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private HttpListener _listener;
|
|
||||||
private readonly ManualResetEventSlim _listenForNextRequest = new ManualResetEventSlim(false);
|
|
||||||
|
|
||||||
public Action<Exception, IRequest> ErrorHandler { get; set; }
|
|
||||||
public Action<WebSocketConnectEventArgs> WebSocketHandler { get; set; }
|
|
||||||
public Func<IHttpRequest, Uri, Task> RequestHandler { get; set; }
|
|
||||||
|
|
||||||
private readonly Action<string> _endpointListener;
|
|
||||||
|
|
||||||
public HttpListenerServer(ILogger logger, Action<string> endpointListener)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_endpointListener = endpointListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> UrlPrefixes { get; set; }
|
|
||||||
|
|
||||||
public void Start(IEnumerable<string> urlPrefixes)
|
|
||||||
{
|
|
||||||
UrlPrefixes = urlPrefixes.ToList();
|
|
||||||
|
|
||||||
if (_listener == null)
|
|
||||||
_listener = new HttpListener();
|
|
||||||
|
|
||||||
//HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First());
|
|
||||||
|
|
||||||
foreach (var prefix in UrlPrefixes)
|
|
||||||
{
|
|
||||||
_logger.Info("Adding HttpListener prefix " + prefix);
|
|
||||||
_listener.Prefixes.Add(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
_listener.Start();
|
|
||||||
|
|
||||||
Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsListening
|
|
||||||
{
|
|
||||||
get { return _listener != null && _listener.IsListening; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop here to begin processing of new requests.
|
|
||||||
private void Listen()
|
|
||||||
{
|
|
||||||
while (IsListening)
|
|
||||||
{
|
|
||||||
if (_listener == null) return;
|
|
||||||
_listenForNextRequest.Reset();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_listener.BeginGetContext(ListenerCallback, _listener);
|
|
||||||
_listenForNextRequest.Wait();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.Error("Listen()", ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_listener == null) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the processing of a request in here.
|
|
||||||
private void ListenerCallback(IAsyncResult asyncResult)
|
|
||||||
{
|
|
||||||
_listenForNextRequest.Set();
|
|
||||||
|
|
||||||
var listener = asyncResult.AsyncState as HttpListener;
|
|
||||||
HttpListenerContext context;
|
|
||||||
|
|
||||||
if (listener == null) return;
|
|
||||||
var isListening = listener.IsListening;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!isListening)
|
|
||||||
{
|
|
||||||
_logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return;
|
|
||||||
}
|
|
||||||
// The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework,
|
|
||||||
// blocks until there is a request to be processed or some type of data is available.
|
|
||||||
context = listener.EndGetContext(asyncResult);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// You will get an exception when httpListener.Stop() is called
|
|
||||||
// because there will be a thread stopped waiting on the .EndGetContext()
|
|
||||||
// method, and again, that is just the way most Begin/End asynchronous
|
|
||||||
// methods of the .NET Framework work.
|
|
||||||
var errMsg = ex + ": " + IsListening;
|
|
||||||
_logger.Warn(errMsg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task.Factory.StartNew(() => InitTask(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitTask(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var task = this.ProcessRequestAsync(context);
|
|
||||||
task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
|
|
||||||
|
|
||||||
if (task.Status == TaskStatus.Created)
|
|
||||||
{
|
|
||||||
task.RunSynchronously();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
HandleError(ex, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task ProcessRequestAsync(HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var request = context.Request;
|
|
||||||
|
|
||||||
LogHttpRequest(request);
|
|
||||||
|
|
||||||
if (request.IsWebSocketRequest)
|
|
||||||
{
|
|
||||||
return ProcessWebSocketRequest(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(context.Request.RawUrl))
|
|
||||||
return ((object)null).AsTaskResult();
|
|
||||||
|
|
||||||
var operationName = context.Request.GetOperationName();
|
|
||||||
|
|
||||||
var httpReq = GetRequest(context, operationName);
|
|
||||||
|
|
||||||
return RequestHandler(httpReq, request.Url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes the web socket request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ctx">The CTX.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
|
|
||||||
{
|
|
||||||
#if !__MonoCS__
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (WebSocketHandler != null)
|
|
||||||
{
|
|
||||||
WebSocketHandler(new WebSocketConnectEventArgs
|
|
||||||
{
|
|
||||||
WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger),
|
|
||||||
Endpoint = ctx.Request.RemoteEndPoint.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("AcceptWebSocketAsync error", ex);
|
|
||||||
ctx.Response.StatusCode = 500;
|
|
||||||
ctx.Response.Close();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleError(Exception ex, HttpListenerContext context)
|
|
||||||
{
|
|
||||||
var operationName = context.Request.GetOperationName();
|
|
||||||
var httpReq = GetRequest(context, operationName);
|
|
||||||
|
|
||||||
if (ErrorHandler != null)
|
|
||||||
{
|
|
||||||
ErrorHandler(ex, httpReq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName)
|
|
||||||
{
|
|
||||||
var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None);
|
|
||||||
req.RequestAttributes = req.GetAttributes();
|
|
||||||
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Logs the HTTP request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
private void LogHttpRequest(HttpListenerRequest request)
|
|
||||||
{
|
|
||||||
var endpoint = request.LocalEndPoint;
|
|
||||||
|
|
||||||
if (endpoint != null)
|
|
||||||
{
|
|
||||||
var address = endpoint.ToString();
|
|
||||||
|
|
||||||
_endpointListener(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogRequest(_logger, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Logs the request.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
/// <param name="request">The request.</param>
|
|
||||||
private static void LogRequest(ILogger logger, HttpListenerRequest request)
|
|
||||||
{
|
|
||||||
var log = new StringBuilder();
|
|
||||||
|
|
||||||
var logHeaders = true;
|
|
||||||
|
|
||||||
if (logHeaders)
|
|
||||||
{
|
|
||||||
var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k]));
|
|
||||||
|
|
||||||
log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod;
|
|
||||||
|
|
||||||
logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
if (_listener != null)
|
|
||||||
{
|
|
||||||
foreach (var prefix in UrlPrefixes)
|
|
||||||
{
|
|
||||||
_listener.Prefixes.Remove(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
_listener.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
private readonly object _disposeLock = new object();
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
|
|
||||||
lock (_disposeLock)
|
|
||||||
{
|
|
||||||
if (_disposed) return;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
//release unmanaged resources here...
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,10 +5,12 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ServiceStack;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
public class RangeRequestWriter : IStreamWriter, IHttpResult
|
public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the source stream.
|
/// Gets or sets the source stream.
|
||||||
|
@ -168,16 +170,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
public void WriteTo(Stream responseStream)
|
public void WriteTo(Stream responseStream)
|
||||||
{
|
|
||||||
WriteToInternal(responseStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes to async.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="responseStream">The response stream.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private void WriteToInternal(Stream responseStream)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -237,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task WriteToAsync(Stream responseStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Headers only
|
||||||
|
if (IsHeadRequest)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var source = SourceStream)
|
||||||
|
{
|
||||||
|
// If the requested range is "0-", we can optimize by just doing a stream copy
|
||||||
|
if (RangeEnd >= TotalContentLength - 1)
|
||||||
|
{
|
||||||
|
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error in range request writer", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (OnComplete != null)
|
||||||
|
{
|
||||||
|
OnComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
|
||||||
|
{
|
||||||
|
var array = new byte[BufferSize];
|
||||||
|
int count;
|
||||||
|
while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
|
||||||
|
{
|
||||||
|
var bytesToCopy = Math.Min(count, copyLength);
|
||||||
|
|
||||||
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
copyLength -= bytesToCopy;
|
||||||
|
|
||||||
|
if (copyLength <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string ContentType { get; set; }
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
public IRequest RequestContext { get; set; }
|
public IRequest RequestContext { get; set; }
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Specialized;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
|
@ -32,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
return header.Substring(ap + 1, end - ap - 1);
|
return header.Substring(ap + 1, end - ap - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadMultiPart()
|
async Task LoadMultiPart()
|
||||||
{
|
{
|
||||||
string boundary = GetParameter(ContentType, "; boundary=");
|
string boundary = GetParameter(ContentType, "; boundary=");
|
||||||
if (boundary == null)
|
if (boundary == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var input = GetSubStream(InputStream);
|
using (var requestStream = GetSubStream(InputStream))
|
||||||
|
|
||||||
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
|
|
||||||
//Not ending with \r\n?
|
|
||||||
var ms = new MemoryStream(32 * 1024);
|
|
||||||
input.CopyTo(ms);
|
|
||||||
input = ms;
|
|
||||||
ms.WriteByte((byte)'\r');
|
|
||||||
ms.WriteByte((byte)'\n');
|
|
||||||
|
|
||||||
input.Position = 0;
|
|
||||||
|
|
||||||
//Uncomment to debug
|
|
||||||
//var content = new StreamReader(ms).ReadToEnd();
|
|
||||||
//Console.WriteLine(boundary + "::" + content);
|
|
||||||
//input.Position = 0;
|
|
||||||
|
|
||||||
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
|
|
||||||
|
|
||||||
HttpMultipart.Element e;
|
|
||||||
while ((e = multi_part.ReadNextElement()) != null)
|
|
||||||
{
|
{
|
||||||
if (e.Filename == null)
|
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
|
||||||
{
|
//Not ending with \r\n?
|
||||||
byte[] copy = new byte[e.Length];
|
var ms = new MemoryStream(32 * 1024);
|
||||||
|
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
|
||||||
|
|
||||||
input.Position = e.Start;
|
var input = ms;
|
||||||
input.Read(copy, 0, (int)e.Length);
|
ms.WriteByte((byte)'\r');
|
||||||
|
ms.WriteByte((byte)'\n');
|
||||||
|
|
||||||
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
|
input.Position = 0;
|
||||||
}
|
|
||||||
else
|
//Uncomment to debug
|
||||||
|
//var content = new StreamReader(ms).ReadToEnd();
|
||||||
|
//Console.WriteLine(boundary + "::" + content);
|
||||||
|
//input.Position = 0;
|
||||||
|
|
||||||
|
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
|
||||||
|
|
||||||
|
HttpMultipart.Element e;
|
||||||
|
while ((e = multi_part.ReadNextElement()) != null)
|
||||||
{
|
{
|
||||||
//
|
if (e.Filename == null)
|
||||||
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
{
|
||||||
//
|
byte[] copy = new byte[e.Length];
|
||||||
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
|
||||||
files.AddFile(e.Name, sub);
|
input.Position = e.Start;
|
||||||
|
input.Read(copy, 0, (int)e.Length);
|
||||||
|
|
||||||
|
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// We use a substream, as in 2.x we will support large uploads streamed to disk,
|
||||||
|
//
|
||||||
|
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
|
||||||
|
files.AddFile(e.Name, sub);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EndSubStream(input);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public NameValueCollection Form
|
public NameValueCollection Form
|
||||||
|
@ -91,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
files = new HttpFileCollection();
|
files = new HttpFileCollection();
|
||||||
|
|
||||||
if (IsContentType("multipart/form-data", true))
|
if (IsContentType("multipart/form-data", true))
|
||||||
LoadMultiPart();
|
{
|
||||||
else if (
|
var task = LoadMultiPart();
|
||||||
IsContentType("application/x-www-form-urlencoded", true))
|
Task.WaitAll(task);
|
||||||
LoadWwwForm();
|
}
|
||||||
|
else if (IsContentType("application/x-www-form-urlencoded", true))
|
||||||
|
{
|
||||||
|
var task = LoadWwwForm();
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
form.Protect();
|
form.Protect();
|
||||||
}
|
}
|
||||||
|
@ -220,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
|
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async Task LoadWwwForm()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void LoadWwwForm()
|
|
||||||
{
|
{
|
||||||
using (Stream input = GetSubStream(InputStream))
|
using (Stream input = GetSubStream(InputStream))
|
||||||
{
|
{
|
||||||
using (StreamReader s = new StreamReader(input, ContentEncoding))
|
using (var ms = new MemoryStream())
|
||||||
{
|
{
|
||||||
StringBuilder key = new StringBuilder();
|
await input.CopyToAsync(ms).ConfigureAwait(false);
|
||||||
StringBuilder value = new StringBuilder();
|
ms.Position = 0;
|
||||||
int c;
|
|
||||||
|
|
||||||
while ((c = s.Read()) != -1)
|
using (StreamReader s = new StreamReader(ms, ContentEncoding))
|
||||||
{
|
{
|
||||||
if (c == '=')
|
StringBuilder key = new StringBuilder();
|
||||||
|
StringBuilder value = new StringBuilder();
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while ((c = s.Read()) != -1)
|
||||||
{
|
{
|
||||||
value.Length = 0;
|
if (c == '=')
|
||||||
while ((c = s.Read()) != -1)
|
|
||||||
{
|
{
|
||||||
if (c == '&')
|
value.Length = 0;
|
||||||
|
while ((c = s.Read()) != -1)
|
||||||
|
{
|
||||||
|
if (c == '&')
|
||||||
|
{
|
||||||
|
AddRawKeyValue(key, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
value.Append((char)c);
|
||||||
|
}
|
||||||
|
if (c == -1)
|
||||||
{
|
{
|
||||||
AddRawKeyValue(key, value);
|
AddRawKeyValue(key, value);
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
value.Append((char)c);
|
|
||||||
}
|
}
|
||||||
if (c == -1)
|
else if (c == '&')
|
||||||
{
|
|
||||||
AddRawKeyValue(key, value);
|
AddRawKeyValue(key, value);
|
||||||
return;
|
else
|
||||||
}
|
key.Append((char)c);
|
||||||
}
|
}
|
||||||
else if (c == '&')
|
if (c == -1)
|
||||||
AddRawKeyValue(key, value);
|
AddRawKeyValue(key, value);
|
||||||
else
|
|
||||||
key.Append((char)c);
|
|
||||||
}
|
}
|
||||||
if (c == -1)
|
|
||||||
AddRawKeyValue(key, value);
|
|
||||||
|
|
||||||
EndSubStream(input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return remoteIp ??
|
return remoteIp ??
|
||||||
(remoteIp = XForwardedFor ??
|
(remoteIp = (CheckBadChars(XForwardedFor)) ??
|
||||||
(NormalizeIp(XRealIp) ??
|
(NormalizeIp(CheckBadChars(XRealIp)) ??
|
||||||
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
|
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
|
||||||
|
|
||||||
|
//
|
||||||
|
// CheckBadChars - throws on invalid chars to be not found in header name/value
|
||||||
|
//
|
||||||
|
internal static string CheckBadChars(string name)
|
||||||
|
{
|
||||||
|
if (name == null || name.Length == 0)
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VALUE check
|
||||||
|
//Trim spaces from both ends
|
||||||
|
name = name.Trim(HttpTrimCharacters);
|
||||||
|
|
||||||
|
//First, check for correctly formed multi-line value
|
||||||
|
//Second, check for absenece of CTL characters
|
||||||
|
int crlf = 0;
|
||||||
|
for (int i = 0; i < name.Length; ++i)
|
||||||
|
{
|
||||||
|
char c = (char)(0x000000ff & (uint)name[i]);
|
||||||
|
switch (crlf)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (c == '\r')
|
||||||
|
{
|
||||||
|
crlf = 1;
|
||||||
|
}
|
||||||
|
else if (c == '\n')
|
||||||
|
{
|
||||||
|
// Technically this is bad HTTP. But it would be a breaking change to throw here.
|
||||||
|
// Is there an exploit?
|
||||||
|
crlf = 2;
|
||||||
|
}
|
||||||
|
else if (c == 127 || (c < ' ' && c != '\t'))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("net_WebHeaderInvalidControlChars");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
crlf = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (c == ' ' || c == '\t')
|
||||||
|
{
|
||||||
|
crlf = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (crlf != 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool ContainsNonAsciiChars(string token)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < token.Length; ++i)
|
||||||
|
{
|
||||||
|
if ((token[i] < 0x20) || (token[i] > 0x7e))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private string NormalizeIp(string ip)
|
private string NormalizeIp(string ip)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(ip))
|
if (!string.IsNullOrWhiteSpace(ip))
|
||||||
|
@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void EndSubStream(Stream stream)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetHandlerPathIfAny(string listenerUrl)
|
public static string GetHandlerPathIfAny(string listenerUrl)
|
||||||
{
|
{
|
||||||
if (listenerUrl == null) return null;
|
if (listenerUrl == null) return null;
|
||||||
|
|
|
@ -4,13 +4,15 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ServiceStack;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class StreamWriter
|
/// Class StreamWriter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StreamWriter : IStreamWriter, IHasOptions
|
public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
|
||||||
{
|
{
|
||||||
private ILogger Logger { get; set; }
|
private ILogger Logger { get; set; }
|
||||||
|
|
||||||
|
@ -73,24 +75,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 256k
|
||||||
|
private const int BufferSize = 262144;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes to.
|
/// Writes to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
public void WriteTo(Stream responseStream)
|
public void WriteTo(Stream responseStream)
|
||||||
{
|
|
||||||
WriteToInternal(responseStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 256k
|
|
||||||
private const int BufferSize = 262144;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Writes to async.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="responseStream">The response stream.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private void WriteToInternal(Stream responseStream)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -107,7 +99,36 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
{
|
{
|
||||||
OnError();
|
OnError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (OnComplete != null)
|
||||||
|
{
|
||||||
|
OnComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteToAsync(Stream responseStream)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var src = SourceStream)
|
||||||
|
{
|
||||||
|
await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error streaming data", ex);
|
||||||
|
|
||||||
|
if (OnError != null)
|
||||||
|
{
|
||||||
|
OnError();
|
||||||
|
}
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.IO
|
||||||
// This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
|
// This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
|
||||||
// Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
|
// Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
|
||||||
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
|
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
|
||||||
await Task.Delay(25000).ConfigureAwait(false);
|
await Task.Delay(45000).ConfigureAwait(false);
|
||||||
|
|
||||||
string val;
|
string val;
|
||||||
_tempIgnoredPaths.TryRemove(path, out val);
|
_tempIgnoredPaths.TryRemove(path, out val);
|
||||||
|
|
|
@ -33,6 +33,7 @@ using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Model.Channels;
|
using MediaBrowser.Model.Channels;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
|
@ -353,10 +354,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
private void RegisterItem(Guid id, BaseItem item)
|
private void RegisterItem(Guid id, BaseItem item)
|
||||||
{
|
{
|
||||||
if (item.SourceType != SourceType.Library)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (item is IItemByName)
|
if (item is IItemByName)
|
||||||
{
|
{
|
||||||
if (!(item is MusicArtist))
|
if (!(item is MusicArtist))
|
||||||
|
@ -364,14 +361,25 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item is Photo)
|
|
||||||
|
if (item.IsFolder)
|
||||||
{
|
{
|
||||||
return;
|
if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel))
|
||||||
|
{
|
||||||
|
if (item.SourceType != SourceType.Library)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//if (!(item is Folder))
|
else
|
||||||
//{
|
{
|
||||||
// return;
|
if (item is Photo)
|
||||||
//}
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
|
LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,19 +790,19 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
public BaseItem FindByPath(string path, bool? isFolder)
|
public BaseItem FindByPath(string path, bool? isFolder)
|
||||||
{
|
{
|
||||||
var query = new InternalItemsQuery
|
|
||||||
{
|
|
||||||
Path = path,
|
|
||||||
IsFolder = isFolder
|
|
||||||
};
|
|
||||||
|
|
||||||
// If this returns multiple items it could be tricky figuring out which one is correct.
|
// If this returns multiple items it could be tricky figuring out which one is correct.
|
||||||
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
||||||
|
|
||||||
return GetItemIds(query)
|
var query = new InternalItemsQuery
|
||||||
.Select(GetItemById)
|
{
|
||||||
.Where(i => i != null)
|
Path = path,
|
||||||
.OrderByDescending(i => i.DateCreated)
|
IsFolder = isFolder,
|
||||||
|
SortBy = new[] { ItemSortBy.DateCreated },
|
||||||
|
SortOrder = SortOrder.Descending,
|
||||||
|
Limit = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
return GetItemList(query)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1258,6 +1266,8 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
item = RetrieveItem(id);
|
item = RetrieveItem(id);
|
||||||
|
|
||||||
|
//_logger.Debug("GetitemById {0}", id);
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
|
@ -1508,7 +1518,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
UserId = user.Id.ToString("N")
|
UserId = user.Id.ToString("N")
|
||||||
|
|
||||||
}, CancellationToken.None).Result;
|
}, CancellationToken.None).Result;
|
||||||
|
|
||||||
return channelResult.Items;
|
return channelResult.Items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1921,7 +1931,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
private string GetContentTypeOverride(string path, bool inherit)
|
private string GetContentTypeOverride(string path, bool inherit)
|
||||||
{
|
{
|
||||||
var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && _fileSystem.ContainsSubPath(i.Name, path)));
|
var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
|
||||||
if (nameValuePair != null)
|
if (nameValuePair != null)
|
||||||
{
|
{
|
||||||
return nameValuePair.Value;
|
return nameValuePair.Value;
|
||||||
|
@ -2802,6 +2812,11 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
private void RemoveContentTypeOverrides(string path)
|
private void RemoveContentTypeOverrides(string path)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("path");
|
||||||
|
}
|
||||||
|
|
||||||
var removeList = new List<NameValuePair>();
|
var removeList = new List<NameValuePair>();
|
||||||
|
|
||||||
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
|
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
|
||||||
|
|
|
@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using CommonIO;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||||
{
|
{
|
||||||
public class PhotoResolver : ItemResolver<Photo>
|
public class PhotoResolver : ItemResolver<Photo>
|
||||||
{
|
{
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
public PhotoResolver(IImageProcessor imageProcessor)
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||||
/// <returns>Trailer.</returns>
|
/// <returns>Trailer.</returns>
|
||||||
protected override Photo Resolve(ItemResolveArgs args)
|
protected override Photo Resolve(ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
// Must be an image file within a photo collection
|
if (!args.IsDirectory)
|
||||||
if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!args.IsDirectory &&
|
|
||||||
IsImageFile(args.Path, _imageProcessor))
|
|
||||||
{
|
{
|
||||||
return new Photo
|
// Must be an image file within a photo collection
|
||||||
|
var collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
|
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Path = args.Path
|
if (IsImageFile(args.Path, _imageProcessor))
|
||||||
};
|
{
|
||||||
|
var filename = Path.GetFileNameWithoutExtension(args.Path);
|
||||||
|
|
||||||
|
// Make sure the image doesn't belong to a video file
|
||||||
|
if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename)))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Photo
|
||||||
|
{
|
||||||
|
Path = args.Path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename)
|
||||||
|
{
|
||||||
|
if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly string[] IgnoreFiles =
|
private static readonly string[] IgnoreFiles =
|
||||||
{
|
{
|
||||||
"folder",
|
"folder",
|
||||||
|
@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
|
||||||
"landscape",
|
"landscape",
|
||||||
"fanart",
|
"fanart",
|
||||||
"backdrop",
|
"backdrop",
|
||||||
"poster"
|
"poster",
|
||||||
|
"cover"
|
||||||
};
|
};
|
||||||
|
|
||||||
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
|
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
|
||||||
|
|
|
@ -31,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
||||||
}
|
}
|
||||||
|
|
||||||
var season = parent as Season;
|
var season = parent as Season;
|
||||||
|
|
||||||
// Just in case the user decided to nest episodes.
|
// Just in case the user decided to nest episodes.
|
||||||
// Not officially supported but in some cases we can handle it.
|
// Not officially supported but in some cases we can handle it.
|
||||||
if (season == null)
|
if (season == null)
|
||||||
|
@ -41,10 +40,30 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
||||||
|
|
||||||
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
|
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
|
||||||
// Also handle flat tv folders
|
// Also handle flat tv folders
|
||||||
if (season != null || args.HasParent<Series>() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
if (season != null ||
|
||||||
|
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
args.HasParent<Series>())
|
||||||
{
|
{
|
||||||
var episode = ResolveVideo<Episode>(args, false);
|
var episode = ResolveVideo<Episode>(args, false);
|
||||||
|
|
||||||
|
if (episode != null)
|
||||||
|
{
|
||||||
|
var series = parent as Series;
|
||||||
|
if (series == null)
|
||||||
|
{
|
||||||
|
series = parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series != null)
|
||||||
|
{
|
||||||
|
episode.SeriesId = series.Id;
|
||||||
|
}
|
||||||
|
if (season != null)
|
||||||
|
{
|
||||||
|
episode.SeasonId = season.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
||||||
if (args.Parent is Series && args.IsDirectory)
|
if (args.Parent is Series && args.IsDirectory)
|
||||||
{
|
{
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||||
|
var series = ((Series)args.Parent);
|
||||||
|
|
||||||
var season = new Season
|
var season = new Season
|
||||||
{
|
{
|
||||||
IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber
|
IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
|
||||||
|
SeriesId = series.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
|
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
|
||||||
|
|
|
@ -1214,8 +1214,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
|
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
list.Add(item);
|
list.Add(item);
|
||||||
|
|
||||||
_libraryManager.RegisterItem(item);
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
|
@ -106,18 +107,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
|
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using (var stream = await _httpClient.Get(new HttpRequestOptions()
|
try
|
||||||
{
|
{
|
||||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
using (var stream = await _httpClient.Get(new HttpRequestOptions()
|
||||||
CancellationToken = cancellationToken,
|
{
|
||||||
CacheLength = TimeSpan.FromDays(1),
|
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
||||||
CacheMode = CacheMode.Unconditional,
|
CancellationToken = cancellationToken,
|
||||||
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
|
CacheLength = TimeSpan.FromDays(1),
|
||||||
}))
|
CacheMode = CacheMode.Unconditional,
|
||||||
{
|
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
|
||||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
}))
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||||
|
|
||||||
return response.ModelNumber;
|
return response.ModelNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// HDHR4 doesn't have this api
|
||||||
|
return "HDHR";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,16 +469,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test it by pulling down the lineup
|
try
|
||||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
|
||||||
{
|
{
|
||||||
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
// Test it by pulling down the lineup
|
||||||
CancellationToken = CancellationToken.None
|
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||||
}))
|
{
|
||||||
{
|
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
|
||||||
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
CancellationToken = CancellationToken.None
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
|
||||||
|
|
||||||
info.DeviceId = response.DeviceID;
|
info.DeviceId = response.DeviceID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
// HDHR4 doesn't have this api
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,8 @@
|
||||||
<Reference Include="Interfaces.IO">
|
<Reference Include="Interfaces.IO">
|
||||||
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
|
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6012.15754, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.52\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
|
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MoreLinq">
|
<Reference Include="MoreLinq">
|
||||||
|
@ -73,8 +73,8 @@
|
||||||
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
|
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="SocketHttpListener, Version=1.0.6046.26351, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll</HintPath>
|
<HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
|
@ -156,6 +156,7 @@
|
||||||
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
|
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
|
||||||
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
|
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
|
||||||
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
|
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
|
||||||
|
<Compile Include="HttpServer\AsyncStreamWriterFunc.cs" />
|
||||||
<Compile Include="HttpServer\IHttpListener.cs" />
|
<Compile Include="HttpServer\IHttpListener.cs" />
|
||||||
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
|
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
|
||||||
<Compile Include="HttpServer\ContainerAdapter.cs" />
|
<Compile Include="HttpServer\ContainerAdapter.cs" />
|
||||||
|
@ -757,9 +758,7 @@
|
||||||
<EmbeddedResource Include="Localization\iso6392.txt" />
|
<EmbeddedResource Include="Localization\iso6392.txt" />
|
||||||
<EmbeddedResource Include="Localization\Ratings\be.txt" />
|
<EmbeddedResource Include="Localization\Ratings\be.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup />
|
||||||
<Folder Include="HttpServer\NetListener\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
|
|
@ -152,6 +152,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
||||||
}
|
}
|
||||||
|
|
||||||
chapter.ImagePath = path;
|
chapter.ImagePath = path;
|
||||||
|
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||||
changesMade = true;
|
changesMade = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -170,6 +171,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
||||||
else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
|
else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
chapter.ImagePath = path;
|
chapter.ImagePath = path;
|
||||||
|
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
|
||||||
changesMade = true;
|
changesMade = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
_logger.Debug("Upgrading schema for {0} items", numItems);
|
_logger.Debug("Upgrading schema for {0} items", numItems);
|
||||||
|
|
||||||
|
var list = new List<BaseItem>();
|
||||||
|
|
||||||
foreach (var itemId in itemIds)
|
foreach (var itemId in itemIds)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
@ -166,27 +168,50 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
try
|
list.Add(item);
|
||||||
{
|
|
||||||
await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error saving item", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (list.Count >= 1000)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error saving item", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
numComplete++;
|
numComplete++;
|
||||||
double percent = numComplete;
|
double percent = numComplete;
|
||||||
percent /= numItems;
|
percent /= numItems;
|
||||||
progress.Report(percent * 100);
|
progress.Report(percent * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (list.Count > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error saving item", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
private IDbCommand _updateInheritedRatingCommand;
|
private IDbCommand _updateInheritedRatingCommand;
|
||||||
private IDbCommand _updateInheritedTagsCommand;
|
private IDbCommand _updateInheritedTagsCommand;
|
||||||
|
|
||||||
public const int LatestSchemaVersion = 97;
|
public const int LatestSchemaVersion = 108;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
||||||
|
@ -269,10 +269,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
_connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
|
_connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
|
||||||
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
|
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
|
||||||
_connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
|
_connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
|
||||||
|
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonName", "Text");
|
||||||
|
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
|
||||||
|
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
|
||||||
|
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text");
|
||||||
|
|
||||||
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
|
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
|
||||||
_connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
|
_connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
|
||||||
|
|
||||||
|
_connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
|
||||||
|
|
||||||
string[] postQueries =
|
string[] postQueries =
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -403,7 +409,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
"Album",
|
"Album",
|
||||||
"CriticRating",
|
"CriticRating",
|
||||||
"CriticRatingSummary",
|
"CriticRatingSummary",
|
||||||
"IsVirtualItem"
|
"IsVirtualItem",
|
||||||
|
"SeriesName",
|
||||||
|
"SeasonName",
|
||||||
|
"SeasonId",
|
||||||
|
"SeriesId",
|
||||||
|
"SeriesSortName"
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly string[] _mediaStreamSaveColumns =
|
private readonly string[] _mediaStreamSaveColumns =
|
||||||
|
@ -523,7 +534,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
"Album",
|
"Album",
|
||||||
"IsVirtualItem",
|
"IsVirtualItem",
|
||||||
"SeriesName",
|
"SeriesName",
|
||||||
"UserDataKey"
|
"UserDataKey",
|
||||||
|
"SeasonName",
|
||||||
|
"SeasonId",
|
||||||
|
"SeriesId",
|
||||||
|
"SeriesSortName"
|
||||||
};
|
};
|
||||||
_saveItemCommand = _connection.CreateCommand();
|
_saveItemCommand = _connection.CreateCommand();
|
||||||
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
|
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
|
||||||
|
@ -582,6 +597,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
|
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
|
||||||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
|
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
|
||||||
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
|
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
|
||||||
|
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
|
||||||
|
|
||||||
// MediaStreams
|
// MediaStreams
|
||||||
_deleteStreamsCommand = _connection.CreateCommand();
|
_deleteStreamsCommand = _connection.CreateCommand();
|
||||||
|
@ -945,7 +961,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
var hasSeries = item as IHasSeries;
|
var hasSeries = item as IHasSeries;
|
||||||
if (hasSeries != null)
|
if (hasSeries != null)
|
||||||
{
|
{
|
||||||
_saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName;
|
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -954,6 +970,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
_saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
|
_saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
|
||||||
|
|
||||||
|
var episode = item as Episode;
|
||||||
|
if (episode != null)
|
||||||
|
{
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = episode.FindSeasonName();
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = episode.FindSeasonId();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = null;
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSeries != null)
|
||||||
|
{
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId();
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesSortName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = null;
|
||||||
|
_saveItemCommand.GetParameter(index++).Value = null;
|
||||||
|
}
|
||||||
|
|
||||||
_saveItemCommand.Transaction = transaction;
|
_saveItemCommand.Transaction = transaction;
|
||||||
|
|
||||||
_saveItemCommand.ExecuteNonQuery();
|
_saveItemCommand.ExecuteNonQuery();
|
||||||
|
@ -1376,6 +1415,44 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
item.IsVirtualItem = reader.GetBoolean(58);
|
item.IsVirtualItem = reader.GetBoolean(58);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasSeries = item as IHasSeries;
|
||||||
|
if (hasSeries != null)
|
||||||
|
{
|
||||||
|
if (!reader.IsDBNull(59))
|
||||||
|
{
|
||||||
|
hasSeries.SeriesName = reader.GetString(59);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var episode = item as Episode;
|
||||||
|
if (episode != null)
|
||||||
|
{
|
||||||
|
if (!reader.IsDBNull(60))
|
||||||
|
{
|
||||||
|
episode.SeasonName = reader.GetString(60);
|
||||||
|
}
|
||||||
|
if (!reader.IsDBNull(61))
|
||||||
|
{
|
||||||
|
episode.SeasonId = reader.GetGuid(61);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSeries != null)
|
||||||
|
{
|
||||||
|
if (!reader.IsDBNull(62))
|
||||||
|
{
|
||||||
|
hasSeries.SeriesId = reader.GetGuid(62);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSeries != null)
|
||||||
|
{
|
||||||
|
if (!reader.IsDBNull(63))
|
||||||
|
{
|
||||||
|
hasSeries.SeriesSortName = reader.GetString(63);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1437,7 +1514,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
using (var cmd = _connection.CreateCommand())
|
using (var cmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
|
cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
|
||||||
|
|
||||||
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
|
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
|
||||||
|
|
||||||
|
@ -1470,7 +1547,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
using (var cmd = _connection.CreateCommand())
|
using (var cmd = _connection.CreateCommand())
|
||||||
{
|
{
|
||||||
cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
|
cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
|
||||||
|
|
||||||
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
|
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
|
||||||
cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
|
cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
|
||||||
|
@ -1508,6 +1585,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
chapter.ImagePath = reader.GetString(2);
|
chapter.ImagePath = reader.GetString(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!reader.IsDBNull(3))
|
||||||
|
{
|
||||||
|
chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
|
||||||
|
}
|
||||||
|
|
||||||
return chapter;
|
return chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1567,6 +1649,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
|
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
|
||||||
_saveChapterCommand.GetParameter(3).Value = chapter.Name;
|
_saveChapterCommand.GetParameter(3).Value = chapter.Name;
|
||||||
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
|
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
|
||||||
|
_saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
|
||||||
|
|
||||||
_saveChapterCommand.Transaction = transaction;
|
_saveChapterCommand.Transaction = transaction;
|
||||||
|
|
||||||
|
@ -2061,7 +2144,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random };
|
query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2986,6 +3069,39 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
whereClauses.Add("LocationType<>'Virtual'");
|
whereClauses.Add("LocationType<>'Virtual'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (query.IsUnaired.HasValue)
|
||||||
|
{
|
||||||
|
if (query.IsUnaired.Value)
|
||||||
|
{
|
||||||
|
whereClauses.Add("PremiereDate >= DATETIME('now')");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
whereClauses.Add("PremiereDate < DATETIME('now')");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query.IsMissing.HasValue && _config.Configuration.SchemaVersion >= 90)
|
||||||
|
{
|
||||||
|
if (query.IsMissing.Value)
|
||||||
|
{
|
||||||
|
whereClauses.Add("(IsVirtualItem=1 AND PremiereDate < DATETIME('now'))");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate >= DATETIME('now'))");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query.IsVirtualUnaired.HasValue && _config.Configuration.SchemaVersion >= 90)
|
||||||
|
{
|
||||||
|
if (query.IsVirtualUnaired.Value)
|
||||||
|
{
|
||||||
|
whereClauses.Add("(IsVirtualItem=1 AND PremiereDate >= DATETIME('now'))");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (query.MediaTypes.Length == 1)
|
if (query.MediaTypes.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("MediaType=@MediaTypes");
|
whereClauses.Add("MediaType=@MediaTypes");
|
||||||
|
@ -4063,6 +4179,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
throw new ArgumentNullException("values");
|
throw new ArgumentNullException("values");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Just in case there might be case-insensitive duplicates, strip them out now
|
||||||
|
var newValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var pair in values)
|
||||||
|
{
|
||||||
|
newValues[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
// First delete
|
// First delete
|
||||||
|
@ -4071,7 +4194,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
|
|
||||||
_deleteProviderIdsCommand.ExecuteNonQuery();
|
_deleteProviderIdsCommand.ExecuteNonQuery();
|
||||||
|
|
||||||
foreach (var pair in values)
|
foreach (var pair in newValues)
|
||||||
{
|
{
|
||||||
_saveProviderIdsCommand.GetParameter(0).Value = itemId;
|
_saveProviderIdsCommand.GetParameter(0).Value = itemId;
|
||||||
_saveProviderIdsCommand.GetParameter(1).Value = pair.Key;
|
_saveProviderIdsCommand.GetParameter(1).Value = pair.Key;
|
||||||
|
|
|
@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newIndex > oldIndex)
|
|
||||||
{
|
|
||||||
newIndex--;
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = playlist.LinkedChildren[oldIndex];
|
var item = playlist.LinkedChildren[oldIndex];
|
||||||
|
|
||||||
playlist.LinkedChildren.Remove(item);
|
playlist.LinkedChildren.Remove(item);
|
||||||
playlist.LinkedChildren.Insert(newIndex, item);
|
|
||||||
|
if (newIndex >= playlist.LinkedChildren.Count)
|
||||||
|
{
|
||||||
|
playlist.LinkedChildren.Add(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playlist.LinkedChildren.Insert(newIndex, item);
|
||||||
|
}
|
||||||
|
|
||||||
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using System;
|
using System;
|
||||||
|
@ -21,28 +20,9 @@ namespace MediaBrowser.Server.Implementations.Sorting
|
||||||
|
|
||||||
private string GetValue(BaseItem item)
|
private string GetValue(BaseItem item)
|
||||||
{
|
{
|
||||||
Series series = null;
|
var hasSeries = item as IHasSeries;
|
||||||
|
|
||||||
var season = item as Season;
|
return hasSeries != null ? hasSeries.SeriesSortName : null;
|
||||||
|
|
||||||
if (season != null)
|
|
||||||
{
|
|
||||||
series = season.Series;
|
|
||||||
}
|
|
||||||
|
|
||||||
var episode = item as Episode;
|
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
|
||||||
series = episode.Series;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (series == null)
|
|
||||||
{
|
|
||||||
series = item as Series;
|
|
||||||
}
|
|
||||||
|
|
||||||
return series != null ? series.SortName : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -50,6 +50,11 @@ namespace MediaBrowser.Server.Implementations.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
|
||||||
|
{
|
||||||
|
limit = limit.Value + 10;
|
||||||
|
}
|
||||||
|
|
||||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||||
|
@ -89,6 +94,11 @@ namespace MediaBrowser.Server.Implementations.TV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
|
||||||
|
{
|
||||||
|
limit = limit.Value + 10;
|
||||||
|
}
|
||||||
|
|
||||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||||
|
@ -115,7 +125,8 @@ namespace MediaBrowser.Server.Implementations.TV
|
||||||
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
|
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
|
||||||
.OrderByDescending(i => i.Item2)
|
.OrderByDescending(i => i.Item2)
|
||||||
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
|
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
|
||||||
.Select(i => i.Item1);
|
.Select(i => i.Item1)
|
||||||
|
.Take(request.Limit ?? int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetUniqueSeriesKey(BaseItem series)
|
private string GetUniqueSeriesKey(BaseItem series)
|
||||||
|
@ -143,7 +154,6 @@ namespace MediaBrowser.Server.Implementations.TV
|
||||||
SortOrder = SortOrder.Descending,
|
SortOrder = SortOrder.Descending,
|
||||||
IsPlayed = true,
|
IsPlayed = true,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
IsVirtualItem = false,
|
|
||||||
ParentIndexNumberNotEquals = 0
|
ParentIndexNumberNotEquals = 0
|
||||||
|
|
||||||
}).FirstOrDefault();
|
}).FirstOrDefault();
|
||||||
|
|
|
@ -54,11 +54,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
||||||
{
|
{
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
var episodeSeason = episode.Season;
|
|
||||||
if (episodeSeason != null)
|
|
||||||
{
|
|
||||||
return episodeSeason;
|
|
||||||
}
|
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
||||||
{
|
{
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
var episodeSeason = episode.Season;
|
|
||||||
if (episodeSeason != null)
|
|
||||||
{
|
|
||||||
return episodeSeason;
|
|
||||||
}
|
|
||||||
|
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
<package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
|
<package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
|
||||||
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
|
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
|
||||||
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
|
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
|
||||||
<package id="MediaBrowser.Naming" version="1.0.0.52" targetFramework="net45" />
|
<package id="MediaBrowser.Naming" version="1.0.0.53" targetFramework="net45" />
|
||||||
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
|
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
|
||||||
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
<package id="morelinq" version="1.4.0" targetFramework="net45" />
|
||||||
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
|
||||||
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
||||||
<package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
|
<package id="SocketHttpListener" version="1.0.0.35" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
|
@ -274,7 +274,7 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return "Media Browser Server";
|
return "Emby Server";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ using System.Configuration.Install;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Management;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.ServiceProcess;
|
using System.ServiceProcess;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication
|
||||||
|
|
||||||
if (IsAlreadyRunning(applicationPath, currentProcess))
|
if (IsAlreadyRunning(applicationPath, currentProcess))
|
||||||
{
|
{
|
||||||
logger.Info("Shutting down because another instance of Media Browser Server is already running.");
|
logger.Info("Shutting down because another instance of Emby Server is already running.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication
|
||||||
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
|
||||||
private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
|
private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(applicationPath);
|
|
||||||
|
|
||||||
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
|
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return string.Equals(filename, Path.GetFileName(i.MainModule.FileName)) && currentProcess.Id != i.Id;
|
if (currentProcess.Id == i.Id)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//_logger.Info("Module: {0}", i.MainModule.FileName);
|
||||||
|
if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
@ -155,6 +171,41 @@ namespace MediaBrowser.ServerApplication
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_isRunningAsService)
|
||||||
|
{
|
||||||
|
return IsAlreadyRunningAsService(applicationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAlreadyRunningAsService(string applicationPath)
|
||||||
|
{
|
||||||
|
var serviceName = BackgroundService.GetExistingServiceName();
|
||||||
|
|
||||||
|
WqlObjectQuery wqlObjectQuery = new WqlObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE State = 'Running' AND Name = '{0}'", serviceName));
|
||||||
|
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(wqlObjectQuery);
|
||||||
|
ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
|
||||||
|
|
||||||
|
foreach (ManagementObject managementObject in managementObjectCollection)
|
||||||
|
{
|
||||||
|
var obj = managementObject.GetPropertyValue("PathName");
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var path = obj.ToString();
|
||||||
|
|
||||||
|
_logger.Info("Service path: {0}", path);
|
||||||
|
// Need to use indexOf instead of equality because the path will have the full service command line
|
||||||
|
if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
_logger.Info("The windows service is already running");
|
||||||
|
MessageBox.Show("Emby Server is already running as a Windows Service. Only one instance is allowed at a time. To run as a tray icon, shut down the Windows Service.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,14 +644,32 @@ namespace MediaBrowser.ServerApplication
|
||||||
|
|
||||||
private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
|
private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
|
||||||
{
|
{
|
||||||
|
// Reference
|
||||||
|
// http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var version = ImageMagickEncoder.GetVersion();
|
var subkey = Environment.Is64BitProcess
|
||||||
return;
|
? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
|
||||||
|
: "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
|
||||||
|
|
||||||
|
using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
|
||||||
|
.OpenSubKey(subkey))
|
||||||
|
{
|
||||||
|
if (ndpKey != null && ndpKey.GetValue("Version") != null)
|
||||||
|
{
|
||||||
|
var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
|
||||||
|
if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.ErrorException("Error loading ImageMagick", ex);
|
logger.ErrorException("Error getting .NET Framework version", ex);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System.Drawing" />
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Management" />
|
||||||
<Reference Include="System.ServiceProcess" />
|
<Reference Include="System.ServiceProcess" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
|
|
@ -349,21 +349,16 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
|
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
|
//_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
|
//_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
|
||||||
|
|
||||||
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
|
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
|
||||||
|
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
|
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
|
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
||||||
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
//DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
||||||
|
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
|
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
|
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true);
|
|
||||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true);
|
|
||||||
|
|
||||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
@ -104,6 +104,9 @@
|
||||||
<Content Include="dashboard-ui\components\apphost.js">
|
<Content Include="dashboard-ui\components\apphost.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="dashboard-ui\components\categorysyncbuttons.js">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
|
<Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -902,9 +905,6 @@
|
||||||
<Content Include="dashboard-ui\scripts\favorites.js">
|
<Content Include="dashboard-ui\scripts\favorites.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="dashboard-ui\scripts\librarylist.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\scripts\librarymenu.js">
|
<Content Include="dashboard-ui\scripts\librarymenu.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -1043,42 +1043,6 @@
|
||||||
<Content Include="dashboard-ui\userpassword.html">
|
<Content Include="dashboard-ui\userpassword.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="dashboard-ui\voice\commands\controlcommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\commands\disablecommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\commands\enablecommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\commands\playcommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\commands\searchcommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\commands\showcommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\commands\togglecommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\grammarprocessor.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\voicedialog.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\voice.css">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\voice.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\voice\voicecommands.js">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="dashboard-ui\wizardagreement.html">
|
<Content Include="dashboard-ui\wizardagreement.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -1674,15 +1638,6 @@
|
||||||
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
|
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<None Include="dashboard-ui\voice\grammar\en-US.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="dashboard-ui\voice\grammar\grammar.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="dashboard-ui\voice\Readme.md">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
|
|
Loading…
Reference in New Issue