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; return _imageEncoder.SupportedOutputFormats;
} }
public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options) public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
{ {
if (options == null) if (options == null)
{ {
@ -178,14 +178,13 @@ namespace Emby.Drawing
} }
var originalImagePath = originalImage.Path; var originalImagePath = originalImage.Path;
var dateModified = originalImage.DateModified;
if (!_imageEncoder.SupportsImageEncoding) if (!_imageEncoder.SupportsImageEncoding)
{ {
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
var dateModified = originalImage.DateModified;
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding) if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
{ {
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@ -211,7 +210,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath)) if (options.HasDefaultOptions(originalImagePath))
{ {
// Just spit out the original file if all the options are default // Just spit out the original file if all the options are default
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
ImageSize? originalImageSize; ImageSize? originalImageSize;
@ -221,7 +220,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value)) if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
{ {
// Just spit out the original file if all the options are default // Just spit out the original file if all the options are default
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
} }
catch catch
@ -235,10 +234,6 @@ namespace Emby.Drawing
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]); var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
var semaphore = GetLock(cacheFilePath);
await semaphore.WaitAsync().ConfigureAwait(false);
var imageProcessingLockTaken = false; var imageProcessingLockTaken = false;
try try
@ -251,15 +246,20 @@ namespace Emby.Drawing
var newHeight = Convert.ToInt32(newSize.Height); var newHeight = Convert.ToInt32(newSize.Height);
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true; imageProcessingLockTaken = true;
_imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat); _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
CopyFile(tmpPath, cacheFilePath);
return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
} }
return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath)); return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -267,7 +267,7 @@ namespace Emby.Drawing
_logger.ErrorException("Error encoding image", ex); _logger.ErrorException("Error encoding image", ex);
// Just spit out the original file if all the options are default // Just spit out the original file if all the options are default
return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
finally finally
{ {
@ -275,8 +275,18 @@ namespace Emby.Drawing
{ {
_imageProcessingSemaphore.Release(); _imageProcessingSemaphore.Release();
} }
}
}
private void CopyFile(string src, string destination)
{
try
{
File.Copy(src, destination, true);
}
catch
{
semaphore.Release();
} }
} }
@ -412,14 +422,9 @@ namespace Emby.Drawing
var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath)); var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
var semaphore = GetLock(croppedImagePath);
await semaphore.WaitAsync().ConfigureAwait(false);
// Check again in case of contention // Check again in case of contention
if (_fileSystem.FileExists(croppedImagePath)) if (_fileSystem.FileExists(croppedImagePath))
{ {
semaphore.Release();
return GetResult(croppedImagePath); return GetResult(croppedImagePath);
} }
@ -428,11 +433,15 @@ namespace Emby.Drawing
try try
{ {
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath)); _fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true; imageProcessingLockTaken = true;
_imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath); _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
CopyFile(tmpPath, croppedImagePath);
return GetResult(tmpPath);
} }
catch (NotImplementedException) catch (NotImplementedException)
{ {
@ -452,11 +461,7 @@ namespace Emby.Drawing
{ {
_imageProcessingSemaphore.Release(); _imageProcessingSemaphore.Release();
} }
semaphore.Release();
} }
return GetResult(croppedImagePath);
} }
private Tuple<string, DateTime> GetResult(string path) private Tuple<string, DateTime> GetResult(string path)

View File

@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
{ {
var list = new List<ImageInfo>(); var list = new List<ImageInfo>();
foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type))) var itemImages = item.ImageInfos;
foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
{ {
var info = GetImageInfo(item, image, null); var info = GetImageInfo(item, image, null);
@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
} }
} }
foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages)) foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
{ {
var index = 0; var index = 0;
// Prevent implicitly captured closure // Prevent implicitly captured closure
var currentImageType = imageType; var currentImageType = imageType;
foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType)) foreach (var image in itemImages.Where(i => i.Type == currentImageType))
{ {
var info = GetImageInfo(item, image, index); var info = GetImageInfo(item, image, index);
@ -636,6 +638,7 @@ namespace MediaBrowser.Api.Images
CacheDuration = cacheDuration, CacheDuration = cacheDuration,
ResponseHeaders = headers, ResponseHeaders = headers,
ContentType = imageResult.Item2, ContentType = imageResult.Item2,
DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest, IsHeadRequest = isHeadRequest,
Path = imageResult.Item1, Path = imageResult.Item1,

View File

@ -286,19 +286,25 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state) protected string GetH264Encoder(StreamState state)
{ {
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) || // Only use alternative encoders for video files.
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
if (state.VideoType == VideoType.VideoFile)
{ {
return "h264_qsv"; if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
} string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
return "h264_qsv";
}
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
{ {
return "h264_nvenc"; return "h264_nvenc";
} }
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
{ {
return "h264_omx"; return "h264_omx";
}
} }
return "libx264"; return "libx264";
@ -843,6 +849,14 @@ namespace MediaBrowser.Api.Playback
return null; return null;
} }
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
if (state.VideoType != VideoType.VideoFile)
{
return null;
}
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{ {
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))

View File

@ -13,6 +13,7 @@ using ServiceStack.Web;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
@ -336,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
state.Dispose(); state.Dispose();
} }
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job); var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
result.Options["Content-Type"] = contentType; outputHeaders["Content-Type"] = contentType;
// Add the response headers to the result object // Add the response headers to the result object
foreach (var item in responseHeaders) foreach (var item in responseHeaders)
{ {
result.Options[item.Key] = item.Value; outputHeaders[item.Key] = item.Value;
} }
return result; Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream);
return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
} }
finally finally
{ {

View File

@ -48,21 +48,19 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <param name="responseStream">The response stream.</param> /// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream) public void WriteTo(Stream responseStream)
{ {
WriteToInternal(responseStream); var task = WriteToAsync(responseStream);
Task.WaitAll(task);
} }
/// <summary> /// <summary>
/// Writes to async. /// Writes to.
/// </summary> /// </summary>
/// <param name="responseStream">The response stream.</param> /// <param name="responseStream">The response stream.</param>
/// <returns>Task.</returns> public async Task WriteToAsync(Stream responseStream)
private void WriteToInternal(Stream responseStream)
{ {
try try
{ {
var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream); await new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream).ConfigureAwait(false);
Task.WaitAll(task);
} }
catch (IOException) catch (IOException)
{ {
@ -110,11 +108,11 @@ namespace MediaBrowser.Api.Playback.Progressive
var eofCount = 0; var eofCount = 0;
long position = 0; long position = 0;
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false)) using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{ {
while (eofCount < 15) while (eofCount < 15)
{ {
CopyToInternal(fs, outputStream, BufferSize); await CopyToInternal(fs, outputStream, BufferSize).ConfigureAwait(false);
var fsPosition = fs.Position; var fsPosition = fs.Position;
@ -140,11 +138,11 @@ namespace MediaBrowser.Api.Playback.Progressive
} }
} }
private void CopyToInternal(Stream source, Stream destination, int bufferSize) private async Task CopyToInternal(Stream source, Stream destination, int bufferSize)
{ {
var array = new byte[bufferSize]; var array = new byte[bufferSize];
int count; int count;
while ((count = source.Read(array, 0, array.Length)) != 0) while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
{ {
//if (_job != null) //if (_job != null)
//{ //{
@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// } // }
//} //}
destination.Write(array, 0, count); await destination.WriteAsync(array, 0, count).ConfigureAwait(false);
_bytesWritten += count; _bytesWritten += count;

View File

@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
/// <summary> Manager for library. </summary> /// <summary> Manager for library. </summary>
private readonly ILibraryManager _libraryManager; ///< Manager for library private readonly ILibraryManager _libraryManager; ///< Manager for library
/// <summary> The localization. </summary> /// <summary> The localization. </summary>
private readonly ILocalizationManager _localization; ///< The localization private readonly ILocalizationManager _localization; ///< The localization
@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets the given request. </summary> /// <summary> Gets the given request. </summary>
/// <param name="request"> The request. </param> /// <param name="request"> The request. </param>
/// <returns> A Task&lt;object&gt; </returns> /// <returns> A Task&lt;object&gt; </returns>
public async Task<object> Get(GetActivityLogs request) public object Get(GetActivityLogs request)
{ {
request.DisplayType = "Screen"; request.DisplayType = "Screen";
ReportResult result = await GetReportActivities(request).ConfigureAwait(false); ReportResult result = GetReportActivities(request);
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
return null; return null;
request.DisplayType = "Screen"; request.DisplayType = "Screen";
var reportResult = await GetReportResult(request); var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
var reportResult = await GetReportResult(request, user);
return ToOptimizedResult(reportResult); return ToOptimizedResult(reportResult);
} }
@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
if (string.IsNullOrEmpty(request.IncludeItemTypes)) if (string.IsNullOrEmpty(request.IncludeItemTypes))
return null; return null;
request.DisplayType = "Screen"; request.DisplayType = "Screen";
var reportResult = await GetReportStatistic(request); var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
var reportResult = await GetReportStatistic(request, user);
return ToOptimizedResult(reportResult); return ToOptimizedResult(reportResult);
} }
@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename); headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
headers["Content-Encoding"] = "UTF-8"; headers["Content-Encoding"] = "UTF-8";
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
ReportResult result = null; ReportResult result = null;
switch (reportViewType) switch (reportViewType)
{ {
@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
case ReportViewType.ReportData: case ReportViewType.ReportData:
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
ReportBuilder dataBuilder = new ReportBuilder(_libraryManager); ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false); QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
result = dataBuilder.GetResult(queryResult.Items, request); result = dataBuilder.GetResult(queryResult.Items, request);
result.TotalRecordCount = queryResult.TotalRecordCount; result.TotalRecordCount = queryResult.TotalRecordCount;
break; break;
case ReportViewType.ReportActivities: case ReportViewType.ReportActivities:
result = await GetReportActivities(request).ConfigureAwait(false); result = GetReportActivities(request);
break; break;
} }
@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
break; break;
} }
object ro = ResultFactory.GetResult(returnResult, contentType, headers); return ResultFactory.GetResult(returnResult, contentType, headers);
return ro;
} }
#endregion #endregion
#region [Private Methods]
/// <summary> Gets items query. </summary>
/// <param name="request"> The request. </param>
/// <param name="user"> The user. </param>
/// <returns> The items query. </returns>
private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user) private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
{ {
var query = new InternalItemsQuery var query = new InternalItemsQuery(user)
{ {
User = user,
IsPlayed = request.IsPlayed, IsPlayed = request.IsPlayed,
MediaTypes = request.GetMediaTypes(), MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(), IncludeItemTypes = request.GetIncludeItemTypes(),
@ -231,6 +226,7 @@ namespace MediaBrowser.Api.Reports
Tags = request.GetTags(), Tags = request.GetTags(),
OfficialRatings = request.GetOfficialRatings(), OfficialRatings = request.GetOfficialRatings(),
Genres = request.GetGenres(), Genres = request.GetGenres(),
GenreIds = request.GetGenreIds(),
Studios = request.GetStudios(), Studios = request.GetStudios(),
StudioIds = request.GetStudioIds(), StudioIds = request.GetStudioIds(),
Person = request.Person, Person = request.Person,
@ -245,9 +241,11 @@ namespace MediaBrowser.Api.Reports
MaxPlayers = request.MaxPlayers, MaxPlayers = request.MaxPlayers,
MinCommunityRating = request.MinCommunityRating, MinCommunityRating = request.MinCommunityRating,
MinCriticRating = request.MinCriticRating, MinCriticRating = request.MinCriticRating,
ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
ParentIndexNumber = request.ParentIndexNumber, ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason, AiredDuringSeason = request.AiredDuringSeason,
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
EnableTotalRecordCount = request.EnableTotalRecordCount
}; };
if (!string.IsNullOrWhiteSpace(request.Ids)) if (!string.IsNullOrWhiteSpace(request.Ids))
@ -357,98 +355,111 @@ namespace MediaBrowser.Api.Reports
query.AlbumNames = request.Albums.Split('|'); query.AlbumNames = request.Albums.Split('|');
} }
if (request.HasQueryLimit == false)
{
query.StartIndex = null;
query.Limit = null;
}
return query; return query;
} }
/// <summary> Gets query result. </summary> private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
/// <param name="request"> The request. </param>
/// <returns> The query result. </returns>
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
{ {
// Placeholder in case needed later // all report queries currently need this because it's not being specified
request.Recursive = true; request.Recursive = true;
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
var parentItem = string.IsNullOrEmpty(request.ParentId) ?
(user == null ? _libraryManager.RootFolder : user.RootFolder) :
_libraryManager.GetItemById(request.ParentId);
var item = string.IsNullOrEmpty(request.ParentId) ? var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder : user == null ? _libraryManager.RootFolder : user.RootFolder :
parentItem; _libraryManager.GetItemById(request.ParentId);
IEnumerable<BaseItem> items; if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
{
//item = user == null ? _libraryManager.RootFolder : user.RootFolder;
}
else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
{
item = user == null ? _libraryManager.RootFolder : user.RootFolder;
}
// Default list type = children
var folder = item as Folder;
if (folder == null)
{
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
}
if (!string.IsNullOrEmpty(request.Ids))
{
request.Recursive = true;
var query = GetItemsQuery(request, user);
var result = await folder.GetItems(query).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(request.SortBy))
{
var ids = query.ItemIds.ToList();
// Try to preserve order
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
return result;
}
if (request.Recursive) if (request.Recursive)
{ {
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
return result;
} }
else
if (user == null)
{ {
if (user == null) return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
{
var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
return result;
}
var userRoot = item as UserRootFolder;
if (userRoot == null)
{
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
return result;
}
items = ((Folder)item).GetChildren(user, true);
} }
return new QueryResult<BaseItem> { Items = items.ToArray() }; var userRoot = item as UserRootFolder;
if (userRoot == null)
{
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
IEnumerable<BaseItem> items = folder.GetChildren(user, true);
var itemsArray = items.ToArray();
return new QueryResult<BaseItem>
{
Items = itemsArray,
TotalRecordCount = itemsArray.Length
};
} }
#region [Private Methods]
/// <summary> Gets report activities. </summary> /// <summary> Gets report activities. </summary>
/// <param name="request"> The request. </param> /// <param name="request"> The request. </param>
/// <returns> The report activities. </returns> /// <returns> The report activities. </returns>
private Task<ReportResult> GetReportActivities(IReportsDownload request) private ReportResult GetReportActivities(IReportsDownload request)
{ {
return Task<ReportResult>.Run(() => DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
{ (DateTime?)null :
DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ? DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
(DateTime?)null :
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
QueryResult<ActivityLogEntry> queryResult; QueryResult<ActivityLogEntry> queryResult;
if (request.HasQueryLimit) if (request.HasQueryLimit)
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit); queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
else else
queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null); queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
//var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit); //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
var result = builder.GetResult(queryResult, request);
result.TotalRecordCount = queryResult.TotalRecordCount;
return result;
});
ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
var result = builder.GetResult(queryResult, request);
result.TotalRecordCount = queryResult.TotalRecordCount;
return result;
} }
/// <summary> Gets report result. </summary> /// <summary> Gets report result. </summary>
/// <param name="request"> The request. </param> /// <param name="request"> The request. </param>
/// <returns> The report result. </returns> /// <returns> The report result. </returns>
private async Task<ReportResult> GetReportResult(GetItemReport request) private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
{ {
ReportBuilder reportBuilder = new ReportBuilder(_libraryManager); ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false); QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request); ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
reportResult.TotalRecordCount = queryResult.TotalRecordCount; reportResult.TotalRecordCount = queryResult.TotalRecordCount;
@ -458,10 +469,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets report statistic. </summary> /// <summary> Gets report statistic. </summary>
/// <param name="request"> The request. </param> /// <param name="request"> The request. </param>
/// <returns> The report statistic. </returns> /// <returns> The report statistic. </returns>
private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request) private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
{ {
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false); QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager); ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5); ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);

View File

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

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")] [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsInBoxSet { get; set; } public bool? IsInBoxSet { get; set; }
public string ExcludeItemIds { get; set; }
public bool EnableTotalRecordCount { get; set; } public bool EnableTotalRecordCount { get; set; }
/// <summary> /// <summary>
@ -367,6 +369,11 @@ namespace MediaBrowser.Api.UserLibrary
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
} }
public string[] GetExcludeItemIds()
{
return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
public string[] GetExcludeItemTypes() public string[] GetExcludeItemTypes()
{ {
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

View File

@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
{ {
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
var result = await GetItemsToSerialize(request, user).ConfigureAwait(false); var result = await GetQueryResult(request, user).ConfigureAwait(false);
if (result == null) if (result == null)
{ {
@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns> /// <returns>IEnumerable{BaseItem}.</returns>
private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user) private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
{ {
var item = string.IsNullOrEmpty(request.ParentId) ? var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder : user == null ? _libraryManager.RootFolder : user.RootFolder :
@ -263,7 +263,8 @@ namespace MediaBrowser.Api.UserLibrary
ParentIndexNumber = request.ParentIndexNumber, ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason, AiredDuringSeason = request.AiredDuringSeason,
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater, AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
EnableTotalRecordCount = request.EnableTotalRecordCount EnableTotalRecordCount = request.EnableTotalRecordCount,
ExcludeItemIds = request.GetExcludeItemIds()
}; };
if (!string.IsNullOrWhiteSpace(request.Ids)) if (!string.IsNullOrWhiteSpace(request.Ids))

View File

@ -140,7 +140,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private WebRequest GetRequest(HttpRequestOptions options, string method) private WebRequest GetRequest(HttpRequestOptions options, string method)
{ {
var request = CreateWebRequest(options.Url); var url = options.Url;
var uriAddress = new Uri(url);
var userInfo = uriAddress.UserInfo;
if (!string.IsNullOrWhiteSpace(userInfo))
{
_logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
url = url.Replace(userInfo + "@", string.Empty);
}
var request = CreateWebRequest(url);
var httpWebRequest = request as HttpWebRequest; var httpWebRequest = request as HttpWebRequest;
if (httpWebRequest != null) if (httpWebRequest != null)
@ -183,9 +193,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
} }
} }
if (!string.IsNullOrWhiteSpace(userInfo))
{
var parts = userInfo.Split(':');
if (parts.Length == 2)
{
request.Credentials = GetCredential(url, parts[0], parts[1]);
request.PreAuthenticate = true;
}
}
return request; return request;
} }
private CredentialCache GetCredential(string url, string username, string password)
{
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
CredentialCache credentialCache = new CredentialCache();
credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
return credentialCache;
}
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options) private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
{ {
foreach (var header in options.RequestHeaders.ToList()) foreach (var header in options.RequestHeaders.ToList())

View File

@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
{ {
if (updateLevel == PackageVersionClass.Release) if (updateLevel == PackageVersionClass.Release)
{ {
obj = obj.Where(i => !i.prerelease).ToArray(); // Technically all we need to do is check that it's not pre-release
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
} }
else if (updateLevel == PackageVersionClass.Beta) else if (updateLevel == PackageVersionClass.Beta)
{ {
@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
.Where(i => i != null) .Where(i => i != null)
.OrderByDescending(i => Version.Parse(i.AvailableVersion)) .OrderByDescending(i => Version.Parse(i.AvailableVersion))
.FirstOrDefault(); .FirstOrDefault();
return availableUpdate ?? new CheckForUpdateResult return availableUpdate ?? new CheckForUpdateResult
{ {
IsUpdateAvailable = false IsUpdateAvailable = false

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Providers; using System;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using System.Linq; using System.Linq;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -17,7 +18,26 @@ namespace MediaBrowser.Controller.Entities
} }
} }
[IgnoreDataMember]
public string SeriesName { get; set; } public string SeriesName { get; set; }
[IgnoreDataMember]
public Guid? SeriesId { get; set; }
[IgnoreDataMember]
public string SeriesSortName { get; set; }
public string FindSeriesSortName()
{
return SeriesSortName;
}
public string FindSeriesName()
{
return SeriesName;
}
public Guid? FindSeriesId()
{
return SeriesId;
}
public override bool CanDownload() public override bool CanDownload()
{ {

View File

@ -760,11 +760,6 @@ namespace MediaBrowser.Controller.Entities
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue"); Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
return true; return true;
} }
if (query.SortBy.Contains(ItemSortBy.SeriesSortName, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
return true;
}
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase)) if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
{ {
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate"); Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
@ -859,24 +854,6 @@ namespace MediaBrowser.Controller.Entities
return true; return true;
} }
if (query.IsMissing.HasValue)
{
Logger.Debug("Query requires post-filtering due to IsMissing");
return true;
}
if (query.IsUnaired.HasValue)
{
Logger.Debug("Query requires post-filtering due to IsUnaired");
return true;
}
if (query.IsVirtualUnaired.HasValue)
{
Logger.Debug("Query requires post-filtering due to IsVirtualUnaired");
return true;
}
if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager)) if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
{ {
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems"); Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");

View File

@ -1,4 +1,6 @@
 
using System;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
public interface IHasSeries public interface IHasSeries
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Entities
/// Gets the name of the series. /// Gets the name of the series.
/// </summary> /// </summary>
/// <value>The name of the series.</value> /// <value>The name of the series.</value>
string SeriesName { get; } string SeriesName { get; set; }
string FindSeriesName();
string SeriesSortName { get; set; }
string FindSeriesSortName();
Guid? SeriesId { get; set; }
Guid? FindSeriesId();
} }
} }

View File

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

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 // Genre, Rating and Stuido will all be the same
protected override IEnumerable<string> GetIndexByOptions() protected override IEnumerable<string> GetIndexByOptions()
{ {
@ -235,13 +244,21 @@ namespace MediaBrowser.Controller.Entities.TV
} }
[IgnoreDataMember] [IgnoreDataMember]
public string SeriesName public string SeriesName { get; set; }
[IgnoreDataMember]
public Guid? SeriesId { get; set; }
public string FindSeriesName()
{ {
get var series = Series;
{ return series == null ? SeriesName : series.Name;
var series = Series; }
return series == null ? null : series.Name;
} public Guid? FindSeriesId()
{
var series = Series;
return series == null ? (Guid?)null : series.Id;
} }
/// <summary> /// <summary>

View File

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

View File

@ -366,6 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return null; return null;
} }
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
if (state.VideoType != VideoType.VideoFile)
{
return null;
}
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{ {
if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))

View File

@ -120,6 +120,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
bool preserveOriginalTimestamps, bool preserveOriginalTimestamps,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(itemId))
{
throw new ArgumentNullException("itemId");
}
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
throw new ArgumentNullException("mediaSourceId");
}
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken) var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -141,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex, int subtitleStreamIndex,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(itemId))
{
throw new ArgumentNullException("itemId");
}
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
throw new ArgumentNullException("mediaSourceId");
}
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources var mediaSource = mediaSources
.First(i => string.Equals(i.Id, mediaSourceId)); .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
var subtitleStream = mediaSource.MediaStreams var subtitleStream = mediaSource.MediaStreams
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex); .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
@ -609,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw; throw;
} }
process.StandardError.BaseStream.CopyToAsync(logFileStream); // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream));
var ranToCompletion = process.WaitForExit(300000); var ranToCompletion = process.WaitForExit(300000);
@ -686,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
} }
private async Task StartStreamingLog(Stream source, Stream target)
{
try
{
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
catch (ObjectDisposedException)
{
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
}
catch (Exception ex)
{
_logger.ErrorException("Error reading ffmpeg log", ex);
}
}
/// <summary> /// <summary>
/// Sets the ass font. /// Sets the ass font.
/// </summary> /// </summary>

View File

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

View File

@ -36,6 +36,8 @@
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<RestorePackages>true</RestorePackages> <RestorePackages>true</RestorePackages>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -617,9 +619,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs"> <Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link> <Link>Extensions\FloatHelper.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs"> <Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link> <Link>Extensions\IntHelper.cs</Link>
</Compile> </Compile>
@ -1233,13 +1232,6 @@
<PostBuildEvent> <PostBuildEvent>
</PostBuildEvent> </PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">

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"> <Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link> <Link>Extensions\FloatHelper.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
<Link>Extensions\IHasPropertyChangedEvent.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs"> <Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link> <Link>Extensions\IntHelper.cs</Link>
</Compile> </Compile>

View File

@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
/// <value>The cache path.</value> /// <value>The cache path.</value>
public string CachePath { get; set; } public string CachePath { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [enable custom path sub folders].
/// </summary>
/// <value><c>true</c> if [enable custom path sub folders]; otherwise, <c>false</c>.</value>
public bool EnableCustomPathSubFolders { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class. /// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.

View File

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

View File

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

View File

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

View File

@ -131,6 +131,11 @@ namespace MediaBrowser.Model.Dlna
List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options); List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options);
ConditionProcessor conditionProcessor = new ConditionProcessor();
int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
if (directPlayMethods.Count > 0) if (directPlayMethods.Count > 0)
{ {
string audioCodec = audioStream == null ? null : audioStream.Codec; string audioCodec = audioStream == null ? null : audioStream.Codec;
@ -138,27 +143,36 @@ namespace MediaBrowser.Model.Dlna
// Make sure audio codec profiles are satisfied // Make sure audio codec profiles are satisfied
if (!string.IsNullOrEmpty(audioCodec)) if (!string.IsNullOrEmpty(audioCodec))
{ {
ConditionProcessor conditionProcessor = new ConditionProcessor();
List<ProfileCondition> conditions = new List<ProfileCondition>(); List<ProfileCondition> conditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles) foreach (CodecProfile i in options.Profile.CodecProfiles)
{ {
if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container)) if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container))
{ {
foreach (ProfileCondition c in i.Conditions) bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{ {
conditions.Add(c); if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
} }
} }
} }
int? audioChannels = audioStream.Channels;
int? audioBitrate = audioStream.BitRate;
bool all = true; bool all = true;
foreach (ProfileCondition c in conditions) foreach (ProfileCondition c in conditions)
{ {
if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate)) if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate))
{ {
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item); LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
all = false; all = false;
@ -241,9 +255,23 @@ namespace MediaBrowser.Model.Dlna
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>(); List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in audioCodecProfiles) foreach (CodecProfile i in audioCodecProfiles)
{ {
foreach (ProfileCondition c in i.Conditions) bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{ {
audioTranscodingConditions.Add(c); if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
audioTranscodingConditions.Add(c);
}
} }
} }
@ -294,7 +322,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayProfile != null) if (directPlayProfile != null)
{ {
// While options takes the network and other factors into account. Only applies to direct stream // While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate())) if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream)
{ {
playMethods.Add(PlayMethod.DirectStream); playMethods.Add(PlayMethod.DirectStream);
} }
@ -302,7 +330,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports // The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play // If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay && if (item.SupportsDirectPlay &&
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options))) IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
{ {
playMethods.Add(PlayMethod.DirectPlay); playMethods.Add(PlayMethod.DirectPlay);
} }
@ -385,8 +413,8 @@ namespace MediaBrowser.Model.Dlna
MediaStream videoStream = item.VideoStream; MediaStream videoStream = item.VideoStream;
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay); bool isEligibleForDirectPlay = options.EnableDirectPlay && IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream); bool isEligibleForDirectStream = options.EnableDirectStream && IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", _logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
options.Profile.Name ?? "Unknown Profile", options.Profile.Name ?? "Unknown Profile",
@ -464,17 +492,37 @@ namespace MediaBrowser.Model.Dlna
} }
playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex; playlistItem.AudioStreamIndex = audioStreamIndex;
ConditionProcessor conditionProcessor = new ConditionProcessor();
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>(); List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles) foreach (CodecProfile i in options.Profile.CodecProfiles)
{ {
if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container)) if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
{ {
foreach (ProfileCondition c in i.Conditions) bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{ {
videoTranscodingConditions.Add(c); bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream);
int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate;
int? audioChannels = audioStream == null ? null : audioStream.Channels;
string audioProfile = audioStream == null ? null : audioStream.Profile;
if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, audioProfile, isSecondaryAudio))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
videoTranscodingConditions.Add(c);
}
break;
} }
break;
} }
} }
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions); ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
@ -484,11 +532,42 @@ namespace MediaBrowser.Model.Dlna
{ {
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container)) if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container))
{ {
foreach (ProfileCondition c in i.Conditions) bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{ {
audioTranscodingConditions.Add(c); int? width = videoStream == null ? null : videoStream.Width;
int? height = videoStream == null ? null : videoStream.Height;
int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
double? videoLevel = videoStream == null ? null : videoStream.Level;
string videoProfile = videoStream == null ? null : videoStream.Profile;
float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
{
LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
audioTranscodingConditions.Add(c);
}
break;
} }
break;
} }
} }
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
@ -667,9 +746,23 @@ namespace MediaBrowser.Model.Dlna
{ {
if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container)) if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container))
{ {
foreach (ProfileCondition c in i.Conditions) bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{ {
conditions.Add(c); if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
{
LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
} }
} }
} }
@ -698,20 +791,35 @@ namespace MediaBrowser.Model.Dlna
} }
conditions = new List<ProfileCondition>(); conditions = new List<ProfileCondition>();
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
foreach (CodecProfile i in profile.CodecProfiles) foreach (CodecProfile i in profile.CodecProfiles)
{ {
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container)) if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container))
{ {
foreach (ProfileCondition c in i.Conditions) bool applyConditions = true;
foreach (ProfileCondition applyCondition in i.ApplyConditions)
{ {
conditions.Add(c); if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{
LogConditionFailure(profile, "VideoAudioCodecProfile", applyCondition, mediaSource);
applyConditions = false;
break;
}
}
if (applyConditions)
{
foreach (ProfileCondition c in i.Conditions)
{
conditions.Add(c);
}
} }
} }
} }
foreach (ProfileCondition i in conditions) foreach (ProfileCondition i in conditions)
{ {
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio)) if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{ {
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource); LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);

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

View File

@ -1,5 +1,4 @@
using MediaBrowser.Model.Extensions; using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -9,7 +8,7 @@ namespace MediaBrowser.Model.Dto
/// This is used by the api to get information about a Person within a BaseItem /// This is used by the api to get information about a Person within a BaseItem
/// </summary> /// </summary>
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")] [DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
public class BaseItemPerson : IHasPropertyChangedEvent public class BaseItemPerson
{ {
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
return PrimaryImageTag != null; return PrimaryImageTag != null;
} }
} }
/// <summary>
/// Occurs when [property changed].
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

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.Dto;
using MediaBrowser.Model.Extensions;
using System; using System;
using System.ComponentModel;
namespace MediaBrowser.Model.LiveTv namespace MediaBrowser.Model.LiveTv
{ {
public class BaseTimerInfoDto : IHasPropertyChangedEvent, IHasServerId public class BaseTimerInfoDto : IHasServerId
{ {
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary> /// <summary>
/// Id of the recording. /// Id of the recording.
/// </summary> /// </summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -72,7 +72,10 @@ namespace MediaBrowser.Providers.MediaInfo
// Try to translate to three character code // Try to translate to three character code
// Be flexible and check against both the full and three character versions // Be flexible and check against both the full and three character versions
var culture = _localization.GetCultures() var culture = _localization.GetCultures()
.FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) ||
string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
if (culture != null) if (culture != null)
{ {
@ -99,10 +102,12 @@ namespace MediaBrowser.Providers.MediaInfo
private string NormalizeFilenameForSubtitleComparison(string filename) private string NormalizeFilenameForSubtitleComparison(string filename)
{ {
// Try to account for sloppy file naming // Try to account for sloppy file naming
filename = filename.Replace("-", string.Empty);
filename = filename.Replace("_", string.Empty); filename = filename.Replace("_", string.Empty);
filename = filename.Replace(" ", string.Empty); filename = filename.Replace(" ", string.Empty);
// can't normalize this due to languages such as pt-br
//filename = filename.Replace("-", string.Empty);
//filename = filename.Replace(".", string.Empty); //filename = filename.Replace(".", string.Empty);
return filename; return filename;

View File

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

View File

@ -429,7 +429,9 @@ namespace MediaBrowser.Providers.TV
IndexNumber = episodeNumber, IndexNumber = episodeNumber,
ParentIndexNumber = seasonNumber, ParentIndexNumber = seasonNumber,
Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)), Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
IsVirtualItem = true IsVirtualItem = true,
SeasonId = season == null ? (Guid?)null : season.Id,
SeriesId = series.Id
}; };
episode.SetParent(season); episode.SetParent(season);

View File

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

View File

@ -663,29 +663,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.GameSystem = item.GameSystemName; dto.GameSystem = item.GameSystemName;
} }
private List<string> GetBackdropImageTags(BaseItem item, int limit) private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
{ {
return GetCacheTags(item, ImageType.Backdrop, limit).ToList(); return images
}
private List<string> GetScreenshotImageTags(BaseItem item, int limit)
{
var hasScreenshots = item as IHasScreenshots;
if (hasScreenshots == null)
{
return new List<string>();
}
return GetCacheTags(item, ImageType.Screenshot, limit).ToList();
}
private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
{
return item.GetImages(type)
// Convert to a list now in case GetImageCacheTag is slow
.ToList()
.Select(p => GetImageCacheTag(item, p)) .Select(p => GetImageCacheTag(item, p))
.Where(i => i != null) .Where(i => i != null)
.Take(limit)
.ToList(); .ToList();
} }
@ -850,53 +832,6 @@ namespace MediaBrowser.Server.Implementations.Dto
} }
} }
/// <summary>
/// If an item does not any backdrops, this can be used to find the first parent that does have one
/// </summary>
/// <param name="item">The item.</param>
/// <param name="owner">The owner.</param>
/// <returns>BaseItem.</returns>
private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
{
var parent = item.GetParent() ?? owner;
while (parent != null)
{
if (parent.GetImages(ImageType.Backdrop).Any())
{
return parent;
}
parent = parent.GetParent();
}
return null;
}
/// <summary>
/// If an item does not have a logo, this can be used to find the first parent that does have one
/// </summary>
/// <param name="item">The item.</param>
/// <param name="type">The type.</param>
/// <param name="owner">The owner.</param>
/// <returns>BaseItem.</returns>
private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
{
var parent = item.GetParent() ?? owner;
while (parent != null)
{
if (parent.HasImage(type))
{
return parent;
}
parent = parent.GetParent();
}
return null;
}
/// <summary> /// <summary>
/// Gets the chapter info dto. /// Gets the chapter info dto.
/// </summary> /// </summary>
@ -917,7 +852,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{ {
Path = chapterInfo.ImagePath, Path = chapterInfo.ImagePath,
Type = ImageType.Chapter, Type = ImageType.Chapter,
DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath) DateModified = chapterInfo.ImageDateModified
}); });
} }
@ -958,6 +893,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.LockData = item.IsLocked; dto.LockData = item.IsLocked;
dto.ForcedSortName = item.ForcedSortName; dto.ForcedSortName = item.ForcedSortName;
} }
dto.Container = item.Container;
var hasBudget = item as IHasBudget; var hasBudget = item as IHasBudget;
if (hasBudget != null) if (hasBudget != null)
@ -1027,7 +963,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var backdropLimit = options.GetImageLimit(ImageType.Backdrop); var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0) if (backdropLimit > 0)
{ {
dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit); dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
} }
if (fields.Contains(ItemFields.ScreenshotImageTags)) if (fields.Contains(ItemFields.ScreenshotImageTags))
@ -1035,7 +971,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
if (screenshotLimit > 0) if (screenshotLimit > 0)
{ {
dto.ScreenshotImageTags = GetScreenshotImageTags(item, screenshotLimit); dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
} }
} }
@ -1064,6 +1000,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Id = GetDtoId(item); dto.Id = GetDtoId(item);
dto.IndexNumber = item.IndexNumber; dto.IndexNumber = item.IndexNumber;
dto.ParentIndexNumber = item.ParentIndexNumber;
dto.IsFolder = item.IsFolder; dto.IsFolder = item.IsFolder;
dto.MediaType = item.MediaType; dto.MediaType = item.MediaType;
dto.LocationType = item.LocationType; dto.LocationType = item.LocationType;
@ -1076,15 +1013,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode; dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage; dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
var hasCriticRating = item as IHasCriticRating; dto.CriticRating = item.CriticRating;
if (hasCriticRating != null)
{
dto.CriticRating = hasCriticRating.CriticRating;
if (fields.Contains(ItemFields.CriticRatingSummary)) if (fields.Contains(ItemFields.CriticRatingSummary))
{ {
dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary; dto.CriticRatingSummary = item.CriticRatingSummary;
}
} }
var hasTrailers = item as IHasTrailers; var hasTrailers = item as IHasTrailers;
@ -1127,23 +1060,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.ShortOverview)) if (fields.Contains(ItemFields.ShortOverview))
{ {
var hasShortOverview = item as IHasShortOverview; dto.ShortOverview = item.ShortOverview;
if (hasShortOverview != null)
{
dto.ShortOverview = hasShortOverview.ShortOverview;
}
}
// If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
{
var parentWithBackdrop = GetParentBackdropItem(item, owner);
if (parentWithBackdrop != null)
{
dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
}
} }
if (fields.Contains(ItemFields.ParentId)) if (fields.Contains(ItemFields.ParentId))
@ -1155,46 +1072,7 @@ namespace MediaBrowser.Server.Implementations.Dto
} }
} }
dto.ParentIndexNumber = item.ParentIndexNumber; AddInheritedImages(dto, item, options, owner);
// If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
if (!dto.HasLogo && options.GetImageLimit(ImageType.Logo) > 0)
{
var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
if (parentWithLogo != null)
{
dto.ParentLogoItemId = GetDtoId(parentWithLogo);
dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
}
}
// If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
if (!dto.HasArtImage && options.GetImageLimit(ImageType.Art) > 0)
{
var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
if (parentWithImage != null)
{
dto.ParentArtItemId = GetDtoId(parentWithImage);
dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
}
}
// If there is no thumb, indicate what parent has one in case the Ui wants to allow inheritance
if (!dto.HasThumb && options.GetImageLimit(ImageType.Thumb) > 0)
{
var parentWithImage = GetParentImageItem(item, ImageType.Thumb, owner);
if (parentWithImage != null)
{
dto.ParentThumbItemId = GetDtoId(parentWithImage);
dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
}
}
if (fields.Contains(ItemFields.Path)) if (fields.Contains(ItemFields.Path))
{ {
@ -1426,42 +1304,38 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.SeasonId = seasonId.Value.ToString("N"); dto.SeasonId = seasonId.Value.ToString("N");
} }
var episodeSeason = episode.Season; dto.SeasonName = episode.SeasonName;
if (episodeSeason != null)
var seriesId = episode.SeriesId;
if (seriesId.HasValue)
{ {
if (fields.Contains(ItemFields.SeasonName)) dto.SeriesId = seriesId.Value.ToString("N");
{
dto.SeasonName = episodeSeason.Name;
}
} }
var episodeSeries = episode.Series; Series episodeSeries = null;
if (episodeSeries != null) if (fields.Contains(ItemFields.SeriesGenres))
{ {
if (fields.Contains(ItemFields.SeriesGenres)) episodeSeries = episodeSeries ?? episode.Series;
if (episodeSeries != null)
{ {
dto.SeriesGenres = episodeSeries.Genres.ToList(); dto.SeriesGenres = episodeSeries.Genres.ToList();
} }
}
dto.SeriesId = GetDtoId(episodeSeries); //if (fields.Contains(ItemFields.SeriesPrimaryImage))
{
if (fields.Contains(ItemFields.AirTime)) episodeSeries = episodeSeries ?? episode.Series;
{ if (episodeSeries != null)
dto.AirTime = episodeSeries.AirTime;
}
if (options.GetImageLimit(ImageType.Thumb) > 0)
{
dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
}
if (options.GetImageLimit(ImageType.Primary) > 0)
{ {
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary); dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
} }
}
if (fields.Contains(ItemFields.SeriesStudio)) if (fields.Contains(ItemFields.SeriesStudio))
{
episodeSeries = episodeSeries ?? episode.Series;
if (episodeSeries != null)
{ {
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault(); dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
} }
@ -1483,16 +1357,29 @@ namespace MediaBrowser.Server.Implementations.Dto
var season = item as Season; var season = item as Season;
if (season != null) if (season != null)
{ {
series = season.Series; dto.SeriesName = season.SeriesName;
if (series != null) var seriesId = season.SeriesId;
if (seriesId.HasValue)
{ {
dto.SeriesId = GetDtoId(series); dto.SeriesId = seriesId.Value.ToString("N");
dto.SeriesName = series.Name; }
dto.AirTime = series.AirTime;
dto.SeriesStudio = series.Studios.FirstOrDefault();
if (options.GetImageLimit(ImageType.Primary) > 0) series = null;
if (fields.Contains(ItemFields.SeriesStudio))
{
series = series ?? season.Series;
if (series != null)
{
dto.SeriesStudio = series.Studios.FirstOrDefault();
}
}
if (fields.Contains(ItemFields.SeriesPrimaryImage))
{
series = series ?? season.Series;
if (series != null)
{ {
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
} }
@ -1543,6 +1430,77 @@ namespace MediaBrowser.Server.Implementations.Dto
} }
} }
private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner)
{
var logoLimit = options.GetImageLimit(ImageType.Logo);
var artLimit = options.GetImageLimit(ImageType.Art);
var thumbLimit = options.GetImageLimit(ImageType.Thumb);
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
{
return;
}
BaseItem parent = null;
var isFirst = true;
while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) &&
(parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null)
{
if (parent == null)
{
break;
}
var allImages = parent.ImageInfos;
if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null)
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
if (image != null)
{
dto.ParentLogoItemId = GetDtoId(parent);
dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
}
}
if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null)
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
if (image != null)
{
dto.ParentArtItemId = GetDtoId(parent);
dto.ParentArtImageTag = GetImageCacheTag(parent, image);
}
}
if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series))
{
var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
if (image != null)
{
dto.ParentThumbItemId = GetDtoId(parent);
dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
}
}
if (backdropLimit > 0 && !dto.HasBackdrop)
{
var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
if (images.Count > 0)
{
dto.ParentBackdropItemId = GetDtoId(parent);
dto.ParentBackdropImageTags = GetImageTags(parent, images);
}
}
isFirst = false;
parent = parent.GetParent();
}
}
private string GetMappedPath(IHasMetadata item) private string GetMappedPath(IHasMetadata item)
{ {
var path = item.Path; var path = item.Path;

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

View File

@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
options.ContentType = MimeTypes.GetMimeType(path); options.ContentType = MimeTypes.GetMimeType(path);
} }
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); if (!options.DateLastModified.HasValue)
{
options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
}
var cacheKey = path + options.DateLastModified.Value.Ticks; var cacheKey = path + options.DateLastModified.Value.Ticks;
options.CacheKey = cacheKey.GetMD5(); options.CacheKey = cacheKey.GetMD5();
@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
throw error; throw error;
} }
public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
{
return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
}
} }
} }

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.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks;
using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer namespace MediaBrowser.Server.Implementations.HttpServer
{ {
public class RangeRequestWriter : IStreamWriter, IHttpResult public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult
{ {
/// <summary> /// <summary>
/// Gets or sets the source stream. /// Gets or sets the source stream.
@ -168,16 +170,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// </summary> /// </summary>
/// <param name="responseStream">The response stream.</param> /// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream) public void WriteTo(Stream responseStream)
{
WriteToInternal(responseStream);
}
/// <summary>
/// Writes to async.
/// </summary>
/// <param name="responseStream">The response stream.</param>
/// <returns>Task.</returns>
private void WriteToInternal(Stream responseStream)
{ {
try try
{ {
@ -237,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer
} }
} }
public async Task WriteToAsync(Stream responseStream)
{
try
{
// Headers only
if (IsHeadRequest)
{
return;
}
using (var source = SourceStream)
{
// If the requested range is "0-", we can optimize by just doing a stream copy
if (RangeEnd >= TotalContentLength - 1)
{
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
}
else
{
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
}
}
}
catch (IOException ex)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error in range request writer", ex);
throw;
}
finally
{
if (OnComplete != null)
{
OnComplete();
}
}
}
private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
{
var array = new byte[BufferSize];
int count;
while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
{
var bytesToCopy = Math.Min(count, copyLength);
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
copyLength -= bytesToCopy;
if (copyLength <= 0)
{
break;
}
}
}
public string ContentType { get; set; } public string ContentType { get; set; }
public IRequest RequestContext { get; set; } public IRequest RequestContext { get; set; }

View File

@ -3,6 +3,7 @@ using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
using System.Web; using System.Web;
using ServiceStack; using ServiceStack;
using ServiceStack.Web; using ServiceStack.Web;
@ -32,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return header.Substring(ap + 1, end - ap - 1); return header.Substring(ap + 1, end - ap - 1);
} }
void LoadMultiPart() async Task LoadMultiPart()
{ {
string boundary = GetParameter(ContentType, "; boundary="); string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null) if (boundary == null)
return; return;
var input = GetSubStream(InputStream); using (var requestStream = GetSubStream(InputStream))
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
//Not ending with \r\n?
var ms = new MemoryStream(32 * 1024);
input.CopyTo(ms);
input = ms;
ms.WriteByte((byte)'\r');
ms.WriteByte((byte)'\n');
input.Position = 0;
//Uncomment to debug
//var content = new StreamReader(ms).ReadToEnd();
//Console.WriteLine(boundary + "::" + content);
//input.Position = 0;
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
HttpMultipart.Element e;
while ((e = multi_part.ReadNextElement()) != null)
{ {
if (e.Filename == null) //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
{ //Not ending with \r\n?
byte[] copy = new byte[e.Length]; var ms = new MemoryStream(32 * 1024);
await requestStream.CopyToAsync(ms).ConfigureAwait(false);
input.Position = e.Start; var input = ms;
input.Read(copy, 0, (int)e.Length); ms.WriteByte((byte)'\r');
ms.WriteByte((byte)'\n');
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy)); input.Position = 0;
}
else //Uncomment to debug
//var content = new StreamReader(ms).ReadToEnd();
//Console.WriteLine(boundary + "::" + content);
//input.Position = 0;
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
HttpMultipart.Element e;
while ((e = multi_part.ReadNextElement()) != null)
{ {
// if (e.Filename == null)
// We use a substream, as in 2.x we will support large uploads streamed to disk, {
// byte[] copy = new byte[e.Length];
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files.AddFile(e.Name, sub); input.Position = e.Start;
input.Read(copy, 0, (int)e.Length);
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
}
else
{
//
// We use a substream, as in 2.x we will support large uploads streamed to disk,
//
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files.AddFile(e.Name, sub);
}
} }
} }
EndSubStream(input);
} }
public NameValueCollection Form public NameValueCollection Form
@ -91,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
files = new HttpFileCollection(); files = new HttpFileCollection();
if (IsContentType("multipart/form-data", true)) if (IsContentType("multipart/form-data", true))
LoadMultiPart(); {
else if ( var task = LoadMultiPart();
IsContentType("application/x-www-form-urlencoded", true)) Task.WaitAll(task);
LoadWwwForm(); }
else if (IsContentType("application/x-www-form-urlencoded", true))
{
var task = LoadWwwForm();
Task.WaitAll(task);
}
form.Protect(); form.Protect();
} }
@ -220,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0; return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
} }
async Task LoadWwwForm()
void LoadWwwForm()
{ {
using (Stream input = GetSubStream(InputStream)) using (Stream input = GetSubStream(InputStream))
{ {
using (StreamReader s = new StreamReader(input, ContentEncoding)) using (var ms = new MemoryStream())
{ {
StringBuilder key = new StringBuilder(); await input.CopyToAsync(ms).ConfigureAwait(false);
StringBuilder value = new StringBuilder(); ms.Position = 0;
int c;
while ((c = s.Read()) != -1) using (StreamReader s = new StreamReader(ms, ContentEncoding))
{ {
if (c == '=') StringBuilder key = new StringBuilder();
StringBuilder value = new StringBuilder();
int c;
while ((c = s.Read()) != -1)
{ {
value.Length = 0; if (c == '=')
while ((c = s.Read()) != -1)
{ {
if (c == '&') value.Length = 0;
while ((c = s.Read()) != -1)
{
if (c == '&')
{
AddRawKeyValue(key, value);
break;
}
else
value.Append((char)c);
}
if (c == -1)
{ {
AddRawKeyValue(key, value); AddRawKeyValue(key, value);
break; return;
} }
else
value.Append((char)c);
} }
if (c == -1) else if (c == '&')
{
AddRawKeyValue(key, value); AddRawKeyValue(key, value);
return; else
} key.Append((char)c);
} }
else if (c == '&') if (c == -1)
AddRawKeyValue(key, value); AddRawKeyValue(key, value);
else
key.Append((char)c);
} }
if (c == -1)
AddRawKeyValue(key, value);
EndSubStream(input);
} }
} }
} }

View File

@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
get get
{ {
return remoteIp ?? return remoteIp ??
(remoteIp = XForwardedFor ?? (remoteIp = (CheckBadChars(XForwardedFor)) ??
(NormalizeIp(XRealIp) ?? (NormalizeIp(CheckBadChars(XRealIp)) ??
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
} }
} }
private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
//
// CheckBadChars - throws on invalid chars to be not found in header name/value
//
internal static string CheckBadChars(string name)
{
if (name == null || name.Length == 0)
{
return name;
}
// VALUE check
//Trim spaces from both ends
name = name.Trim(HttpTrimCharacters);
//First, check for correctly formed multi-line value
//Second, check for absenece of CTL characters
int crlf = 0;
for (int i = 0; i < name.Length; ++i)
{
char c = (char)(0x000000ff & (uint)name[i]);
switch (crlf)
{
case 0:
if (c == '\r')
{
crlf = 1;
}
else if (c == '\n')
{
// Technically this is bad HTTP. But it would be a breaking change to throw here.
// Is there an exploit?
crlf = 2;
}
else if (c == 127 || (c < ' ' && c != '\t'))
{
throw new ArgumentException("net_WebHeaderInvalidControlChars");
}
break;
case 1:
if (c == '\n')
{
crlf = 2;
break;
}
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
case 2:
if (c == ' ' || c == '\t')
{
crlf = 0;
break;
}
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
}
if (crlf != 0)
{
throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
}
return name;
}
internal static bool ContainsNonAsciiChars(string token)
{
for (int i = 0; i < token.Length; ++i)
{
if ((token[i] < 0x20) || (token[i] > 0x7e))
{
return true;
}
}
return false;
}
private string NormalizeIp(string ip) private string NormalizeIp(string ip)
{ {
if (!string.IsNullOrWhiteSpace(ip)) if (!string.IsNullOrWhiteSpace(ip))
@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return stream; return stream;
} }
static void EndSubStream(Stream stream)
{
}
public static string GetHandlerPathIfAny(string listenerUrl) public static string GetHandlerPathIfAny(string listenerUrl)
{ {
if (listenerUrl == null) return null; if (listenerUrl == null) return null;

View File

@ -4,13 +4,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Threading.Tasks;
using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer namespace MediaBrowser.Server.Implementations.HttpServer
{ {
/// <summary> /// <summary>
/// Class StreamWriter /// Class StreamWriter
/// </summary> /// </summary>
public class StreamWriter : IStreamWriter, IHasOptions public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
{ {
private ILogger Logger { get; set; } private ILogger Logger { get; set; }
@ -73,24 +75,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{ {
} }
// 256k
private const int BufferSize = 262144;
/// <summary> /// <summary>
/// Writes to. /// Writes to.
/// </summary> /// </summary>
/// <param name="responseStream">The response stream.</param> /// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream) public void WriteTo(Stream responseStream)
{
WriteToInternal(responseStream);
}
// 256k
private const int BufferSize = 262144;
/// <summary>
/// Writes to async.
/// </summary>
/// <param name="responseStream">The response stream.</param>
/// <returns>Task.</returns>
private void WriteToInternal(Stream responseStream)
{ {
try try
{ {
@ -107,7 +99,36 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{ {
OnError(); OnError();
} }
throw;
}
finally
{
if (OnComplete != null)
{
OnComplete();
}
}
}
public async Task WriteToAsync(Stream responseStream)
{
try
{
using (var src = SourceStream)
{
await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Logger.ErrorException("Error streaming data", ex);
if (OnError != null)
{
OnError();
}
throw; throw;
} }
finally finally

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. // This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
// Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds // Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata // But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
await Task.Delay(25000).ConfigureAwait(false); await Task.Delay(45000).ConfigureAwait(false);
string val; string val;
_tempIgnoredPaths.TryRemove(path, out val); _tempIgnoredPaths.TryRemove(path, out val);

View File

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

View File

@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using CommonIO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers namespace MediaBrowser.Server.Implementations.Library.Resolvers
{ {
public class PhotoResolver : ItemResolver<Photo> public class PhotoResolver : ItemResolver<Photo>
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
public PhotoResolver(IImageProcessor imageProcessor) private readonly ILibraryManager _libraryManager;
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_libraryManager = libraryManager;
} }
/// <summary> /// <summary>
@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
/// <returns>Trailer.</returns> /// <returns>Trailer.</returns>
protected override Photo Resolve(ItemResolveArgs args) protected override Photo Resolve(ItemResolveArgs args)
{ {
// Must be an image file within a photo collection if (!args.IsDirectory)
if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
!args.IsDirectory &&
IsImageFile(args.Path, _imageProcessor))
{ {
return new Photo // Must be an image file within a photo collection
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{ {
Path = args.Path if (IsImageFile(args.Path, _imageProcessor))
}; {
var filename = Path.GetFileNameWithoutExtension(args.Path);
// Make sure the image doesn't belong to a video file
if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename)))
{
return null;
}
return new Photo
{
Path = args.Path
};
}
}
} }
return null; return null;
} }
private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename)
{
if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
private static readonly string[] IgnoreFiles = private static readonly string[] IgnoreFiles =
{ {
"folder", "folder",
@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
"landscape", "landscape",
"fanart", "fanart",
"backdrop", "backdrop",
"poster" "poster",
"cover"
}; };
internal static bool IsImageFile(string path, IImageProcessor imageProcessor) internal static bool IsImageFile(string path, IImageProcessor imageProcessor)

View File

@ -31,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
} }
var season = parent as Season; var season = parent as Season;
// Just in case the user decided to nest episodes. // Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it. // Not officially supported but in some cases we can handle it.
if (season == null) if (season == null)
@ -41,10 +40,30 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders // Also handle flat tv folders
if (season != null || args.HasParent<Series>() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) if (season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
{ {
var episode = ResolveVideo<Episode>(args, false); var episode = ResolveVideo<Episode>(args, false);
if (episode != null)
{
var series = parent as Series;
if (series == null)
{
series = parent.GetParents().OfType<Series>().FirstOrDefault();
}
if (series != null)
{
episode.SeriesId = series.Id;
}
if (season != null)
{
episode.SeasonId = season.Id;
}
}
return episode; return episode;
} }

View File

@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
if (args.Parent is Series && args.IsDirectory) if (args.Parent is Series && args.IsDirectory)
{ {
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var series = ((Series)args.Parent);
var season = new Season var season = new Season
{ {
IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
SeriesId = series.Id
}; };
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0) if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)

View File

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

View File

@ -17,6 +17,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
@ -106,18 +107,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken) private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
{ {
using (var stream = await _httpClient.Get(new HttpRequestOptions() try
{ {
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), using (var stream = await _httpClient.Get(new HttpRequestOptions()
CancellationToken = cancellationToken, {
CacheLength = TimeSpan.FromDays(1), Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CacheMode = CacheMode.Unconditional, CancellationToken = cancellationToken,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds) CacheLength = TimeSpan.FromDays(1),
})) CacheMode = CacheMode.Unconditional,
{ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream); }))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
return response.ModelNumber; return response.ModelNumber;
}
}
catch (HttpException ex)
{
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
{
// HDHR4 doesn't have this api
return "HDHR";
}
throw;
} }
} }
@ -455,16 +469,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return; return;
} }
// Test it by pulling down the lineup try
using (var stream = await _httpClient.Get(new HttpRequestOptions
{ {
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), // Test it by pulling down the lineup
CancellationToken = CancellationToken.None using (var stream = await _httpClient.Get(new HttpRequestOptions
})) {
{ Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream); CancellationToken = CancellationToken.None
}))
{
var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
info.DeviceId = response.DeviceID; info.DeviceId = response.DeviceID;
}
}
catch (HttpException ex)
{
if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
{
// HDHR4 doesn't have this api
return;
}
throw;
} }
} }

View File

@ -56,8 +56,8 @@
<Reference Include="Interfaces.IO"> <Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath> <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference> </Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.6012.15754, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.52\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath> <HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="MoreLinq"> <Reference Include="MoreLinq">
@ -73,8 +73,8 @@
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath> <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="SocketHttpListener, Version=1.0.6046.26351, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll</HintPath> <HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
@ -156,6 +156,7 @@
<Compile Include="EntryPoints\ServerEventNotifier.cs" /> <Compile Include="EntryPoints\ServerEventNotifier.cs" />
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" /> <Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" /> <Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
<Compile Include="HttpServer\AsyncStreamWriterFunc.cs" />
<Compile Include="HttpServer\IHttpListener.cs" /> <Compile Include="HttpServer\IHttpListener.cs" />
<Compile Include="HttpServer\Security\AuthorizationContext.cs" /> <Compile Include="HttpServer\Security\AuthorizationContext.cs" />
<Compile Include="HttpServer\ContainerAdapter.cs" /> <Compile Include="HttpServer\ContainerAdapter.cs" />
@ -757,9 +758,7 @@
<EmbeddedResource Include="Localization\iso6392.txt" /> <EmbeddedResource Include="Localization\iso6392.txt" />
<EmbeddedResource Include="Localization\Ratings\be.txt" /> <EmbeddedResource Include="Localization\Ratings\be.txt" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="HttpServer\NetListener\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

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

View File

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

View File

@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _updateInheritedRatingCommand; private IDbCommand _updateInheritedRatingCommand;
private IDbCommand _updateInheritedTagsCommand; private IDbCommand _updateInheritedTagsCommand;
public const int LatestSchemaVersion = 97; public const int LatestSchemaVersion = 108;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class. /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@ -269,10 +269,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT"); _connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonName", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text");
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
_connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
_connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
string[] postQueries = string[] postQueries =
{ {
@ -403,7 +409,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Album", "Album",
"CriticRating", "CriticRating",
"CriticRatingSummary", "CriticRatingSummary",
"IsVirtualItem" "IsVirtualItem",
"SeriesName",
"SeasonName",
"SeasonId",
"SeriesId",
"SeriesSortName"
}; };
private readonly string[] _mediaStreamSaveColumns = private readonly string[] _mediaStreamSaveColumns =
@ -523,7 +534,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Album", "Album",
"IsVirtualItem", "IsVirtualItem",
"SeriesName", "SeriesName",
"UserDataKey" "UserDataKey",
"SeasonName",
"SeasonId",
"SeriesId",
"SeriesSortName"
}; };
_saveItemCommand = _connection.CreateCommand(); _saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@ -582,6 +597,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks"); _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name"); _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath"); _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
// MediaStreams // MediaStreams
_deleteStreamsCommand = _connection.CreateCommand(); _deleteStreamsCommand = _connection.CreateCommand();
@ -945,7 +961,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
var hasSeries = item as IHasSeries; var hasSeries = item as IHasSeries;
if (hasSeries != null) if (hasSeries != null)
{ {
_saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName; _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
} }
else else
{ {
@ -954,6 +970,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault(); _saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
var episode = item as Episode;
if (episode != null)
{
_saveItemCommand.GetParameter(index++).Value = episode.FindSeasonName();
_saveItemCommand.GetParameter(index++).Value = episode.FindSeasonId();
}
else
{
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
}
if (hasSeries != null)
{
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId();
_saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesSortName();
}
else
{
_saveItemCommand.GetParameter(index++).Value = null;
_saveItemCommand.GetParameter(index++).Value = null;
}
_saveItemCommand.Transaction = transaction; _saveItemCommand.Transaction = transaction;
_saveItemCommand.ExecuteNonQuery(); _saveItemCommand.ExecuteNonQuery();
@ -1376,6 +1415,44 @@ namespace MediaBrowser.Server.Implementations.Persistence
item.IsVirtualItem = reader.GetBoolean(58); item.IsVirtualItem = reader.GetBoolean(58);
} }
var hasSeries = item as IHasSeries;
if (hasSeries != null)
{
if (!reader.IsDBNull(59))
{
hasSeries.SeriesName = reader.GetString(59);
}
}
var episode = item as Episode;
if (episode != null)
{
if (!reader.IsDBNull(60))
{
episode.SeasonName = reader.GetString(60);
}
if (!reader.IsDBNull(61))
{
episode.SeasonId = reader.GetGuid(61);
}
}
if (hasSeries != null)
{
if (!reader.IsDBNull(62))
{
hasSeries.SeriesId = reader.GetGuid(62);
}
}
if (hasSeries != null)
{
if (!reader.IsDBNull(63))
{
hasSeries.SeriesSortName = reader.GetString(63);
}
}
return item; return item;
} }
@ -1437,7 +1514,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand()) using (var cmd = _connection.CreateCommand())
{ {
cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"; cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id; cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
@ -1470,7 +1547,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand()) using (var cmd = _connection.CreateCommand())
{ {
cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex"; cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id; cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index; cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
@ -1508,6 +1585,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
chapter.ImagePath = reader.GetString(2); chapter.ImagePath = reader.GetString(2);
} }
if (!reader.IsDBNull(3))
{
chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
}
return chapter; return chapter;
} }
@ -1567,6 +1649,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks; _saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
_saveChapterCommand.GetParameter(3).Value = chapter.Name; _saveChapterCommand.GetParameter(3).Value = chapter.Name;
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath; _saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
_saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
_saveChapterCommand.Transaction = transaction; _saveChapterCommand.Transaction = transaction;
@ -2061,7 +2144,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{ {
if (query.User != null) if (query.User != null)
{ {
query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random }; query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
} }
else else
{ {
@ -2986,6 +3069,39 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("LocationType<>'Virtual'"); whereClauses.Add("LocationType<>'Virtual'");
} }
} }
if (query.IsUnaired.HasValue)
{
if (query.IsUnaired.Value)
{
whereClauses.Add("PremiereDate >= DATETIME('now')");
}
else
{
whereClauses.Add("PremiereDate < DATETIME('now')");
}
}
if (query.IsMissing.HasValue && _config.Configuration.SchemaVersion >= 90)
{
if (query.IsMissing.Value)
{
whereClauses.Add("(IsVirtualItem=1 AND PremiereDate < DATETIME('now'))");
}
else
{
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate >= DATETIME('now'))");
}
}
if (query.IsVirtualUnaired.HasValue && _config.Configuration.SchemaVersion >= 90)
{
if (query.IsVirtualUnaired.Value)
{
whereClauses.Add("(IsVirtualItem=1 AND PremiereDate >= DATETIME('now'))");
}
else
{
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
}
}
if (query.MediaTypes.Length == 1) if (query.MediaTypes.Length == 1)
{ {
whereClauses.Add("MediaType=@MediaTypes"); whereClauses.Add("MediaType=@MediaTypes");
@ -4063,6 +4179,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("values"); throw new ArgumentNullException("values");
} }
// Just in case there might be case-insensitive duplicates, strip them out now
var newValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var pair in values)
{
newValues[pair.Key] = pair.Value;
}
CheckDisposed(); CheckDisposed();
// First delete // First delete
@ -4071,7 +4194,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteProviderIdsCommand.ExecuteNonQuery(); _deleteProviderIdsCommand.ExecuteNonQuery();
foreach (var pair in values) foreach (var pair in newValues)
{ {
_saveProviderIdsCommand.GetParameter(0).Value = itemId; _saveProviderIdsCommand.GetParameter(0).Value = itemId;
_saveProviderIdsCommand.GetParameter(1).Value = pair.Key; _saveProviderIdsCommand.GetParameter(1).Value = pair.Key;

View File

@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
return; return;
} }
if (newIndex > oldIndex)
{
newIndex--;
}
var item = playlist.LinkedChildren[oldIndex]; var item = playlist.LinkedChildren[oldIndex];
playlist.LinkedChildren.Remove(item); playlist.LinkedChildren.Remove(item);
playlist.LinkedChildren.Insert(newIndex, item);
if (newIndex >= playlist.LinkedChildren.Count)
{
playlist.LinkedChildren.Add(item);
}
else
{
playlist.LinkedChildren.Insert(newIndex, item);
}
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
} }

View File

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

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) var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { typeof(Series).Name }, IncludeItemTypes = new[] { typeof(Series).Name },
@ -89,6 +94,11 @@ namespace MediaBrowser.Server.Implementations.TV
} }
} }
if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
{
limit = limit.Value + 10;
}
var items = _libraryManager.GetItemList(new InternalItemsQuery(user) var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { typeof(Series).Name }, IncludeItemTypes = new[] { typeof(Series).Name },
@ -115,7 +125,8 @@ namespace MediaBrowser.Server.Implementations.TV
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId))) .Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
.OrderByDescending(i => i.Item2) .OrderByDescending(i => i.Item2)
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
.Select(i => i.Item1); .Select(i => i.Item1)
.Take(request.Limit ?? int.MaxValue);
} }
private string GetUniqueSeriesKey(BaseItem series) private string GetUniqueSeriesKey(BaseItem series)
@ -143,7 +154,6 @@ namespace MediaBrowser.Server.Implementations.TV
SortOrder = SortOrder.Descending, SortOrder = SortOrder.Descending,
IsPlayed = true, IsPlayed = true,
Limit = 1, Limit = 1,
IsVirtualItem = false,
ParentIndexNumberNotEquals = 0 ParentIndexNumberNotEquals = 0
}).FirstOrDefault(); }).FirstOrDefault();

View File

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

View File

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

View File

@ -4,10 +4,10 @@
<package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" /> <package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
<package id="ini-parser" version="2.3.0" targetFramework="net45" /> <package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" /> <package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.52" targetFramework="net45" /> <package id="MediaBrowser.Naming" version="1.0.0.53" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" /> <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" /> <package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" /> <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
<package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" /> <package id="SocketHttpListener" version="1.0.0.35" targetFramework="net45" />
</packages> </packages>

View File

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

View File

@ -12,6 +12,7 @@ using System.Configuration.Install;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.ServiceProcess; using System.ServiceProcess;
using System.Threading; using System.Threading;
@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication
if (IsAlreadyRunning(applicationPath, currentProcess)) if (IsAlreadyRunning(applicationPath, currentProcess))
{ {
logger.Info("Shutting down because another instance of Media Browser Server is already running."); logger.Info("Shutting down because another instance of Emby Server is already running.");
return; return;
} }
@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
private static bool IsAlreadyRunning(string applicationPath, Process currentProcess) private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
{ {
var filename = Path.GetFileName(applicationPath);
var duplicate = Process.GetProcesses().FirstOrDefault(i => var duplicate = Process.GetProcesses().FirstOrDefault(i =>
{ {
try try
{ {
return string.Equals(filename, Path.GetFileName(i.MainModule.FileName)) && currentProcess.Id != i.Id; if (currentProcess.Id == i.Id)
{
return false;
}
}
catch (Exception)
{
return false;
}
try
{
//_logger.Info("Module: {0}", i.MainModule.FileName);
if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
} }
catch (Exception) catch (Exception)
{ {
@ -155,6 +171,41 @@ namespace MediaBrowser.ServerApplication
} }
} }
if (!_isRunningAsService)
{
return IsAlreadyRunningAsService(applicationPath);
}
return false;
}
private static bool IsAlreadyRunningAsService(string applicationPath)
{
var serviceName = BackgroundService.GetExistingServiceName();
WqlObjectQuery wqlObjectQuery = new WqlObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE State = 'Running' AND Name = '{0}'", serviceName));
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(wqlObjectQuery);
ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
foreach (ManagementObject managementObject in managementObjectCollection)
{
var obj = managementObject.GetPropertyValue("PathName");
if (obj == null)
{
continue;
}
var path = obj.ToString();
_logger.Info("Service path: {0}", path);
// Need to use indexOf instead of equality because the path will have the full service command line
if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1)
{
_logger.Info("The windows service is already running");
MessageBox.Show("Emby Server is already running as a Windows Service. Only one instance is allowed at a time. To run as a tray icon, shut down the Windows Service.");
return true;
}
}
return false; return false;
} }
@ -593,14 +644,32 @@ namespace MediaBrowser.ServerApplication
private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger) private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
{ {
// Reference
// http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
try try
{ {
var version = ImageMagickEncoder.GetVersion(); var subkey = Environment.Is64BitProcess
return; ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
: "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
.OpenSubKey(subkey))
{
if (ndpKey != null && ndpKey.GetValue("Version") != null)
{
var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
{
return;
}
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
logger.ErrorException("Error loading ImageMagick", ex); logger.ErrorException("Error getting .NET Framework version", ex);
return;
} }
try try

View File

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

View File

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

View File

@ -104,6 +104,9 @@
<Content Include="dashboard-ui\components\apphost.js"> <Content Include="dashboard-ui\components\apphost.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\components\categorysyncbuttons.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\channelmapper\channelmapper.js"> <Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -902,9 +905,6 @@
<Content Include="dashboard-ui\scripts\favorites.js"> <Content Include="dashboard-ui\scripts\favorites.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\librarylist.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\librarymenu.js"> <Content Include="dashboard-ui\scripts\librarymenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -1043,42 +1043,6 @@
<Content Include="dashboard-ui\userpassword.html"> <Content Include="dashboard-ui\userpassword.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\voice\commands\controlcommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\commands\disablecommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\commands\enablecommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\commands\playcommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\commands\searchcommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\commands\showcommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\commands\togglecommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\grammarprocessor.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\voicedialog.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\voice.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\voice.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\voice\voicecommands.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\wizardagreement.html"> <Content Include="dashboard-ui\wizardagreement.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -1674,15 +1638,6 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js"> <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<None Include="dashboard-ui\voice\grammar\en-US.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\voice\grammar\grammar.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\voice\Readme.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />