Merge branch 'beta'

This commit is contained in:
Luke Pulverenti 2016-07-22 01:50:33 -04:00
commit f57ce51d12
79 changed files with 1458 additions and 1096 deletions

View File

@ -163,7 +163,7 @@ namespace Emby.Drawing
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)
{
@ -178,14 +178,13 @@ namespace Emby.Drawing
}
var originalImagePath = originalImage.Path;
var dateModified = originalImage.DateModified;
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)
{
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@ -211,7 +210,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath))
{
// 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;
@ -221,7 +220,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
{
// 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
@ -235,10 +234,6 @@ namespace Emby.Drawing
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 semaphore = GetLock(cacheFilePath);
await semaphore.WaitAsync().ConfigureAwait(false);
var imageProcessingLockTaken = false;
try
@ -251,15 +246,20 @@ namespace Emby.Drawing
var newHeight = Convert.ToInt32(newSize.Height);
_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);
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)
{
@ -267,7 +267,7 @@ namespace Emby.Drawing
_logger.ErrorException("Error encoding image", ex);
// 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
{
@ -275,8 +275,18 @@ namespace Emby.Drawing
{
_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 semaphore = GetLock(croppedImagePath);
await semaphore.WaitAsync().ConfigureAwait(false);
// Check again in case of contention
if (_fileSystem.FileExists(croppedImagePath))
{
semaphore.Release();
return GetResult(croppedImagePath);
}
@ -428,11 +433,15 @@ namespace Emby.Drawing
try
{
_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);
imageProcessingLockTaken = true;
_imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
_imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
CopyFile(tmpPath, croppedImagePath);
return GetResult(tmpPath);
}
catch (NotImplementedException)
{
@ -452,11 +461,7 @@ namespace Emby.Drawing
{
_imageProcessingSemaphore.Release();
}
semaphore.Release();
}
return GetResult(croppedImagePath);
}
private Tuple<string, DateTime> GetResult(string path)

View File

@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
{
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);
@ -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;
// Prevent implicitly captured closure
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);
@ -636,6 +638,7 @@ namespace MediaBrowser.Api.Images
CacheDuration = cacheDuration,
ResponseHeaders = headers,
ContentType = imageResult.Item2,
DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,

View File

@ -286,19 +286,25 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state)
{
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
// 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 "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))
{
return "h264_nvenc";
}
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
return "h264_omx";
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
return "h264_nvenc";
}
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
return "h264_omx";
}
}
return "libx264";
@ -843,6 +849,14 @@ namespace MediaBrowser.Api.Playback
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 (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))

View File

@ -13,6 +13,7 @@ using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
@ -336,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
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
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
{

View File

@ -48,21 +48,19 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
WriteToInternal(responseStream);
var task = WriteToAsync(responseStream);
Task.WaitAll(task);
}
/// <summary>
/// Writes to async.
/// Writes to.
/// </summary>
/// <param name="responseStream">The response stream.</param>
/// <returns>Task.</returns>
private void WriteToInternal(Stream responseStream)
public async Task WriteToAsync(Stream responseStream)
{
try
{
var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream);
Task.WaitAll(task);
await new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream).ConfigureAwait(false);
}
catch (IOException)
{
@ -110,11 +108,11 @@ namespace MediaBrowser.Api.Playback.Progressive
var eofCount = 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)
{
CopyToInternal(fs, outputStream, BufferSize);
await CopyToInternal(fs, outputStream, BufferSize).ConfigureAwait(false);
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];
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)
//{
@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// }
//}
destination.Write(array, 0, count);
await destination.WriteAsync(array, 0, count).ConfigureAwait(false);
_bytesWritten += count;

View File

@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
/// <summary> Manager for library. </summary>
private readonly ILibraryManager _libraryManager; ///< Manager for library
/// <summary> The localization. </summary>
/// <summary> The localization. </summary>
private readonly ILocalizationManager _localization; ///< The localization
@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets the given request. </summary>
/// <param name="request"> The request. </param>
/// <returns> A Task&lt;object&gt; </returns>
public async Task<object> Get(GetActivityLogs request)
public object Get(GetActivityLogs request)
{
request.DisplayType = "Screen";
ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
ReportResult result = GetReportActivities(request);
return ToOptimizedResult(result);
}
@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
return null;
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);
}
@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
if (string.IsNullOrEmpty(request.IncludeItemTypes))
return null;
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);
}
@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
headers["Content-Encoding"] = "UTF-8";
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
ReportResult result = null;
switch (reportViewType)
{
@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
case ReportViewType.ReportData:
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
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.TotalRecordCount = queryResult.TotalRecordCount;
break;
case ReportViewType.ReportActivities:
result = await GetReportActivities(request).ConfigureAwait(false);
result = GetReportActivities(request);
break;
}
@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
break;
}
object ro = ResultFactory.GetResult(returnResult, contentType, headers);
return ro;
return ResultFactory.GetResult(returnResult, contentType, headers);
}
#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)
{
var query = new InternalItemsQuery
var query = new InternalItemsQuery(user)
{
User = user,
IsPlayed = request.IsPlayed,
MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(),
@ -231,6 +226,7 @@ namespace MediaBrowser.Api.Reports
Tags = request.GetTags(),
OfficialRatings = request.GetOfficialRatings(),
Genres = request.GetGenres(),
GenreIds = request.GetGenreIds(),
Studios = request.GetStudios(),
StudioIds = request.GetStudioIds(),
Person = request.Person,
@ -245,9 +241,11 @@ namespace MediaBrowser.Api.Reports
MaxPlayers = request.MaxPlayers,
MinCommunityRating = request.MinCommunityRating,
MinCriticRating = request.MinCriticRating,
ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason,
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
EnableTotalRecordCount = request.EnableTotalRecordCount
};
if (!string.IsNullOrWhiteSpace(request.Ids))
@ -357,98 +355,111 @@ namespace MediaBrowser.Api.Reports
query.AlbumNames = request.Albums.Split('|');
}
if (request.HasQueryLimit == false)
{
query.StartIndex = null;
query.Limit = null;
}
return query;
}
/// <summary> Gets query result. </summary>
/// <param name="request"> The request. </param>
/// <returns> The query result. </returns>
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
{
// Placeholder in case needed later
// all report queries currently need this because it's not being specified
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) ?
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)
{
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
return result;
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
else
if (user == null)
{
if (user == null)
{
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 await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
}
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>
/// <param name="request"> The request. </param>
/// <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.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
(DateTime?)null :
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
QueryResult<ActivityLogEntry> queryResult;
if (request.HasQueryLimit)
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
else
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
//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;
});
QueryResult<ActivityLogEntry> queryResult;
if (request.HasQueryLimit)
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
else
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
//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;
}
/// <summary> Gets report result. </summary>
/// <param name="request"> The request. </param>
/// <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);
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.TotalRecordCount = queryResult.TotalRecordCount;
@ -458,10 +469,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets report statistic. </summary>
/// <param name="request"> The request. </param>
/// <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);
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);

View File

@ -114,11 +114,10 @@ namespace MediaBrowser.Api
private void SetWizardFinishValues(ServerConfiguration config)
{
config.EnableLocalizedGuids = true;
config.EnableCustomPathSubFolders = true;
config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true;
//config.EnableFolderView = true;
config.SchemaVersion = 97;
config.SchemaVersion = 108;
}
public void Post(UpdateStartupConfiguration request)

View File

@ -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")]
public bool? IsInBoxSet { get; set; }
public string ExcludeItemIds { get; set; }
public bool EnableTotalRecordCount { get; set; }
/// <summary>
@ -367,6 +369,11 @@ namespace MediaBrowser.Api.UserLibrary
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
public string[] GetExcludeItemIds()
{
return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
public string[] GetExcludeItemTypes()
{
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

View File

@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
{
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)
{
@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="user">The user.</param>
/// <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) ?
user == null ? _libraryManager.RootFolder : user.RootFolder :
@ -263,7 +263,8 @@ namespace MediaBrowser.Api.UserLibrary
ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason,
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
EnableTotalRecordCount = request.EnableTotalRecordCount
EnableTotalRecordCount = request.EnableTotalRecordCount,
ExcludeItemIds = request.GetExcludeItemIds()
};
if (!string.IsNullOrWhiteSpace(request.Ids))

View File

@ -140,7 +140,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
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;
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;
}
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)
{
foreach (var header in options.RequestHeaders.ToList())

View File

@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
{
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)
{
@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
.Where(i => i != null)
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
.FirstOrDefault();
return availableUpdate ?? new CheckForUpdateResult
{
IsUpdateAvailable = false

View File

@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the enhanced image.

View File

@ -1878,7 +1878,7 @@ namespace MediaBrowser.Controller.Entities
return new ItemImageInfo
{
Path = path,
DateModified = FileSystem.GetLastWriteTimeUtc(path),
DateModified = chapter.ImageDateModified,
Type = imageType
};
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Providers;
using System;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using System.Linq;
using System.Runtime.Serialization;
@ -17,7 +18,26 @@ namespace MediaBrowser.Controller.Entities
}
}
[IgnoreDataMember]
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()
{

View File

@ -760,11 +760,6 @@ namespace MediaBrowser.Controller.Entities
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
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))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
@ -859,24 +854,6 @@ namespace MediaBrowser.Controller.Entities
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))
{
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");

View File

@ -1,4 +1,6 @@

using System;
namespace MediaBrowser.Controller.Entities
{
public interface IHasSeries
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Entities
/// Gets the name of the series.
/// </summary>
/// <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();
}
}

View File

@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
public Episode()
{
RemoteTrailers = new List<MediaUrl>();
@ -25,11 +24,11 @@ namespace MediaBrowser.Controller.Entities.TV
public List<Guid> RemoteTrailerIds { get; set; }
public List<MediaUrl> RemoteTrailers { get; set; }
/// <summary>
/// Gets the season in which it aired.
/// </summary>
/// <value>The aired season.</value>
public int? AirsBeforeSeasonNumber { get; set; }
/// <summary>
/// Gets the season in which it aired.
/// </summary>
/// <value>The aired season.</value>
public int? AirsBeforeSeasonNumber { get; set; }
public int? AirsAfterSeasonNumber { 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.
/// </summary>
/// <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]
protected override bool SupportsOwnedItems
@ -166,13 +174,27 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
public string SeriesName
{
get
{
var series = Series;
return series == null ? null : series.Name;
}
public string SeriesName { get; set; }
[IgnoreDataMember]
public string SeasonName { get; set; }
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>
@ -235,20 +257,14 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
public Guid? SeasonId
{
get
{
// First see if the parent is a Season
var season = Season;
if (season != null)
{
return season.Id;
}
return null;
}
public Guid? SeasonId { get; set; }
[IgnoreDataMember]
public Guid? SeriesId { get; set; }
public Guid? FindSeriesId()
{
var series = Series;
return series == null ? (Guid?)null : series.Id;
}
public override IEnumerable<Guid> GetAncestorIds()

View File

@ -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
protected override IEnumerable<string> GetIndexByOptions()
{
@ -235,13 +244,21 @@ namespace MediaBrowser.Controller.Entities.TV
}
[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 ? null : series.Name;
}
var series = Series;
return series == null ? SeriesName : series.Name;
}
public Guid? FindSeriesId()
{
var series = Series;
return series == null ? (Guid?)null : series.Id;
}
/// <summary>

View File

@ -28,6 +28,8 @@ namespace MediaBrowser.Controller.Net
/// <returns>System.Object.</returns>
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null);
/// <summary>
/// Gets the optimized result.
/// </summary>

View File

@ -366,6 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
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 (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))

View File

@ -120,6 +120,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
bool preserveOriginalTimestamps,
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)
.ConfigureAwait(false);
@ -141,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex,
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 mediaSource = mediaSources
.First(i => string.Equals(i.Id, mediaSourceId));
.First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
var subtitleStream = mediaSource.MediaStreams
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
@ -609,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
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);
@ -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>
/// Sets the ass font.
/// </summary>

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<PropertyChanged />
</Weavers>

View File

@ -36,6 +36,8 @@
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<RestorePackages>true</RestorePackages>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -617,9 +619,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link>
</Compile>
@ -1233,13 +1232,6 @@
<PostBuildEvent>
</PostBuildEvent>
</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.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@ -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>

View File

@ -591,9 +591,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link>
</Compile>

View File

@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The cache path.</value>
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>
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.

View File

@ -214,7 +214,6 @@ namespace MediaBrowser.Model.Configuration
Migrations = new string[] { };
SqliteCacheSize = 0;
EnableCustomPathSubFolders = true;
EnableLocalizedGuids = true;
DisplaySpecialsWithinSeasons = true;

View File

@ -11,8 +11,14 @@ namespace MediaBrowser.Model.Dlna
public AudioOptions()
{
Context = EncodingContext.Streaming;
EnableDirectPlay = true;
EnableDirectStream = true;
}
public bool EnableDirectPlay { get; set; }
public bool EnableDirectStream { get; set; }
public string ItemId { get; set; }
public List<MediaSourceInfo> MediaSources { get; set; }
public DeviceProfile Profile { get; set; }

View File

@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Dlna
public ProfileCondition[] Conditions { get; set; }
public ProfileCondition[] ApplyConditions { get; set; }
[XmlAttribute("codec")]
public string Codec { get; set; }
@ -20,6 +22,7 @@ namespace MediaBrowser.Model.Dlna
public CodecProfile()
{
Conditions = new ProfileCondition[] {};
ApplyConditions = new ProfileCondition[] { };
}
public List<string> GetCodecs()

View File

@ -131,6 +131,11 @@ namespace MediaBrowser.Model.Dlna
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)
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
@ -138,27 +143,36 @@ namespace MediaBrowser.Model.Dlna
// Make sure audio codec profiles are satisfied
if (!string.IsNullOrEmpty(audioCodec))
{
ConditionProcessor conditionProcessor = new ConditionProcessor();
List<ProfileCondition> conditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
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;
foreach (ProfileCondition c in conditions)
{
if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate))
if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
all = false;
@ -241,9 +255,23 @@ namespace MediaBrowser.Model.Dlna
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
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)
{
// 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);
}
@ -302,7 +330,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay &&
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
@ -385,8 +413,8 @@ namespace MediaBrowser.Model.Dlna
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
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
bool isEligibleForDirectStream = options.EnableDirectStream && IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
options.Profile.Name ?? "Unknown Profile",
@ -464,17 +492,37 @@ namespace MediaBrowser.Model.Dlna
}
playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex;
ConditionProcessor conditionProcessor = new ConditionProcessor();
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
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);
@ -484,11 +532,42 @@ namespace MediaBrowser.Model.Dlna
{
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);
@ -667,9 +746,23 @@ namespace MediaBrowser.Model.Dlna
{
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>();
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
foreach (CodecProfile i in profile.CodecProfiles)
{
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)
{
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);

View File

@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Dto
/// This holds information about a BaseItem in a format that is convenient for the client.
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
public class BaseItemDto : IHasProviderIds, IHasPropertyChangedEvent, IItemDto, IHasServerId, IHasSyncInfo
public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId, IHasSyncInfo
{
/// <summary>
/// Gets or sets the name.
@ -114,6 +114,8 @@ namespace MediaBrowser.Model.Dto
/// <value>The synchronize percent.</value>
public double? SyncPercent { get; set; }
public string Container { get; set; }
/// <summary>
/// Gets or sets the DVD season number.
/// </summary>
@ -954,6 +956,16 @@ namespace MediaBrowser.Model.Dto
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>
/// Gets a value indicating whether this instance has primary image.
/// </summary>
@ -1099,11 +1111,6 @@ namespace MediaBrowser.Model.Dto
}
}
/// <summary>
/// Occurs when [property changed].
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Gets or sets the program identifier.
/// </summary>

View File

@ -1,5 +1,4 @@
using MediaBrowser.Model.Extensions;
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
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
/// </summary>
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
public class BaseItemPerson : IHasPropertyChangedEvent
public class BaseItemPerson
{
/// <summary>
/// Gets or sets the name.
@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
return PrimaryImageTag != null;
}
}
/// <summary>
/// Occurs when [property changed].
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@ -1,7 +1,5 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics;
using System.Runtime.Serialization;
using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dto
{
@ -9,7 +7,7 @@ namespace MediaBrowser.Model.Dto
/// Class ChapterInfo
/// </summary>
[DebuggerDisplay("Name = {Name}")]
public class ChapterInfoDto : IHasPropertyChangedEvent
public class ChapterInfoDto
{
/// <summary>
/// Gets or sets the start position ticks.
@ -38,7 +36,5 @@ namespace MediaBrowser.Model.Dto
{
get { return ImageTag != null; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@ -1,6 +1,5 @@
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Users;
using System;
using System.ComponentModel;
@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Dto
/// Class UserDto
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
public class UserDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
public class UserDto : IItemDto, IHasServerId
{
/// <summary>
/// Gets or sets the name.
@ -141,11 +140,6 @@ namespace MediaBrowser.Model.Dto
Policy = new UserPolicy();
}
/// <summary>
/// Occurs when [property changed].
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return Name ?? base.ToString();

View File

@ -1,5 +1,4 @@
using MediaBrowser.Model.Extensions;
using System;
using System;
using System.ComponentModel;
namespace MediaBrowser.Model.Dto
@ -7,7 +6,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Class UserItemDataDto
/// </summary>
public class UserItemDataDto : IHasPropertyChangedEvent
public class UserItemDataDto
{
/// <summary>
/// Gets or sets the rating.
@ -74,7 +73,5 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The item identifier.</value>
public string ItemId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@ -1,4 +1,5 @@

using System;
namespace MediaBrowser.Model.Entities
{
/// <summary>
@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
public DateTime ImageDateModified { get; set; }
}
}

View File

@ -1,21 +1,14 @@
using MediaBrowser.Model.Drawing;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Entities
{
/// <summary>
/// Defines the display preferences for any item that supports them (usually Folders)
/// </summary>
public class DisplayPreferences : IHasPropertyChangedEvent
public class DisplayPreferences
{
/// <summary>
/// Occurs when [property changed].
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The image scale
/// </summary>

View File

@ -1,8 +0,0 @@
using System.ComponentModel;
namespace MediaBrowser.Model.Extensions
{
public interface IHasPropertyChangedEvent : INotifyPropertyChanged
{
}
}

View File

@ -1,17 +1,10 @@
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Extensions;
using System;
using System.ComponentModel;
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>
/// Id of the recording.
/// </summary>

View File

@ -1,6 +1,5 @@
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Library;
using System.Collections.Generic;
using System.ComponentModel;
@ -13,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
/// Class ChannelInfoDto
/// </summary>
[DebuggerDisplay("Name = {Name}, Number = {Number}")]
public class ChannelInfoDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
public class ChannelInfoDto : IItemDto, IHasServerId
{
/// <summary>
/// Gets or sets the name.
@ -120,7 +119,5 @@ namespace MediaBrowser.Model.LiveTv
ImageTags = new Dictionary<ImageType, string>();
MediaSources = new List<MediaSourceInfo>();
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@ -4,6 +4,11 @@ namespace MediaBrowser.Model.LiveTv
{
public class TimerInfoDto : BaseTimerInfoDto
{
public TimerInfoDto()
{
Type = "Timer";
}
/// <summary>
/// Gets or sets the status.
/// </summary>
@ -22,6 +27,8 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The external series timer identifier.</value>
public string ExternalSeriesTimerId { get; set; }
public string Type { get; set; }
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>

View File

@ -229,7 +229,6 @@
<Compile Include="Entities\SortOrder.cs" />
<Compile Include="Events\GenericEventArgs.cs" />
<Compile Include="Extensions\DoubleHelper.cs" />
<Compile Include="Extensions\IHasPropertyChangedEvent.cs" />
<Compile Include="Extensions\IntHelper.cs" />
<Compile Include="Extensions\ListHelper.cs" />
<Compile Include="Extensions\StringHelper.cs" />

View File

@ -197,6 +197,8 @@
/// </summary>
SeriesGenres,
SeriesPrimaryImage,
/// <summary>
/// The series studio
/// </summary>

View File

@ -1,5 +1,4 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -8,7 +7,7 @@ using System.Diagnostics;
namespace MediaBrowser.Model.Session
{
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
public class SessionInfoDto : IHasPropertyChangedEvent
public class SessionInfoDto
{
/// <summary>
/// Gets or sets the supported commands.
@ -116,8 +115,6 @@ namespace MediaBrowser.Model.Session
public TranscodingInfo TranscodingInfo { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public SessionInfoDto()
{
AdditionalUsers = new List<SessionUserInfo>();

View File

@ -72,7 +72,10 @@ namespace MediaBrowser.Providers.MediaInfo
// Try to translate to three character code
// Be flexible and check against both the full and three character versions
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)
{
@ -99,10 +102,12 @@ namespace MediaBrowser.Providers.MediaInfo
private string NormalizeFilenameForSubtitleComparison(string filename)
{
// Try to account for sloppy file naming
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);
return filename;

View File

@ -111,7 +111,8 @@ namespace MediaBrowser.Providers.TV
Name = seasonName,
IndexNumber = seasonNumber,
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
IsVirtualItem = isVirtualItem
IsVirtualItem = isVirtualItem,
SeriesId = series.Id
};
season.SetParent(series);

View File

@ -429,7 +429,9 @@ namespace MediaBrowser.Providers.TV
IndexNumber = episodeNumber,
ParentIndexNumber = seasonNumber,
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);

View File

@ -95,13 +95,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
{
metadataPath = GetInternalMetadataPath();
}
else if (Configuration.EnableCustomPathSubFolders)
{
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
}
else
{
metadataPath = Configuration.MetadataPath;
metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
}
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;

View File

@ -663,29 +663,11 @@ namespace MediaBrowser.Server.Implementations.Dto
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();
}
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()
return images
.Select(p => GetImageCacheTag(item, p))
.Where(i => i != null)
.Take(limit)
.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>
/// Gets the chapter info dto.
/// </summary>
@ -917,7 +852,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
Path = chapterInfo.ImagePath,
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.ForcedSortName = item.ForcedSortName;
}
dto.Container = item.Container;
var hasBudget = item as IHasBudget;
if (hasBudget != null)
@ -1027,7 +963,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0)
{
dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit);
dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
}
if (fields.Contains(ItemFields.ScreenshotImageTags))
@ -1035,7 +971,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
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.IndexNumber = item.IndexNumber;
dto.ParentIndexNumber = item.ParentIndexNumber;
dto.IsFolder = item.IsFolder;
dto.MediaType = item.MediaType;
dto.LocationType = item.LocationType;
@ -1076,15 +1013,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
var hasCriticRating = item as IHasCriticRating;
if (hasCriticRating != null)
{
dto.CriticRating = hasCriticRating.CriticRating;
dto.CriticRating = item.CriticRating;
if (fields.Contains(ItemFields.CriticRatingSummary))
{
dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary;
}
if (fields.Contains(ItemFields.CriticRatingSummary))
{
dto.CriticRatingSummary = item.CriticRatingSummary;
}
var hasTrailers = item as IHasTrailers;
@ -1127,23 +1060,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.ShortOverview))
{
var hasShortOverview = item as IHasShortOverview;
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);
}
dto.ShortOverview = item.ShortOverview;
}
if (fields.Contains(ItemFields.ParentId))
@ -1155,46 +1072,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
dto.ParentIndexNumber = item.ParentIndexNumber;
// 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);
}
}
AddInheritedImages(dto, item, options, owner);
if (fields.Contains(ItemFields.Path))
{
@ -1426,42 +1304,38 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.SeasonId = seasonId.Value.ToString("N");
}
var episodeSeason = episode.Season;
if (episodeSeason != null)
dto.SeasonName = episode.SeasonName;
var seriesId = episode.SeriesId;
if (seriesId.HasValue)
{
if (fields.Contains(ItemFields.SeasonName))
{
dto.SeasonName = episodeSeason.Name;
}
dto.SeriesId = seriesId.Value.ToString("N");
}
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.SeriesId = GetDtoId(episodeSeries);
if (fields.Contains(ItemFields.AirTime))
{
dto.AirTime = episodeSeries.AirTime;
}
if (options.GetImageLimit(ImageType.Thumb) > 0)
{
dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
}
if (options.GetImageLimit(ImageType.Primary) > 0)
//if (fields.Contains(ItemFields.SeriesPrimaryImage))
{
episodeSeries = episodeSeries ?? episode.Series;
if (episodeSeries != null)
{
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();
}
@ -1483,16 +1357,29 @@ namespace MediaBrowser.Server.Implementations.Dto
var season = item as Season;
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.SeriesName = series.Name;
dto.AirTime = series.AirTime;
dto.SeriesStudio = series.Studios.FirstOrDefault();
dto.SeriesId = seriesId.Value.ToString("N");
}
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);
}
@ -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)
{
var path = item.Path;

View File

@ -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);
}
}
}

View File

@ -335,7 +335,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="httpReq">The HTTP req.</param>
/// <param name="url">The URL.</param>
/// <returns>Task.</returns>
protected Task RequestHandler(IHttpRequest httpReq, Uri url)
protected async Task RequestHandler(IHttpRequest httpReq, Uri url)
{
var date = DateTime.Now;
@ -345,7 +345,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
httpRes.StatusCode = 503;
httpRes.Close();
return Task.FromResult(true);
return ;
}
var operationName = httpReq.OperationName;
@ -365,13 +365,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
return Task.FromResult(true);
return;
}
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
return Task.FromResult(true);
return;
}
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.Close();
return Task.FromResult(true);
return;
}
}
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
return Task.FromResult(true);
return;
}
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("../" + DefaultRedirectPath);
return Task.FromResult(true);
return;
}
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
return Task.FromResult(true);
return;
}
if (string.IsNullOrEmpty(localPath))
{
httpRes.RedirectToUrl("/" + DefaultRedirectPath);
return Task.FromResult(true);
return;
}
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("web/pin.html");
return Task.FromResult(true);
return;
}
if (!string.IsNullOrWhiteSpace(GlobalResponse))
@ -427,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpRes.Write(GlobalResponse);
httpRes.Close();
return Task.FromResult(true);
return;
}
var handler = HttpHandlerFactory.GetHandler(httpReq);
@ -443,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
}
var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName);
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
//Matches Exceptions handled in HttpListenerBase.InitTask()
task.ContinueWith(x =>
try
{
await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false);
}
finally
{
httpRes.Close();
var statusCode = httpRes.StatusCode;
var duration = DateTime.Now - date;
@ -458,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
}
}, TaskContinuationOptions.None);
return task;
}
}
return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo)
.AsTaskException();
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
}
/// <summary>

View File

@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
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;
options.CacheKey = cacheKey.GetMD5();
@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
throw error;
}
public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
{
return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
{
public class RangeRequestWriter : IStreamWriter, IHttpResult
public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult
{
/// <summary>
/// Gets or sets the source stream.
@ -168,16 +170,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// </summary>
/// <param name="responseStream">The response stream.</param>
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
{
@ -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 IRequest RequestContext { get; set; }

View File

@ -3,6 +3,7 @@ using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using ServiceStack;
using ServiceStack.Web;
@ -32,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return header.Substring(ap + 1, end - ap - 1);
}
void LoadMultiPart()
async Task LoadMultiPart()
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
return;
var input = 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)
using (var requestStream = GetSubStream(InputStream))
{
if (e.Filename == null)
{
byte[] copy = new byte[e.Length];
//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);
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
input.Position = e.Start;
input.Read(copy, 0, (int)e.Length);
var input = ms;
ms.WriteByte((byte)'\r');
ms.WriteByte((byte)'\n');
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
}
else
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)
{
//
// 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);
if (e.Filename == null)
{
byte[] copy = new byte[e.Length];
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
@ -91,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
files = new HttpFileCollection();
if (IsContentType("multipart/form-data", true))
LoadMultiPart();
else if (
IsContentType("application/x-www-form-urlencoded", true))
LoadWwwForm();
{
var task = LoadMultiPart();
Task.WaitAll(task);
}
else if (IsContentType("application/x-www-form-urlencoded", true))
{
var task = LoadWwwForm();
Task.WaitAll(task);
}
form.Protect();
}
@ -220,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
}
void LoadWwwForm()
async Task LoadWwwForm()
{
using (Stream input = GetSubStream(InputStream))
{
using (StreamReader s = new StreamReader(input, ContentEncoding))
using (var ms = new MemoryStream())
{
StringBuilder key = new StringBuilder();
StringBuilder value = new StringBuilder();
int c;
await input.CopyToAsync(ms).ConfigureAwait(false);
ms.Position = 0;
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;
while ((c = s.Read()) != -1)
if (c == '=')
{
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);
break;
return;
}
else
value.Append((char)c);
}
if (c == -1)
{
else if (c == '&')
AddRawKeyValue(key, value);
return;
}
else
key.Append((char)c);
}
else if (c == '&')
if (c == -1)
AddRawKeyValue(key, value);
else
key.Append((char)c);
}
if (c == -1)
AddRawKeyValue(key, value);
EndSubStream(input);
}
}
}

View File

@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
get
{
return remoteIp ??
(remoteIp = XForwardedFor ??
(NormalizeIp(XRealIp) ??
(remoteIp = (CheckBadChars(XForwardedFor)) ??
(NormalizeIp(CheckBadChars(XRealIp)) ??
(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)
{
if (!string.IsNullOrWhiteSpace(ip))
@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return stream;
}
static void EndSubStream(Stream stream)
{
}
public static string GetHandlerPathIfAny(string listenerUrl)
{
if (listenerUrl == null) return null;

View File

@ -4,13 +4,15 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
{
/// <summary>
/// Class StreamWriter
/// </summary>
public class StreamWriter : IStreamWriter, IHasOptions
public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
{
private ILogger Logger { get; set; }
@ -73,24 +75,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
}
// 256k
private const int BufferSize = 262144;
/// <summary>
/// Writes to.
/// </summary>
/// <param name="responseStream">The response stream.</param>
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
{
@ -107,7 +99,36 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
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;
}
finally

View File

@ -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.
// 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
await Task.Delay(25000).ConfigureAwait(false);
await Task.Delay(45000).ConfigureAwait(false);
string val;
_tempIgnoredPaths.TryRemove(path, out val);

View File

@ -33,6 +33,7 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Extensions;
@ -353,10 +354,6 @@ namespace MediaBrowser.Server.Implementations.Library
private void RegisterItem(Guid id, BaseItem item)
{
if (item.SourceType != SourceType.Library)
{
return;
}
if (item is IItemByName)
{
if (!(item is MusicArtist))
@ -364,14 +361,25 @@ namespace MediaBrowser.Server.Implementations.Library
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))
//{
// return;
//}
else
{
if (item is Photo)
{
return;
}
}
LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
}
@ -782,19 +790,19 @@ namespace MediaBrowser.Server.Implementations.Library
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.
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
return GetItemIds(query)
.Select(GetItemById)
.Where(i => i != null)
.OrderByDescending(i => i.DateCreated)
var query = new InternalItemsQuery
{
Path = path,
IsFolder = isFolder,
SortBy = new[] { ItemSortBy.DateCreated },
SortOrder = SortOrder.Descending,
Limit = 1
};
return GetItemList(query)
.FirstOrDefault();
}
@ -1258,6 +1266,8 @@ namespace MediaBrowser.Server.Implementations.Library
item = RetrieveItem(id);
//_logger.Debug("GetitemById {0}", id);
if (item != null)
{
RegisterItem(item);
@ -1508,7 +1518,7 @@ namespace MediaBrowser.Server.Implementations.Library
UserId = user.Id.ToString("N")
}, CancellationToken.None).Result;
return channelResult.Items;
}
@ -1921,7 +1931,7 @@ namespace MediaBrowser.Server.Implementations.Library
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)
{
return nameValuePair.Value;
@ -2802,6 +2812,11 @@ namespace MediaBrowser.Server.Implementations.Library
private void RemoveContentTypeOverrides(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
}
var removeList = new List<NameValuePair>();
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)

View File

@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities;
using System;
using System.IO;
using System.Linq;
using CommonIO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
public class PhotoResolver : ItemResolver<Photo>
{
private readonly IImageProcessor _imageProcessor;
public PhotoResolver(IImageProcessor imageProcessor)
private readonly ILibraryManager _libraryManager;
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
}
/// <summary>
@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
/// <returns>Trailer.</returns>
protected override Photo Resolve(ItemResolveArgs args)
{
// Must be an image file within a photo collection
if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
!args.IsDirectory &&
IsImageFile(args.Path, _imageProcessor))
if (!args.IsDirectory)
{
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;
}
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 =
{
"folder",
@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
"landscape",
"fanart",
"backdrop",
"poster"
"poster",
"cover"
};
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)

View File

@ -31,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
}
var season = parent as Season;
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
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
// 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);
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;
}

View File

@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
if (args.Parent is Series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var series = ((Series)args.Parent);
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)

View File

@ -1214,8 +1214,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
list.Add(item);
_libraryManager.RegisterItem(item);
}
catch (OperationCanceledException)
{

View File

@ -17,6 +17,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
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)
{
using (var stream = await _httpClient.Get(new HttpRequestOptions()
try
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
using (var stream = await _httpClient.Get(new HttpRequestOptions()
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
}))
{
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;
}
// Test it by pulling down the lineup
using (var stream = await _httpClient.Get(new HttpRequestOptions
try
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = CancellationToken.None
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
// Test it by pulling down the lineup
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
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;
}
}

View File

@ -56,8 +56,8 @@
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.6012.15754, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.52\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
<Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MoreLinq">
@ -73,8 +73,8 @@
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll</HintPath>
<Reference Include="SocketHttpListener, Version=1.0.6046.26351, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@ -156,6 +156,7 @@
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
<Compile Include="HttpServer\AsyncStreamWriterFunc.cs" />
<Compile Include="HttpServer\IHttpListener.cs" />
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
<Compile Include="HttpServer\ContainerAdapter.cs" />
@ -757,9 +758,7 @@
<EmbeddedResource Include="Localization\iso6392.txt" />
<EmbeddedResource Include="Localization\Ratings\be.txt" />
</ItemGroup>
<ItemGroup>
<Folder Include="HttpServer\NetListener\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.

View File

@ -152,6 +152,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
chapter.ImagePath = path;
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true;
}
catch (Exception ex)
@ -170,6 +171,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
{
chapter.ImagePath = path;
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true;
}
}

View File

@ -155,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_logger.Debug("Upgrading schema for {0} items", numItems);
var list = new List<BaseItem>();
foreach (var itemId in itemIds)
{
cancellationToken.ThrowIfCancellationRequested();
@ -166,27 +168,50 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (item != null)
{
try
{
await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error saving item", ex);
}
list.Add(item);
}
}
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++;
double percent = numComplete;
percent /= numItems;
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);
}

View File

@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _updateInheritedRatingCommand;
private IDbCommand _updateInheritedTagsCommand;
public const int LatestSchemaVersion = 97;
public const int LatestSchemaVersion = 108;
/// <summary>
/// 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", "SeriesName", "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, "ItemValues", "CleanValue", "Text");
_connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
string[] postQueries =
{
@ -403,7 +409,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Album",
"CriticRating",
"CriticRatingSummary",
"IsVirtualItem"
"IsVirtualItem",
"SeriesName",
"SeasonName",
"SeasonId",
"SeriesId",
"SeriesSortName"
};
private readonly string[] _mediaStreamSaveColumns =
@ -523,7 +534,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Album",
"IsVirtualItem",
"SeriesName",
"UserDataKey"
"UserDataKey",
"SeasonName",
"SeasonId",
"SeriesId",
"SeriesSortName"
};
_saveItemCommand = _connection.CreateCommand();
_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, "@Name");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
// MediaStreams
_deleteStreamsCommand = _connection.CreateCommand();
@ -945,7 +961,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
var hasSeries = item as IHasSeries;
if (hasSeries != null)
{
_saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName;
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
}
else
{
@ -954,6 +970,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
_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.ExecuteNonQuery();
@ -1376,6 +1415,44 @@ namespace MediaBrowser.Server.Implementations.Persistence
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;
}
@ -1437,7 +1514,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
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;
@ -1470,7 +1547,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
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, "@ChapterIndex", DbType.Int32).Value = index;
@ -1508,6 +1585,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
chapter.ImagePath = reader.GetString(2);
}
if (!reader.IsDBNull(3))
{
chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
}
return chapter;
}
@ -1567,6 +1649,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
_saveChapterCommand.GetParameter(3).Value = chapter.Name;
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
_saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
_saveChapterCommand.Transaction = transaction;
@ -2061,7 +2144,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
if (query.User != null)
{
query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random };
query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
}
else
{
@ -2986,6 +3069,39 @@ namespace MediaBrowser.Server.Implementations.Persistence
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)
{
whereClauses.Add("MediaType=@MediaTypes");
@ -4063,6 +4179,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
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();
// First delete
@ -4071,7 +4194,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteProviderIdsCommand.ExecuteNonQuery();
foreach (var pair in values)
foreach (var pair in newValues)
{
_saveProviderIdsCommand.GetParameter(0).Value = itemId;
_saveProviderIdsCommand.GetParameter(1).Value = pair.Key;

View File

@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
return;
}
if (newIndex > oldIndex)
{
newIndex--;
}
var item = playlist.LinkedChildren[oldIndex];
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);
}

View File

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
@ -21,28 +20,9 @@ namespace MediaBrowser.Server.Implementations.Sorting
private string GetValue(BaseItem item)
{
Series series = null;
var hasSeries = item as IHasSeries;
var season = item as Season;
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;
return hasSeries != null ? hasSeries.SeriesSortName : null;
}
/// <summary>

View File

@ -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)
{
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)
{
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)))
.OrderByDescending(i => i.Item2)
.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)
@ -143,7 +154,6 @@ namespace MediaBrowser.Server.Implementations.TV
SortOrder = SortOrder.Descending,
IsPlayed = true,
Limit = 1,
IsVirtualItem = false,
ParentIndexNumberNotEquals = 0
}).FirstOrDefault();

View File

@ -54,11 +54,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
{
return series;
}
var episodeSeason = episode.Season;
if (episodeSeason != null)
{
return episodeSeason;
}
return episode;
}

View File

@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
{
return series;
}
var episodeSeason = episode.Season;
if (episodeSeason != null)
{
return episodeSeason;
}
return episode;
}

View File

@ -4,10 +4,10 @@
<package id="Emby.XmlTv" version="1.0.0.55" 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="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="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" 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>

View File

@ -274,7 +274,7 @@ namespace MediaBrowser.Server.Startup.Common
{
get
{
return "Media Browser Server";
return "Emby Server";
}
}

View File

@ -12,6 +12,7 @@ using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Threading;
@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication
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;
}
@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication
/// <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)
{
var filename = Path.GetFileName(applicationPath);
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
{
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)
{
@ -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;
}
@ -593,14 +644,32 @@ namespace MediaBrowser.ServerApplication
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
{
var version = ImageMagickEncoder.GetVersion();
return;
var subkey = Environment.Is64BitProcess
? "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)
{
logger.ErrorException("Error loading ImageMagick", ex);
logger.ErrorException("Error getting .NET Framework version", ex);
return;
}
try

View File

@ -88,6 +88,7 @@
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Management" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />

View File

@ -349,21 +349,16 @@ namespace MediaBrowser.WebDashboard.Api
}
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
//_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
//_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
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);
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
//DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{

View File

@ -104,6 +104,9 @@
<Content Include="dashboard-ui\components\apphost.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\categorysyncbuttons.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -902,9 +905,6 @@
<Content Include="dashboard-ui\scripts\favorites.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\librarylist.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\librarymenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -1043,42 +1043,6 @@
<Content Include="dashboard-ui\userpassword.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -1674,15 +1638,6 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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" />
</ItemGroup>
<ItemGroup />