aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Drawing/ImageProcessor.cs53
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs9
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs36
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs11
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs22
-rw-r--r--MediaBrowser.Api/Reports/ReportsService.cs161
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs3
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs7
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs30
-rw-r--r--MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs6
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs22
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs23
-rw-r--r--MediaBrowser.Controller/Entities/IHasSeries.cs9
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs72
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs29
-rw-r--r--MediaBrowser.Controller/Net/IHttpResultFactory.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs50
-rw-r--r--MediaBrowser.Model.Portable/FodyWeavers.xml3
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj12
-rw-r--r--MediaBrowser.Model.Portable/packages.config5
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj3
-rw-r--r--MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs6
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs1
-rw-r--r--MediaBrowser.Model/Dlna/AudioOptions.cs6
-rw-r--r--MediaBrowser.Model/Dlna/CodecProfile.cs3
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs158
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs19
-rw-r--r--MediaBrowser.Model/Dto/BaseItemPerson.cs10
-rw-r--r--MediaBrowser.Model/Dto/ChapterInfoDto.cs8
-rw-r--r--MediaBrowser.Model/Dto/UserDto.cs8
-rw-r--r--MediaBrowser.Model/Dto/UserItemDataDto.cs7
-rw-r--r--MediaBrowser.Model/Entities/ChapterInfo.cs4
-rw-r--r--MediaBrowser.Model/Entities/DisplayPreferences.cs9
-rw-r--r--MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs8
-rw-r--r--MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs9
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelInfoDto.cs5
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs7
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs2
-rw-r--r--MediaBrowser.Model/Session/SessionInfoDto.cs5
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs9
-rw-r--r--MediaBrowser.Providers/TV/DummySeasonProvider.cs3
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs288
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs56
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs41
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs11
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs285
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs74
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs135
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs85
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs47
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs55
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs48
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs23
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs6
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs59
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj13
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs49
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs139
-rw-r--r--MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs24
-rw-r--r--MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs14
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs5
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs5
-rw-r--r--MediaBrowser.Server.Implementations/packages.config4
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs2
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs83
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj1
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs15
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj51
79 files changed, 1443 insertions, 1081 deletions
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index dc93cb730..3288229a2 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -163,7 +163,7 @@ namespace Emby.Drawing
return _imageEncoder.SupportedOutputFormats;
}
- public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options)
+ public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
{
if (options == null)
{
@@ -178,14 +178,13 @@ namespace Emby.Drawing
}
var originalImagePath = originalImage.Path;
+ var dateModified = originalImage.DateModified;
if (!_imageEncoder.SupportsImageEncoding)
{
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- var dateModified = originalImage.DateModified;
-
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
{
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@@ -211,7 +210,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath))
{
// Just spit out the original file if all the options are default
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
ImageSize? originalImageSize;
@@ -221,7 +220,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
{
// Just spit out the original file if all the options are default
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
}
catch
@@ -235,10 +234,6 @@ namespace Emby.Drawing
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
- var semaphore = GetLock(cacheFilePath);
-
- await semaphore.WaitAsync().ConfigureAwait(false);
-
var imageProcessingLockTaken = false;
try
@@ -251,15 +246,20 @@ namespace Emby.Drawing
var newHeight = Convert.ToInt32(newSize.Height);
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+ var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true;
- _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+ _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+ CopyFile(tmpPath, cacheFilePath);
+
+ return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
}
- return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath));
+ return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
}
catch (Exception ex)
{
@@ -267,7 +267,7 @@ namespace Emby.Drawing
_logger.ErrorException("Error encoding image", ex);
// Just spit out the original file if all the options are default
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
finally
{
@@ -275,8 +275,18 @@ namespace Emby.Drawing
{
_imageProcessingSemaphore.Release();
}
+ }
+ }
+
+ private void CopyFile(string src, string destination)
+ {
+ try
+ {
+ File.Copy(src, destination, true);
+ }
+ catch
+ {
- semaphore.Release();
}
}
@@ -412,14 +422,9 @@ namespace Emby.Drawing
var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
- var semaphore = GetLock(croppedImagePath);
-
- await semaphore.WaitAsync().ConfigureAwait(false);
-
// Check again in case of contention
if (_fileSystem.FileExists(croppedImagePath))
{
- semaphore.Release();
return GetResult(croppedImagePath);
}
@@ -428,11 +433,15 @@ namespace Emby.Drawing
try
{
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
+ var tmpPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N"));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true;
- _imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
+ _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
+ CopyFile(tmpPath, croppedImagePath);
+ return GetResult(tmpPath);
}
catch (NotImplementedException)
{
@@ -452,11 +461,7 @@ namespace Emby.Drawing
{
_imageProcessingSemaphore.Release();
}
-
- semaphore.Release();
}
-
- return GetResult(croppedImagePath);
}
private Tuple<string, DateTime> GetResult(string path)
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index a511f8c72..5866ad15b 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
{
var list = new List<ImageInfo>();
- foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
+ var itemImages = item.ImageInfos;
+
+ foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
{
var info = GetImageInfo(item, image, null);
@@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
}
}
- foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
+ foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
{
var index = 0;
// Prevent implicitly captured closure
var currentImageType = imageType;
- foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
+ foreach (var image in itemImages.Where(i => i.Type == currentImageType))
{
var info = GetImageInfo(item, image, index);
@@ -636,6 +638,7 @@ namespace MediaBrowser.Api.Images
CacheDuration = cacheDuration,
ResponseHeaders = headers,
ContentType = imageResult.Item2,
+ DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 0b4e5efe8..f60a106da 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -286,19 +286,25 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state)
{
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ // Only use alternative encoders for video files.
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ if (state.VideoType == VideoType.VideoFile)
{
- return "h264_qsv";
- }
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_qsv";
+ }
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
- {
- return "h264_nvenc";
- }
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
- {
- return "h264_omx";
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_nvenc";
+ }
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_omx";
+ }
}
return "libx264";
@@ -843,6 +849,14 @@ namespace MediaBrowser.Api.Playback
return null;
}
+ // Only use alternative encoders for video files.
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ if (state.VideoType != VideoType.VideoFile)
+ {
+ return null;
+ }
+
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 868d8d488..d8b7ce2ef 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -13,6 +13,7 @@ using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
@@ -336,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
state.Dispose();
}
- var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
+ var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- result.Options["Content-Type"] = contentType;
+ outputHeaders["Content-Type"] = contentType;
// Add the response headers to the result object
foreach (var item in responseHeaders)
{
- result.Options[item.Key] = item.Value;
+ outputHeaders[item.Key] = item.Value;
}
- return result;
+ Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream);
+
+ return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
}
finally
{
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index 9f02c51cd..bf543579f 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -48,21 +48,19 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
- WriteToInternal(responseStream);
+ var task = WriteToAsync(responseStream);
+ Task.WaitAll(task);
}
/// <summary>
- /// Writes to async.
+ /// Writes to.
/// </summary>
/// <param name="responseStream">The response stream.</param>
- /// <returns>Task.</returns>
- private void WriteToInternal(Stream responseStream)
+ public async Task WriteToAsync(Stream responseStream)
{
try
{
- var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream);
-
- Task.WaitAll(task);
+ await new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream).ConfigureAwait(false);
}
catch (IOException)
{
@@ -110,11 +108,11 @@ namespace MediaBrowser.Api.Playback.Progressive
var eofCount = 0;
long position = 0;
- using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false))
+ using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{
while (eofCount < 15)
{
- CopyToInternal(fs, outputStream, BufferSize);
+ await CopyToInternal(fs, outputStream, BufferSize).ConfigureAwait(false);
var fsPosition = fs.Position;
@@ -140,11 +138,11 @@ namespace MediaBrowser.Api.Playback.Progressive
}
}
- private void CopyToInternal(Stream source, Stream destination, int bufferSize)
+ private async Task CopyToInternal(Stream source, Stream destination, int bufferSize)
{
var array = new byte[bufferSize];
int count;
- while ((count = source.Read(array, 0, array.Length)) != 0)
+ while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
{
//if (_job != null)
//{
@@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// }
//}
- destination.Write(array, 0, count);
+ await destination.WriteAsync(array, 0, count).ConfigureAwait(false);
_bytesWritten += count;
diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs
index 36a2a4b61..d0b6d6e78 100644
--- a/MediaBrowser.Api/Reports/ReportsService.cs
+++ b/MediaBrowser.Api/Reports/ReportsService.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
/// <summary> Manager for library. </summary>
private readonly ILibraryManager _libraryManager; ///< Manager for library
- /// <summary> The localization. </summary>
+ /// <summary> The localization. </summary>
private readonly ILocalizationManager _localization; ///< The localization
@@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets the given request. </summary>
/// <param name="request"> The request. </param>
/// <returns> A Task&lt;object&gt; </returns>
- public async Task<object> Get(GetActivityLogs request)
+ public object Get(GetActivityLogs request)
{
request.DisplayType = "Screen";
- ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
+ ReportResult result = GetReportActivities(request);
return ToOptimizedResult(result);
}
@@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
return null;
request.DisplayType = "Screen";
- var reportResult = await GetReportResult(request);
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+ var reportResult = await GetReportResult(request, user);
return ToOptimizedResult(reportResult);
}
@@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
if (string.IsNullOrEmpty(request.IncludeItemTypes))
return null;
request.DisplayType = "Screen";
- var reportResult = await GetReportStatistic(request);
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+ var reportResult = await GetReportStatistic(request, user);
return ToOptimizedResult(reportResult);
}
@@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
headers["Content-Encoding"] = "UTF-8";
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
ReportResult result = null;
switch (reportViewType)
{
@@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
case ReportViewType.ReportData:
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
- QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+ QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
result = dataBuilder.GetResult(queryResult.Items, request);
result.TotalRecordCount = queryResult.TotalRecordCount;
break;
case ReportViewType.ReportActivities:
- result = await GetReportActivities(request).ConfigureAwait(false);
+ result = GetReportActivities(request);
break;
}
@@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
break;
}
- object ro = ResultFactory.GetResult(returnResult, contentType, headers);
- return ro;
+ return ResultFactory.GetResult(returnResult, contentType, headers);
}
#endregion
- #region [Private Methods]
-
- /// <summary> Gets items query. </summary>
- /// <param name="request"> The request. </param>
- /// <param name="user"> The user. </param>
- /// <returns> The items query. </returns>
private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
{
- var query = new InternalItemsQuery
+ var query = new InternalItemsQuery(user)
{
- User = user,
IsPlayed = request.IsPlayed,
MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(),
@@ -231,6 +226,7 @@ namespace MediaBrowser.Api.Reports
Tags = request.GetTags(),
OfficialRatings = request.GetOfficialRatings(),
Genres = request.GetGenres(),
+ GenreIds = request.GetGenreIds(),
Studios = request.GetStudios(),
StudioIds = request.GetStudioIds(),
Person = request.Person,
@@ -245,9 +241,11 @@ namespace MediaBrowser.Api.Reports
MaxPlayers = request.MaxPlayers,
MinCommunityRating = request.MinCommunityRating,
MinCriticRating = request.MinCriticRating,
+ ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason,
- AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
+ AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+ EnableTotalRecordCount = request.EnableTotalRecordCount
};
if (!string.IsNullOrWhiteSpace(request.Ids))
@@ -357,98 +355,111 @@ namespace MediaBrowser.Api.Reports
query.AlbumNames = request.Albums.Split('|');
}
- if (request.HasQueryLimit == false)
- {
- query.StartIndex = null;
- query.Limit = null;
- }
-
return query;
}
- /// <summary> Gets query result. </summary>
- /// <param name="request"> The request. </param>
- /// <returns> The query result. </returns>
- private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+ private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
{
- // Placeholder in case needed later
+ // all report queries currently need this because it's not being specified
request.Recursive = true;
- var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
- request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
-
- var parentItem = string.IsNullOrEmpty(request.ParentId) ?
- (user == null ? _libraryManager.RootFolder : user.RootFolder) :
- _libraryManager.GetItemById(request.ParentId);
var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder :
- parentItem;
+ _libraryManager.GetItemById(request.ParentId);
- IEnumerable<BaseItem> items;
+ if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+ {
+ //item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+ }
+ else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+ {
+ item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+ }
- if (request.Recursive)
+ // Default list type = children
+
+ var folder = item as Folder;
+ if (folder == null)
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
- return result;
+ folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
}
- else
+
+ if (!string.IsNullOrEmpty(request.Ids))
{
- if (user == null)
+ request.Recursive = true;
+ var query = GetItemsQuery(request, user);
+ var result = await folder.GetItems(query).ConfigureAwait(false);
+
+ if (string.IsNullOrWhiteSpace(request.SortBy))
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
- return result;
+ var ids = query.ItemIds.ToList();
+
+ // Try to preserve order
+ result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
- var userRoot = item as UserRootFolder;
+ return result;
+ }
- if (userRoot == null)
- {
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ if (request.Recursive)
+ {
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ }
- return result;
- }
+ if (user == null)
+ {
+ return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+ }
+
+ var userRoot = item as UserRootFolder;
- items = ((Folder)item).GetChildren(user, true);
+ if (userRoot == null)
+ {
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
- return new QueryResult<BaseItem> { Items = items.ToArray() };
+ IEnumerable<BaseItem> items = folder.GetChildren(user, true);
+ var itemsArray = items.ToArray();
+
+ return new QueryResult<BaseItem>
+ {
+ Items = itemsArray,
+ TotalRecordCount = itemsArray.Length
+ };
}
+ #region [Private Methods]
+
/// <summary> Gets report activities. </summary>
/// <param name="request"> The request. </param>
/// <returns> The report activities. </returns>
- private Task<ReportResult> GetReportActivities(IReportsDownload request)
+ private ReportResult GetReportActivities(IReportsDownload request)
{
- return Task<ReportResult>.Run(() =>
- {
- DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
- (DateTime?)null :
- DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
- QueryResult<ActivityLogEntry> queryResult;
- if (request.HasQueryLimit)
- queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
- else
- queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
- //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-
- ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
- var result = builder.GetResult(queryResult, request);
- result.TotalRecordCount = queryResult.TotalRecordCount;
- return result;
+ DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
+ (DateTime?)null :
+ DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- });
+ QueryResult<ActivityLogEntry> queryResult;
+ if (request.HasQueryLimit)
+ queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+ else
+ queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
+ //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+ ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
+ var result = builder.GetResult(queryResult, request);
+ result.TotalRecordCount = queryResult.TotalRecordCount;
+ return result;
}
/// <summary> Gets report result. </summary>
/// <param name="request"> The request. </param>
/// <returns> The report result. </returns>
- private async Task<ReportResult> GetReportResult(GetItemReport request)
+ private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
{
ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
- QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+ QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
reportResult.TotalRecordCount = queryResult.TotalRecordCount;
@@ -458,10 +469,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets report statistic. </summary>
/// <param name="request"> The request. </param>
/// <returns> The report statistic. </returns>
- private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+ private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
{
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
- QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+ QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs
index dfb82438e..1bebd42eb 100644
--- a/MediaBrowser.Api/StartupWizardService.cs
+++ b/MediaBrowser.Api/StartupWizardService.cs
@@ -114,11 +114,10 @@ namespace MediaBrowser.Api
private void SetWizardFinishValues(ServerConfiguration config)
{
config.EnableLocalizedGuids = true;
- config.EnableCustomPathSubFolders = true;
config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true;
//config.EnableFolderView = true;
- config.SchemaVersion = 97;
+ config.SchemaVersion = 108;
}
public void Post(UpdateStartupConfiguration request)
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
index d27a560ba..5a66f872f 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -103,6 +103,8 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsInBoxSet { get; set; }
+ public string ExcludeItemIds { get; set; }
+
public bool EnableTotalRecordCount { get; set; }
/// <summary>
@@ -367,6 +369,11 @@ namespace MediaBrowser.Api.UserLibrary
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
+ public string[] GetExcludeItemIds()
+ {
+ return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
public string[] GetExcludeItemTypes()
{
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index a6f1d8b98..74b33fbb7 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
- var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
+ var result = await GetQueryResult(request, user).ConfigureAwait(false);
if (result == null)
{
@@ -135,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
- private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user)
+ private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
{
var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder :
@@ -263,7 +263,8 @@ namespace MediaBrowser.Api.UserLibrary
ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason,
AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
- EnableTotalRecordCount = request.EnableTotalRecordCount
+ EnableTotalRecordCount = request.EnableTotalRecordCount,
+ ExcludeItemIds = request.GetExcludeItemIds()
};
if (!string.IsNullOrWhiteSpace(request.Ids))
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 2b7cc10aa..371757f6c 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -140,7 +140,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private WebRequest GetRequest(HttpRequestOptions options, string method)
{
- var request = CreateWebRequest(options.Url);
+ var url = options.Url;
+
+ var uriAddress = new Uri(url);
+ var userInfo = uriAddress.UserInfo;
+ if (!string.IsNullOrWhiteSpace(userInfo))
+ {
+ _logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
+ url = url.Replace(userInfo + "@", string.Empty);
+ }
+
+ var request = CreateWebRequest(url);
var httpWebRequest = request as HttpWebRequest;
if (httpWebRequest != null)
@@ -183,9 +193,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
+ if (!string.IsNullOrWhiteSpace(userInfo))
+ {
+ var parts = userInfo.Split(':');
+ if (parts.Length == 2)
+ {
+ request.Credentials = GetCredential(url, parts[0], parts[1]);
+ request.PreAuthenticate = true;
+ }
+ }
+
return request;
}
+ private CredentialCache GetCredential(string url, string username, string password)
+ {
+ //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
+ CredentialCache credentialCache = new CredentialCache();
+ credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
+ return credentialCache;
+ }
+
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
{
foreach (var header in options.RequestHeaders.ToList())
diff --git a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
index 2ffaedc4b..d1ec30210 100644
--- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
+++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
@@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
{
if (updateLevel == PackageVersionClass.Release)
{
- obj = obj.Where(i => !i.prerelease).ToArray();
+ // Technically all we need to do is check that it's not pre-release
+ // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
+ obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
}
else if (updateLevel == PackageVersionClass.Beta)
{
@@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
.Where(i => i != null)
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
.FirstOrDefault();
-
+
return availableUpdate ?? new CheckForUpdateResult
{
IsUpdateAvailable = false
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index d42a04f2e..19f391b4a 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
- Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
+ Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the enhanced image.
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 9d1a45689..0860cb61c 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1878,7 +1878,7 @@ namespace MediaBrowser.Controller.Entities
return new ItemImageInfo
{
Path = path,
- DateModified = FileSystem.GetLastWriteTimeUtc(path),
+ DateModified = chapter.ImageDateModified,
Type = imageType
};
}
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 96fad670e..2ca56bc70 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Providers;
+using System;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using System.Linq;
using System.Runtime.Serialization;
@@ -17,7 +18,26 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
public string SeriesName { get; set; }
+ [IgnoreDataMember]
+ public Guid? SeriesId { get; set; }
+ [IgnoreDataMember]
+ public string SeriesSortName { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ return SeriesSortName;
+ }
+ public string FindSeriesName()
+ {
+ return SeriesName;
+ }
+
+ public Guid? FindSeriesId()
+ {
+ return SeriesId;
+ }
public override bool CanDownload()
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 210c11564..89b9479e7 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -760,11 +760,6 @@ namespace MediaBrowser.Controller.Entities
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
return true;
}
- if (query.SortBy.Contains(ItemSortBy.SeriesSortName, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
- return true;
- }
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
@@ -859,24 +854,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.IsMissing.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsMissing");
- return true;
- }
-
- if (query.IsUnaired.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsUnaired");
- return true;
- }
-
- if (query.IsVirtualUnaired.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsVirtualUnaired");
- return true;
- }
-
if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
{
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs
index 64c33a376..531f58788 100644
--- a/MediaBrowser.Controller/Entities/IHasSeries.cs
+++ b/MediaBrowser.Controller/Entities/IHasSeries.cs
@@ -1,4 +1,6 @@

+using System;
+
namespace MediaBrowser.Controller.Entities
{
public interface IHasSeries
@@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Entities
/// Gets the name of the series.
/// </summary>
/// <value>The name of the series.</value>
- string SeriesName { get; }
+ string SeriesName { get; set; }
+ string FindSeriesName();
+ string SeriesSortName { get; set; }
+ string FindSeriesSortName();
+ Guid? SeriesId { get; set; }
+ Guid? FindSeriesId();
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 2dc459239..726390f65 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
-
public Episode()
{
RemoteTrailers = new List<MediaUrl>();
@@ -25,11 +24,11 @@ namespace MediaBrowser.Controller.Entities.TV
public List<Guid> RemoteTrailerIds { get; set; }
public List<MediaUrl> RemoteTrailers { get; set; }
- /// <summary>
- /// Gets the season in which it aired.
- /// </summary>
- /// <value>The aired season.</value>
- public int? AirsBeforeSeasonNumber { get; set; }
+ /// <summary>
+ /// Gets the season in which it aired.
+ /// </summary>
+ /// <value>The aired season.</value>
+ public int? AirsBeforeSeasonNumber { get; set; }
public int? AirsAfterSeasonNumber { get; set; }
public int? AirsBeforeEpisodeNumber { get; set; }
@@ -54,7 +53,16 @@ namespace MediaBrowser.Controller.Entities.TV
/// This is the ending episode number for double episodes.
/// </summary>
/// <value>The index number.</value>
- public int? IndexNumberEnd { get; set; }
+ public int? IndexNumberEnd { get; set; }
+
+ [IgnoreDataMember]
+ public string SeriesSortName { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ var series = Series;
+ return series == null ? SeriesSortName : series.SortName;
+ }
[IgnoreDataMember]
protected override bool SupportsOwnedItems
@@ -166,13 +174,27 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public string SeriesName
- {
- get
- {
- var series = Series;
- return series == null ? null : series.Name;
- }
+ public string SeriesName { get; set; }
+
+ [IgnoreDataMember]
+ public string SeasonName { get; set; }
+
+ public string FindSeasonName()
+ {
+ var season = Season;
+ return season == null ? SeasonName : season.Name;
+ }
+
+ public string FindSeriesName()
+ {
+ var series = Series;
+ return series == null ? SeriesName : series.Name;
+ }
+
+ public Guid? FindSeasonId()
+ {
+ var season = Season;
+ return season == null ? (Guid?)null : season.Id;
}
/// <summary>
@@ -235,20 +257,14 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public Guid? SeasonId
- {
- get
- {
- // First see if the parent is a Season
- var season = Season;
-
- if (season != null)
- {
- return season.Id;
- }
-
- return null;
- }
+ public Guid? SeasonId { get; set; }
+ [IgnoreDataMember]
+ public Guid? SeriesId { get; set; }
+
+ public Guid? FindSeriesId()
+ {
+ var series = Series;
+ return series == null ? (Guid?)null : series.Id;
}
public override IEnumerable<Guid> GetAncestorIds()
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 9a9014844..ee01c60b1 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -51,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
+ [IgnoreDataMember]
+ public string SeriesSortName { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ var series = Series;
+ return series == null ? SeriesSortName : series.SortName;
+ }
+
// Genre, Rating and Stuido will all be the same
protected override IEnumerable<string> GetIndexByOptions()
{
@@ -235,13 +244,21 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public string SeriesName
+ public string SeriesName { get; set; }
+
+ [IgnoreDataMember]
+ public Guid? SeriesId { get; set; }
+
+ public string FindSeriesName()
{
- get
- {
- var series = Series;
- return series == null ? null : series.Name;
- }
+ var series = Series;
+ return series == null ? SeriesName : series.Name;
+ }
+
+ public Guid? FindSeriesId()
+ {
+ var series = Series;
+ return series == null ? (Guid?)null : series.Id;
}
/// <summary>
diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
index cd7ee603e..49d4614d8 100644
--- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs
+++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
@@ -28,6 +28,8 @@ namespace MediaBrowser.Controller.Net
/// <returns>System.Object.</returns>
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
+ object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null);
+
/// <summary>
/// Gets the optimized result.
/// </summary>
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index 725f0bc6d..32cd950af 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -366,6 +366,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return null;
}
+ // Only use alternative encoders for video files.
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ if (state.VideoType != VideoType.VideoFile)
+ {
+ return null;
+ }
+
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{
if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 9a83aba88..a63aca11b 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -120,6 +120,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
bool preserveOriginalTimestamps,
CancellationToken cancellationToken)
{
+ if (string.IsNullOrWhiteSpace(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ throw new ArgumentNullException("mediaSourceId");
+ }
+
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
.ConfigureAwait(false);
@@ -141,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex,
CancellationToken cancellationToken)
{
+ if (string.IsNullOrWhiteSpace(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ throw new ArgumentNullException("mediaSourceId");
+ }
+
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
- .First(i => string.Equals(i.Id, mediaSourceId));
+ .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
var subtitleStream = mediaSource.MediaStreams
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
@@ -609,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw;
}
- process.StandardError.BaseStream.CopyToAsync(logFileStream);
+ // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
+ Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream));
var ranToCompletion = process.WaitForExit(300000);
@@ -686,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
+ private async Task StartStreamingLog(Stream source, Stream target)
+ {
+ try
+ {
+ using (var reader = new StreamReader(source))
+ {
+ while (!reader.EndOfStream)
+ {
+ var line = await reader.ReadLineAsync().ConfigureAwait(false);
+
+ var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
+
+ await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ await target.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error reading ffmpeg log", ex);
+ }
+ }
+
/// <summary>
/// Sets the ass font.
/// </summary>
diff --git a/MediaBrowser.Model.Portable/FodyWeavers.xml b/MediaBrowser.Model.Portable/FodyWeavers.xml
index 736992810..6e2fa02e6 100644
--- a/MediaBrowser.Model.Portable/FodyWeavers.xml
+++ b/MediaBrowser.Model.Portable/FodyWeavers.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Weavers>
- <PropertyChanged />
</Weavers> \ No newline at end of file
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 0de9fb519..862d95f7e 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -36,6 +36,8 @@
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<RestorePackages>true</RestorePackages>
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -617,9 +619,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
- <Link>Extensions\IHasPropertyChangedEvent.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link>
</Compile>
@@ -1233,13 +1232,6 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
- <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
- <PropertyGroup>
- <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
- </PropertyGroup>
- <Error Condition="!Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
- </Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.Model.Portable/packages.config b/MediaBrowser.Model.Portable/packages.config
deleted file mode 100644
index cd4eb36a4..000000000
--- a/MediaBrowser.Model.Portable/packages.config
+++ /dev/null
@@ -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> \ No newline at end of file
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index fe0b3bcae..953e5b7be 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -591,9 +591,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
- <Link>Extensions\IHasPropertyChangedEvent.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
index 2b53c6688..c4f9f206d 100644
--- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
@@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The cache path.</value>
public string CachePath { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [enable custom path sub folders].
- /// </summary>
- /// <value><c>true</c> if [enable custom path sub folders]; otherwise, <c>false</c>.</value>
- public bool EnableCustomPathSubFolders { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 081c46f0a..58b74ba64 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -214,7 +214,6 @@ namespace MediaBrowser.Model.Configuration
Migrations = new string[] { };
SqliteCacheSize = 0;
- EnableCustomPathSubFolders = true;
EnableLocalizedGuids = true;
DisplaySpecialsWithinSeasons = true;
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs
index 6ad4fa265..162b88c98 100644
--- a/MediaBrowser.Model/Dlna/AudioOptions.cs
+++ b/MediaBrowser.Model/Dlna/AudioOptions.cs
@@ -11,8 +11,14 @@ namespace MediaBrowser.Model.Dlna
public AudioOptions()
{
Context = EncodingContext.Streaming;
+
+ EnableDirectPlay = true;
+ EnableDirectStream = true;
}
+ public bool EnableDirectPlay { get; set; }
+ public bool EnableDirectStream { get; set; }
+
public string ItemId { get; set; }
public List<MediaSourceInfo> MediaSources { get; set; }
public DeviceProfile Profile { get; set; }
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs
index 7200f648c..385e98f61 100644
--- a/MediaBrowser.Model/Dlna/CodecProfile.cs
+++ b/MediaBrowser.Model/Dlna/CodecProfile.cs
@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Dlna
public ProfileCondition[] Conditions { get; set; }
+ public ProfileCondition[] ApplyConditions { get; set; }
+
[XmlAttribute("codec")]
public string Codec { get; set; }
@@ -20,6 +22,7 @@ namespace MediaBrowser.Model.Dlna
public CodecProfile()
{
Conditions = new ProfileCondition[] {};
+ ApplyConditions = new ProfileCondition[] { };
}
public List<string> GetCodecs()
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index b204cf14e..2863eba2e 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -131,6 +131,11 @@ namespace MediaBrowser.Model.Dlna
List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options);
+ ConditionProcessor conditionProcessor = new ConditionProcessor();
+
+ int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
+ int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
+
if (directPlayMethods.Count > 0)
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
@@ -138,27 +143,36 @@ namespace MediaBrowser.Model.Dlna
// Make sure audio codec profiles are satisfied
if (!string.IsNullOrEmpty(audioCodec))
{
- ConditionProcessor conditionProcessor = new ConditionProcessor();
-
List<ProfileCondition> conditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- conditions.Add(c);
+ if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
+ {
+ LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ conditions.Add(c);
+ }
}
}
}
- int? audioChannels = audioStream.Channels;
- int? audioBitrate = audioStream.BitRate;
-
bool all = true;
foreach (ProfileCondition c in conditions)
{
- if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate))
+ if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
all = false;
@@ -241,9 +255,23 @@ namespace MediaBrowser.Model.Dlna
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in audioCodecProfiles)
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- audioTranscodingConditions.Add(c);
+ if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
+ {
+ LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ audioTranscodingConditions.Add(c);
+ }
}
}
@@ -294,7 +322,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayProfile != null)
{
// While options takes the network and other factors into account. Only applies to direct stream
- if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()))
+ if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream)
{
playMethods.Add(PlayMethod.DirectStream);
}
@@ -302,7 +330,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay &&
- IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
+ IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
@@ -385,8 +413,8 @@ namespace MediaBrowser.Model.Dlna
MediaStream videoStream = item.VideoStream;
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
- bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
- bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
+ bool isEligibleForDirectStream = options.EnableDirectStream && IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
options.Profile.Name ?? "Unknown Profile",
@@ -464,17 +492,37 @@ namespace MediaBrowser.Model.Dlna
}
playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex;
+ ConditionProcessor conditionProcessor = new ConditionProcessor();
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- videoTranscodingConditions.Add(c);
+ bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream);
+ int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate;
+ int? audioChannels = audioStream == null ? null : audioStream.Channels;
+ string audioProfile = audioStream == null ? null : audioStream.Profile;
+
+ if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, audioProfile, isSecondaryAudio))
+ {
+ LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ videoTranscodingConditions.Add(c);
+ }
+ break;
}
- break;
}
}
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
@@ -484,11 +532,42 @@ namespace MediaBrowser.Model.Dlna
{
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- audioTranscodingConditions.Add(c);
+ int? width = videoStream == null ? null : videoStream.Width;
+ int? height = videoStream == null ? null : videoStream.Height;
+ int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
+ int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
+ double? videoLevel = videoStream == null ? null : videoStream.Level;
+ string videoProfile = videoStream == null ? null : videoStream.Profile;
+ float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
+ bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
+ string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
+
+ TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
+ int? packetLength = videoStream == null ? null : videoStream.PacketLength;
+ int? refFrames = videoStream == null ? null : videoStream.RefFrames;
+
+ int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
+ int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
+
+ if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ {
+ LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ audioTranscodingConditions.Add(c);
+ }
+ break;
}
- break;
}
}
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
@@ -667,9 +746,23 @@ namespace MediaBrowser.Model.Dlna
{
if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- conditions.Add(c);
+ if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ {
+ LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ conditions.Add(c);
+ }
}
}
}
@@ -698,20 +791,35 @@ namespace MediaBrowser.Model.Dlna
}
conditions = new List<ProfileCondition>();
+ bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
+
foreach (CodecProfile i in profile.CodecProfiles)
{
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- conditions.Add(c);
+ if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
+ {
+ LogConditionFailure(profile, "VideoAudioCodecProfile", applyCondition, mediaSource);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ conditions.Add(c);
+ }
}
}
}
foreach (ProfileCondition i in conditions)
{
- bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 146fcc74e..8ca1dfcb1 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Dto
/// This holds information about a BaseItem in a format that is convenient for the client.
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
- public class BaseItemDto : IHasProviderIds, IHasPropertyChangedEvent, IItemDto, IHasServerId, IHasSyncInfo
+ public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId, IHasSyncInfo
{
/// <summary>
/// Gets or sets the name.
@@ -114,6 +114,8 @@ namespace MediaBrowser.Model.Dto
/// <value>The synchronize percent.</value>
public double? SyncPercent { get; set; }
+ public string Container { get; set; }
+
/// <summary>
/// Gets or sets the DVD season number.
/// </summary>
@@ -955,6 +957,16 @@ namespace MediaBrowser.Model.Dto
}
/// <summary>
+ /// Gets a value indicating whether this instance has thumb.
+ /// </summary>
+ /// <value><c>true</c> if this instance has thumb; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool HasBackdrop
+ {
+ get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); }
+ }
+
+ /// <summary>
/// Gets a value indicating whether this instance has primary image.
/// </summary>
/// <value><c>true</c> if this instance has primary image; otherwise, <c>false</c>.</value>
@@ -1100,11 +1112,6 @@ namespace MediaBrowser.Model.Dto
}
/// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs
index 8e7750562..b74912907 100644
--- a/MediaBrowser.Model/Dto/BaseItemPerson.cs
+++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Extensions;
-using System.ComponentModel;
+using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
@@ -9,7 +8,7 @@ namespace MediaBrowser.Model.Dto
/// This is used by the api to get information about a Person within a BaseItem
/// </summary>
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
- public class BaseItemPerson : IHasPropertyChangedEvent
+ public class BaseItemPerson
{
/// <summary>
/// Gets or sets the name.
@@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
return PrimaryImageTag != null;
}
}
-
- /// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/Dto/ChapterInfoDto.cs b/MediaBrowser.Model/Dto/ChapterInfoDto.cs
index 62b1839d4..a71d97990 100644
--- a/MediaBrowser.Model/Dto/ChapterInfoDto.cs
+++ b/MediaBrowser.Model/Dto/ChapterInfoDto.cs
@@ -1,7 +1,5 @@
-using System.ComponentModel;
-using System.Diagnostics;
+using System.Diagnostics;
using System.Runtime.Serialization;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dto
{
@@ -9,7 +7,7 @@ namespace MediaBrowser.Model.Dto
/// Class ChapterInfo
/// </summary>
[DebuggerDisplay("Name = {Name}")]
- public class ChapterInfoDto : IHasPropertyChangedEvent
+ public class ChapterInfoDto
{
/// <summary>
/// Gets or sets the start position ticks.
@@ -38,7 +36,5 @@ namespace MediaBrowser.Model.Dto
{
get { return ImageTag != null; }
}
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs
index f133f3343..18470466c 100644
--- a/MediaBrowser.Model/Dto/UserDto.cs
+++ b/MediaBrowser.Model/Dto/UserDto.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Users;
using System;
using System.ComponentModel;
@@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Dto
/// Class UserDto
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
- public class UserDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
+ public class UserDto : IItemDto, IHasServerId
{
/// <summary>
/// Gets or sets the name.
@@ -141,11 +140,6 @@ namespace MediaBrowser.Model.Dto
Policy = new UserPolicy();
}
- /// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
public override string ToString()
{
return Name ?? base.ToString();
diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs
index ce0c8fa99..00491002b 100644
--- a/MediaBrowser.Model/Dto/UserItemDataDto.cs
+++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Extensions;
-using System;
+using System;
using System.ComponentModel;
namespace MediaBrowser.Model.Dto
@@ -7,7 +6,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Class UserItemDataDto
/// </summary>
- public class UserItemDataDto : IHasPropertyChangedEvent
+ public class UserItemDataDto
{
/// <summary>
/// Gets or sets the rating.
@@ -74,7 +73,5 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The item identifier.</value>
public string ItemId { get; set; }
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs
index 9da7a9caa..7e5700965 100644
--- a/MediaBrowser.Model/Entities/ChapterInfo.cs
+++ b/MediaBrowser.Model/Entities/ChapterInfo.cs
@@ -1,4 +1,5 @@
-
+using System;
+
namespace MediaBrowser.Model.Entities
{
/// <summary>
@@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
+ public DateTime ImageDateModified { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs
index 6399670c9..0ba8eaa4f 100644
--- a/MediaBrowser.Model/Entities/DisplayPreferences.cs
+++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs
@@ -1,22 +1,15 @@
using MediaBrowser.Model.Drawing;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Entities
{
/// <summary>
/// Defines the display preferences for any item that supports them (usually Folders)
/// </summary>
- public class DisplayPreferences : IHasPropertyChangedEvent
+ public class DisplayPreferences
{
/// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
/// The image scale
/// </summary>
private const double ImageScale = .9;
diff --git a/MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs b/MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs
deleted file mode 100644
index c87550620..000000000
--- a/MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System.ComponentModel;
-
-namespace MediaBrowser.Model.Extensions
-{
- public interface IHasPropertyChangedEvent : INotifyPropertyChanged
- {
- }
-}
diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
index 296ead5c2..4d863c6eb 100644
--- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
@@ -1,18 +1,11 @@
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Extensions;
using System;
-using System.ComponentModel;
namespace MediaBrowser.Model.LiveTv
{
- public class BaseTimerInfoDto : IHasPropertyChangedEvent, IHasServerId
+ public class BaseTimerInfoDto : IHasServerId
{
/// <summary>
- /// Occurs when a property value changes.
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
/// Id of the recording.
/// </summary>
public string Id { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
index 91493def8..aa91e3c74 100644
--- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Library;
using System.Collections.Generic;
using System.ComponentModel;
@@ -13,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
/// Class ChannelInfoDto
/// </summary>
[DebuggerDisplay("Name = {Name}, Number = {Number}")]
- public class ChannelInfoDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
+ public class ChannelInfoDto : IItemDto, IHasServerId
{
/// <summary>
/// Gets or sets the name.
@@ -120,7 +119,5 @@ namespace MediaBrowser.Model.LiveTv
ImageTags = new Dictionary<ImageType, string>();
MediaSources = new List<MediaSourceInfo>();
}
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index c33535a3d..a95678fae 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -4,6 +4,11 @@ namespace MediaBrowser.Model.LiveTv
{
public class TimerInfoDto : BaseTimerInfoDto
{
+ public TimerInfoDto()
+ {
+ Type = "Timer";
+ }
+
/// <summary>
/// Gets or sets the status.
/// </summary>
@@ -22,6 +27,8 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The external series timer identifier.</value>
public string ExternalSeriesTimerId { get; set; }
+ public string Type { get; set; }
+
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index e54273b84..931b2ebcb 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -229,7 +229,6 @@
<Compile Include="Entities\SortOrder.cs" />
<Compile Include="Events\GenericEventArgs.cs" />
<Compile Include="Extensions\DoubleHelper.cs" />
- <Compile Include="Extensions\IHasPropertyChangedEvent.cs" />
<Compile Include="Extensions\IntHelper.cs" />
<Compile Include="Extensions\ListHelper.cs" />
<Compile Include="Extensions\StringHelper.cs" />
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index cea638a39..21f87247a 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -197,6 +197,8 @@
/// </summary>
SeriesGenres,
+ SeriesPrimaryImage,
+
/// <summary>
/// The series studio
/// </summary>
diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs
index da8ab9b8a..5c3c9a79c 100644
--- a/MediaBrowser.Model/Session/SessionInfoDto.cs
+++ b/MediaBrowser.Model/Session/SessionInfoDto.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -8,7 +7,7 @@ using System.Diagnostics;
namespace MediaBrowser.Model.Session
{
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
- public class SessionInfoDto : IHasPropertyChangedEvent
+ public class SessionInfoDto
{
/// <summary>
/// Gets or sets the supported commands.
@@ -116,8 +115,6 @@ namespace MediaBrowser.Model.Session
public TranscodingInfo TranscodingInfo { get; set; }
- public event PropertyChangedEventHandler PropertyChanged;
-
public SessionInfoDto()
{
AdditionalUsers = new List<SessionUserInfo>();
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index d2c0feabe..f64b7b792 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -72,7 +72,10 @@ namespace MediaBrowser.Providers.MediaInfo
// Try to translate to three character code
// Be flexible and check against both the full and three character versions
var culture = _localization.GetCultures()
- .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
+ .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
if (culture != null)
{
@@ -99,10 +102,12 @@ namespace MediaBrowser.Providers.MediaInfo
private string NormalizeFilenameForSubtitleComparison(string filename)
{
// Try to account for sloppy file naming
- filename = filename.Replace("-", string.Empty);
filename = filename.Replace("_", string.Empty);
filename = filename.Replace(" ", string.Empty);
+ // can't normalize this due to languages such as pt-br
+ //filename = filename.Replace("-", string.Empty);
+
//filename = filename.Replace(".", string.Empty);
return filename;
diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
index 909760fee..baea0a06d 100644
--- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
@@ -111,7 +111,8 @@ namespace MediaBrowser.Providers.TV
Name = seasonName,
IndexNumber = seasonNumber,
Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
- IsVirtualItem = isVirtualItem
+ IsVirtualItem = isVirtualItem,
+ SeriesId = series.Id
};
season.SetParent(series);
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index 4e2d9a8d2..22e126795 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -429,7 +429,9 @@ namespace MediaBrowser.Providers.TV
IndexNumber = episodeNumber,
ParentIndexNumber = seasonNumber,
Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
- IsVirtualItem = true
+ IsVirtualItem = true,
+ SeasonId = season == null ? (Guid?)null : season.Id,
+ SeriesId = series.Id
};
episode.SetParent(season);
diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 7db457c6e..e8669bbc2 100644
--- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -95,13 +95,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
{
metadataPath = GetInternalMetadataPath();
}
- else if (Configuration.EnableCustomPathSubFolders)
- {
- metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
- }
else
{
- metadataPath = Configuration.MetadataPath;
+ metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
}
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 8805d567a..616625bc9 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -663,29 +663,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.GameSystem = item.GameSystemName;
}
- private List<string> GetBackdropImageTags(BaseItem item, int limit)
+ private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
{
- return GetCacheTags(item, ImageType.Backdrop, limit).ToList();
- }
-
- private List<string> GetScreenshotImageTags(BaseItem item, int limit)
- {
- var hasScreenshots = item as IHasScreenshots;
- if (hasScreenshots == null)
- {
- return new List<string>();
- }
- return GetCacheTags(item, ImageType.Screenshot, limit).ToList();
- }
-
- private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
- {
- return item.GetImages(type)
- // Convert to a list now in case GetImageCacheTag is slow
- .ToList()
+ return images
.Select(p => GetImageCacheTag(item, p))
.Where(i => i != null)
- .Take(limit)
.ToList();
}
@@ -851,53 +833,6 @@ namespace MediaBrowser.Server.Implementations.Dto
}
/// <summary>
- /// If an item does not any backdrops, this can be used to find the first parent that does have one
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="owner">The owner.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
- {
- var parent = item.GetParent() ?? owner;
-
- while (parent != null)
- {
- if (parent.GetImages(ImageType.Backdrop).Any())
- {
- return parent;
- }
-
- parent = parent.GetParent();
- }
-
- return null;
- }
-
- /// <summary>
- /// If an item does not have a logo, this can be used to find the first parent that does have one
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="type">The type.</param>
- /// <param name="owner">The owner.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
- {
- var parent = item.GetParent() ?? owner;
-
- while (parent != null)
- {
- if (parent.HasImage(type))
- {
- return parent;
- }
-
- parent = parent.GetParent();
- }
-
- return null;
- }
-
- /// <summary>
/// Gets the chapter info dto.
/// </summary>
/// <param name="chapterInfo">The chapter info.</param>
@@ -917,7 +852,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
Path = chapterInfo.ImagePath,
Type = ImageType.Chapter,
- DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath)
+ DateModified = chapterInfo.ImageDateModified
});
}
@@ -958,6 +893,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.LockData = item.IsLocked;
dto.ForcedSortName = item.ForcedSortName;
}
+ dto.Container = item.Container;
var hasBudget = item as IHasBudget;
if (hasBudget != null)
@@ -1027,7 +963,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0)
{
- dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit);
+ dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
}
if (fields.Contains(ItemFields.ScreenshotImageTags))
@@ -1035,7 +971,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
if (screenshotLimit > 0)
{
- dto.ScreenshotImageTags = GetScreenshotImageTags(item, screenshotLimit);
+ dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
}
}
@@ -1064,6 +1000,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Id = GetDtoId(item);
dto.IndexNumber = item.IndexNumber;
+ dto.ParentIndexNumber = item.ParentIndexNumber;
dto.IsFolder = item.IsFolder;
dto.MediaType = item.MediaType;
dto.LocationType = item.LocationType;
@@ -1076,15 +1013,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
- var hasCriticRating = item as IHasCriticRating;
- if (hasCriticRating != null)
- {
- dto.CriticRating = hasCriticRating.CriticRating;
+ dto.CriticRating = item.CriticRating;
- if (fields.Contains(ItemFields.CriticRatingSummary))
- {
- dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary;
- }
+ if (fields.Contains(ItemFields.CriticRatingSummary))
+ {
+ dto.CriticRatingSummary = item.CriticRatingSummary;
}
var hasTrailers = item as IHasTrailers;
@@ -1127,23 +1060,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.ShortOverview))
{
- var hasShortOverview = item as IHasShortOverview;
- if (hasShortOverview != null)
- {
- dto.ShortOverview = hasShortOverview.ShortOverview;
- }
- }
-
- // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
- if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
- {
- var parentWithBackdrop = GetParentBackdropItem(item, owner);
-
- if (parentWithBackdrop != null)
- {
- dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
- dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
- }
+ dto.ShortOverview = item.ShortOverview;
}
if (fields.Contains(ItemFields.ParentId))
@@ -1155,46 +1072,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
- dto.ParentIndexNumber = item.ParentIndexNumber;
-
- // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
- if (!dto.HasLogo && options.GetImageLimit(ImageType.Logo) > 0)
- {
- var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
-
- if (parentWithLogo != null)
- {
- dto.ParentLogoItemId = GetDtoId(parentWithLogo);
-
- dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
- }
- }
-
- // If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
- if (!dto.HasArtImage && options.GetImageLimit(ImageType.Art) > 0)
- {
- var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
-
- if (parentWithImage != null)
- {
- dto.ParentArtItemId = GetDtoId(parentWithImage);
-
- dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
- }
- }
-
- // If there is no thumb, indicate what parent has one in case the Ui wants to allow inheritance
- if (!dto.HasThumb && options.GetImageLimit(ImageType.Thumb) > 0)
- {
- var parentWithImage = GetParentImageItem(item, ImageType.Thumb, owner);
-
- if (parentWithImage != null)
- {
- dto.ParentThumbItemId = GetDtoId(parentWithImage);
-
- dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
- }
- }
+ AddInheritedImages(dto, item, options, owner);
if (fields.Contains(ItemFields.Path))
{
@@ -1426,42 +1304,38 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.SeasonId = seasonId.Value.ToString("N");
}
- var episodeSeason = episode.Season;
- if (episodeSeason != null)
+ dto.SeasonName = episode.SeasonName;
+
+ var seriesId = episode.SeriesId;
+ if (seriesId.HasValue)
{
- if (fields.Contains(ItemFields.SeasonName))
- {
- dto.SeasonName = episodeSeason.Name;
- }
+ dto.SeriesId = seriesId.Value.ToString("N");
}
- var episodeSeries = episode.Series;
+ Series episodeSeries = null;
- if (episodeSeries != null)
+ if (fields.Contains(ItemFields.SeriesGenres))
{
- if (fields.Contains(ItemFields.SeriesGenres))
+ episodeSeries = episodeSeries ?? episode.Series;
+ if (episodeSeries != null)
{
dto.SeriesGenres = episodeSeries.Genres.ToList();
}
+ }
- dto.SeriesId = GetDtoId(episodeSeries);
-
- if (fields.Contains(ItemFields.AirTime))
- {
- dto.AirTime = episodeSeries.AirTime;
- }
-
- if (options.GetImageLimit(ImageType.Thumb) > 0)
- {
- dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
- }
-
- if (options.GetImageLimit(ImageType.Primary) > 0)
+ //if (fields.Contains(ItemFields.SeriesPrimaryImage))
+ {
+ episodeSeries = episodeSeries ?? episode.Series;
+ if (episodeSeries != null)
{
dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
}
+ }
- if (fields.Contains(ItemFields.SeriesStudio))
+ if (fields.Contains(ItemFields.SeriesStudio))
+ {
+ episodeSeries = episodeSeries ?? episode.Series;
+ if (episodeSeries != null)
{
dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
}
@@ -1483,16 +1357,29 @@ namespace MediaBrowser.Server.Implementations.Dto
var season = item as Season;
if (season != null)
{
- series = season.Series;
+ dto.SeriesName = season.SeriesName;
+
+ var seriesId = season.SeriesId;
+ if (seriesId.HasValue)
+ {
+ dto.SeriesId = seriesId.Value.ToString("N");
+ }
+
+ series = null;
- if (series != null)
+ if (fields.Contains(ItemFields.SeriesStudio))
{
- dto.SeriesId = GetDtoId(series);
- dto.SeriesName = series.Name;
- dto.AirTime = series.AirTime;
- dto.SeriesStudio = series.Studios.FirstOrDefault();
+ series = series ?? season.Series;
+ if (series != null)
+ {
+ dto.SeriesStudio = series.Studios.FirstOrDefault();
+ }
+ }
- if (options.GetImageLimit(ImageType.Primary) > 0)
+ if (fields.Contains(ItemFields.SeriesPrimaryImage))
+ {
+ series = series ?? season.Series;
+ if (series != null)
{
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
}
@@ -1543,6 +1430,77 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
+ private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner)
+ {
+ var logoLimit = options.GetImageLimit(ImageType.Logo);
+ var artLimit = options.GetImageLimit(ImageType.Art);
+ var thumbLimit = options.GetImageLimit(ImageType.Thumb);
+ var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
+
+ if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
+ {
+ return;
+ }
+
+ BaseItem parent = null;
+ var isFirst = true;
+
+ while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) &&
+ (parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null)
+ {
+ if (parent == null)
+ {
+ break;
+ }
+
+ var allImages = parent.ImageInfos;
+
+ if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null)
+ {
+ var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
+
+ if (image != null)
+ {
+ dto.ParentLogoItemId = GetDtoId(parent);
+ dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
+ }
+ }
+ if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null)
+ {
+ var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
+
+ if (image != null)
+ {
+ dto.ParentArtItemId = GetDtoId(parent);
+ dto.ParentArtImageTag = GetImageCacheTag(parent, image);
+ }
+ }
+ if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series))
+ {
+ var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
+
+ if (image != null)
+ {
+ dto.ParentThumbItemId = GetDtoId(parent);
+ dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
+ }
+ }
+ if (backdropLimit > 0 && !dto.HasBackdrop)
+ {
+ var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
+
+ if (images.Count > 0)
+ {
+ dto.ParentBackdropItemId = GetDtoId(parent);
+ dto.ParentBackdropImageTags = GetImageTags(parent, images);
+ }
+ }
+
+ isFirst = false;
+ parent = parent.GetParent();
+ }
+ }
+
private string GetMappedPath(IHasMetadata item)
{
var path = item.Path;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs
new file mode 100644
index 000000000..5aa01c706
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs
@@ -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);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index f091f0f1f..17e4793cb 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -335,7 +335,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="httpReq">The HTTP req.</param>
/// <param name="url">The URL.</param>
/// <returns>Task.</returns>
- protected Task RequestHandler(IHttpRequest httpReq, Uri url)
+ protected async Task RequestHandler(IHttpRequest httpReq, Uri url)
{
var date = DateTime.Now;
@@ -345,7 +345,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
httpRes.StatusCode = 503;
httpRes.Close();
- return Task.FromResult(true);
+ return ;
}
var operationName = httpReq.OperationName;
@@ -365,13 +365,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
@@ -389,35 +389,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>");
httpRes.Close();
- return Task.FromResult(true);
+ return;
}
}
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("../" + DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.IsNullOrEmpty(localPath))
{
httpRes.RedirectToUrl("/" + DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("web/pin.html");
- return Task.FromResult(true);
+ return;
}
if (!string.IsNullOrWhiteSpace(GlobalResponse))
@@ -427,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpRes.Write(GlobalResponse);
httpRes.Close();
- return Task.FromResult(true);
+ return;
}
var handler = HttpHandlerFactory.GetHandler(httpReq);
@@ -443,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
}
- var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName);
-
- task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
- //Matches Exceptions handled in HttpListenerBase.InitTask()
-
- task.ContinueWith(x =>
+ try
+ {
+ await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false);
+ }
+ finally
{
+ httpRes.Close();
var statusCode = httpRes.StatusCode;
var duration = DateTime.Now - date;
@@ -458,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
}
-
- }, TaskContinuationOptions.None);
- return task;
+ }
}
- return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo)
- .AsTaskException();
+ throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index 1d4829260..c0a2a5eb3 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
options.ContentType = MimeTypes.GetMimeType(path);
}
- options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
+ if (!options.DateLastModified.HasValue)
+ {
+ options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
+ }
+
var cacheKey = path + options.DateLastModified.Value.Ticks;
options.CacheKey = cacheKey.GetMD5();
@@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
throw error;
}
+
+ public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
+ {
+ return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
deleted file mode 100644
index 31c0e87b3..000000000
--- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
+++ /dev/null
@@ -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;
- }
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
index fb4397462..7ac92408b 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
+using System.Threading.Tasks;
+using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
{
- public class RangeRequestWriter : IStreamWriter, IHttpResult
+ public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult
{
/// <summary>
/// Gets or sets the source stream.
@@ -169,16 +171,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
- WriteToInternal(responseStream);
- }
-
- /// <summary>
- /// Writes to async.
- /// </summary>
- /// <param name="responseStream">The response stream.</param>
- /// <returns>Task.</returns>
- private void WriteToInternal(Stream responseStream)
- {
try
{
// Headers only
@@ -237,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
+ public async Task WriteToAsync(Stream responseStream)
+ {
+ try
+ {
+ // Headers only
+ if (IsHeadRequest)
+ {
+ return;
+ }
+
+ using (var source = SourceStream)
+ {
+ // If the requested range is "0-", we can optimize by just doing a stream copy
+ if (RangeEnd >= TotalContentLength - 1)
+ {
+ await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
+ }
+ else
+ {
+ await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in range request writer", ex);
+ throw;
+ }
+ finally
+ {
+ if (OnComplete != null)
+ {
+ OnComplete();
+ }
+ }
+ }
+
+ private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
+ {
+ var array = new byte[BufferSize];
+ int count;
+ while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
+ {
+ var bytesToCopy = Math.Min(count, copyLength);
+
+ await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
+
+ copyLength -= bytesToCopy;
+
+ if (copyLength <= 0)
+ {
+ break;
+ }
+ }
+ }
+
public string ContentType { get; set; }
public IRequest RequestContext { get; set; }
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
index efa850922..bfa65ac6b 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
@@ -3,6 +3,7 @@ using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
+using System.Threading.Tasks;
using System.Web;
using ServiceStack;
using ServiceStack.Web;
@@ -32,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return header.Substring(ap + 1, end - ap - 1);
}
- void LoadMultiPart()
+ async Task LoadMultiPart()
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
return;
- var input = GetSubStream(InputStream);
+ 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);
+ await requestStream.CopyToAsync(ms).ConfigureAwait(false);
- //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');
+ var input = ms;
+ ms.WriteByte((byte)'\r');
+ ms.WriteByte((byte)'\n');
- input.Position = 0;
+ input.Position = 0;
- //Uncomment to debug
- //var content = new StreamReader(ms).ReadToEnd();
- //Console.WriteLine(boundary + "::" + content);
- //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);
+ var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
- HttpMultipart.Element e;
- while ((e = multi_part.ReadNextElement()) != null)
- {
- if (e.Filename == null)
+ HttpMultipart.Element e;
+ while ((e = multi_part.ReadNextElement()) != null)
{
- byte[] copy = new byte[e.Length];
+ if (e.Filename == null)
+ {
+ byte[] copy = new byte[e.Length];
- input.Position = e.Start;
- input.Read(copy, 0, (int)e.Length);
+ input.Position = e.Start;
+ input.Read(copy, 0, (int)e.Length);
- form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
- }
- else
- {
- //
- // We use a substream, as in 2.x we will support large uploads streamed to disk,
- //
- HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
- files.AddFile(e.Name, sub);
+ form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
+ }
+ else
+ {
+ //
+ // We use a substream, as in 2.x we will support large uploads streamed to disk,
+ //
+ HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
+ files.AddFile(e.Name, sub);
+ }
}
}
- EndSubStream(input);
}
public NameValueCollection Form
@@ -91,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
files = new HttpFileCollection();
if (IsContentType("multipart/form-data", true))
- LoadMultiPart();
- else if (
- IsContentType("application/x-www-form-urlencoded", true))
- LoadWwwForm();
+ {
+ var task = LoadMultiPart();
+ Task.WaitAll(task);
+ }
+ else if (IsContentType("application/x-www-form-urlencoded", true))
+ {
+ var task = LoadWwwForm();
+ Task.WaitAll(task);
+ }
form.Protect();
}
@@ -220,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
}
-
-
-
-
- void LoadWwwForm()
+ async Task LoadWwwForm()
{
using (Stream input = GetSubStream(InputStream))
{
- using (StreamReader s = new StreamReader(input, ContentEncoding))
+ using (var ms = new MemoryStream())
{
- StringBuilder key = new StringBuilder();
- StringBuilder value = new StringBuilder();
- int c;
+ await input.CopyToAsync(ms).ConfigureAwait(false);
+ ms.Position = 0;
- while ((c = s.Read()) != -1)
+ using (StreamReader s = new StreamReader(ms, ContentEncoding))
{
- if (c == '=')
+ StringBuilder key = new StringBuilder();
+ StringBuilder value = new StringBuilder();
+ int c;
+
+ while ((c = s.Read()) != -1)
{
- value.Length = 0;
- while ((c = s.Read()) != -1)
+ if (c == '=')
{
- if (c == '&')
+ value.Length = 0;
+ while ((c = s.Read()) != -1)
+ {
+ if (c == '&')
+ {
+ AddRawKeyValue(key, value);
+ break;
+ }
+ else
+ value.Append((char)c);
+ }
+ if (c == -1)
{
AddRawKeyValue(key, value);
- break;
+ return;
}
- else
- value.Append((char)c);
}
- if (c == -1)
- {
+ else if (c == '&')
AddRawKeyValue(key, value);
- return;
- }
+ else
+ key.Append((char)c);
}
- else if (c == '&')
+ if (c == -1)
AddRawKeyValue(key, value);
- else
- key.Append((char)c);
}
- if (c == -1)
- AddRawKeyValue(key, value);
-
- EndSubStream(input);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
index c7d889505..dc2aec3e1 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
@@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
get
{
return remoteIp ??
- (remoteIp = XForwardedFor ??
- (NormalizeIp(XRealIp) ??
+ (remoteIp = (CheckBadChars(XForwardedFor)) ??
+ (NormalizeIp(CheckBadChars(XRealIp)) ??
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
}
}
+ private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
+
+ //
+ // CheckBadChars - throws on invalid chars to be not found in header name/value
+ //
+ internal static string CheckBadChars(string name)
+ {
+ if (name == null || name.Length == 0)
+ {
+ return name;
+ }
+
+ // VALUE check
+ //Trim spaces from both ends
+ name = name.Trim(HttpTrimCharacters);
+
+ //First, check for correctly formed multi-line value
+ //Second, check for absenece of CTL characters
+ int crlf = 0;
+ for (int i = 0; i < name.Length; ++i)
+ {
+ char c = (char)(0x000000ff & (uint)name[i]);
+ switch (crlf)
+ {
+ case 0:
+ if (c == '\r')
+ {
+ crlf = 1;
+ }
+ else if (c == '\n')
+ {
+ // Technically this is bad HTTP. But it would be a breaking change to throw here.
+ // Is there an exploit?
+ crlf = 2;
+ }
+ else if (c == 127 || (c < ' ' && c != '\t'))
+ {
+ throw new ArgumentException("net_WebHeaderInvalidControlChars");
+ }
+ break;
+
+ case 1:
+ if (c == '\n')
+ {
+ crlf = 2;
+ break;
+ }
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+
+ case 2:
+ if (c == ' ' || c == '\t')
+ {
+ crlf = 0;
+ break;
+ }
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ }
+ }
+ if (crlf != 0)
+ {
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ }
+ return name;
+ }
+
+ internal static bool ContainsNonAsciiChars(string token)
+ {
+ for (int i = 0; i < token.Length; ++i)
+ {
+ if ((token[i] < 0x20) || (token[i] > 0x7e))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
private string NormalizeIp(string ip)
{
if (!string.IsNullOrWhiteSpace(ip))
@@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return stream;
}
- static void EndSubStream(Stream stream)
- {
- }
-
public static string GetHandlerPathIfAny(string listenerUrl)
{
if (listenerUrl == null) return null;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
index a756f4aa8..f5906f6b7 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
@@ -4,13 +4,15 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Threading.Tasks;
+using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
{
/// <summary>
/// Class StreamWriter
/// </summary>
- public class StreamWriter : IStreamWriter, IHasOptions
+ public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
{
private ILogger Logger { get; set; }
@@ -73,30 +75,49 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
}
+ // 256k
+ private const int BufferSize = 262144;
+
/// <summary>
/// Writes to.
/// </summary>
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
- WriteToInternal(responseStream);
+ try
+ {
+ using (var src = SourceStream)
+ {
+ src.CopyTo(responseStream, BufferSize);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error streaming data", ex);
+
+ if (OnError != null)
+ {
+ OnError();
+ }
+
+ throw;
+ }
+ finally
+ {
+ if (OnComplete != null)
+ {
+ OnComplete();
+ }
+ }
}
- // 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)
+ public async Task WriteToAsync(Stream responseStream)
{
try
{
using (var src = SourceStream)
{
- src.CopyTo(responseStream, BufferSize);
+ await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -107,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
OnError();
}
-
+
throw;
}
finally
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index 0690d62dd..99cb80cb2 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -84,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.IO
// This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
// Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
- await Task.Delay(25000).ConfigureAwait(false);
+ await Task.Delay(45000).ConfigureAwait(false);
string val;
_tempIgnoredPaths.TryRemove(path, out val);
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 9d66455e7..712ea4ef3 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -33,6 +33,7 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Channels;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Extensions;
@@ -353,10 +354,6 @@ namespace MediaBrowser.Server.Implementations.Library
private void RegisterItem(Guid id, BaseItem item)
{
- if (item.SourceType != SourceType.Library)
- {
- return;
- }
if (item is IItemByName)
{
if (!(item is MusicArtist))
@@ -364,14 +361,25 @@ namespace MediaBrowser.Server.Implementations.Library
return;
}
}
- if (item is Photo)
+
+ if (item.IsFolder)
{
- return;
+ if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel))
+ {
+ if (item.SourceType != SourceType.Library)
+ {
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (item is Photo)
+ {
+ return;
+ }
}
- //if (!(item is Folder))
- //{
- // return;
- //}
+
LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
}
@@ -782,19 +790,19 @@ namespace MediaBrowser.Server.Implementations.Library
public BaseItem FindByPath(string path, bool? isFolder)
{
+ // If this returns multiple items it could be tricky figuring out which one is correct.
+ // In most cases, the newest one will be and the others obsolete but not yet cleaned up
+
var query = new InternalItemsQuery
{
Path = path,
- IsFolder = isFolder
+ IsFolder = isFolder,
+ SortBy = new[] { ItemSortBy.DateCreated },
+ SortOrder = SortOrder.Descending,
+ Limit = 1
};
- // If this returns multiple items it could be tricky figuring out which one is correct.
- // In most cases, the newest one will be and the others obsolete but not yet cleaned up
-
- return GetItemIds(query)
- .Select(GetItemById)
- .Where(i => i != null)
- .OrderByDescending(i => i.DateCreated)
+ return GetItemList(query)
.FirstOrDefault();
}
@@ -1258,6 +1266,8 @@ namespace MediaBrowser.Server.Implementations.Library
item = RetrieveItem(id);
+ //_logger.Debug("GetitemById {0}", id);
+
if (item != null)
{
RegisterItem(item);
@@ -1508,7 +1518,7 @@ namespace MediaBrowser.Server.Implementations.Library
UserId = user.Id.ToString("N")
}, CancellationToken.None).Result;
-
+
return channelResult.Items;
}
@@ -1921,7 +1931,7 @@ namespace MediaBrowser.Server.Implementations.Library
private string GetContentTypeOverride(string path, bool inherit)
{
- var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && _fileSystem.ContainsSubPath(i.Name, path)));
+ var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
if (nameValuePair != null)
{
return nameValuePair.Value;
@@ -2802,6 +2812,11 @@ namespace MediaBrowser.Server.Implementations.Library
private void RemoveContentTypeOverrides(string path)
{
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
var removeList = new List<NameValuePair>();
foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index 8beb03b71..9dd30edde 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities;
using System;
using System.IO;
using System.Linq;
+using CommonIO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
public class PhotoResolver : ItemResolver<Photo>
{
private readonly IImageProcessor _imageProcessor;
- public PhotoResolver(IImageProcessor imageProcessor)
+ private readonly ILibraryManager _libraryManager;
+
+ public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
+ _libraryManager = libraryManager;
}
/// <summary>
@@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
/// <returns>Trailer.</returns>
protected override Photo Resolve(ItemResolveArgs args)
{
- // Must be an image file within a photo collection
- if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
- !args.IsDirectory &&
- IsImageFile(args.Path, _imageProcessor))
+ if (!args.IsDirectory)
{
- return new Photo
+ // Must be an image file within a photo collection
+ var collectionType = args.GetCollectionType();
+
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
- Path = args.Path
- };
+ if (IsImageFile(args.Path, _imageProcessor))
+ {
+ var filename = Path.GetFileNameWithoutExtension(args.Path);
+
+ // Make sure the image doesn't belong to a video file
+ if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename)))
+ {
+ return null;
+ }
+
+ return new Photo
+ {
+ Path = args.Path
+ };
+ }
+ }
}
return null;
}
+ private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename)
+ {
+ if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
private static readonly string[] IgnoreFiles =
{
"folder",
@@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
"landscape",
"fanart",
"backdrop",
- "poster"
+ "poster",
+ "cover"
};
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 14e5e446b..7b8832c59 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -31,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
}
var season = parent as Season;
-
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
if (season == null)
@@ -41,10 +40,30 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders
- if (season != null || args.HasParent<Series>() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+ if (season != null ||
+ string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
+ args.HasParent<Series>())
{
var episode = ResolveVideo<Episode>(args, false);
+ if (episode != null)
+ {
+ var series = parent as Series;
+ if (series == null)
+ {
+ series = parent.GetParents().OfType<Series>().FirstOrDefault();
+ }
+
+ if (series != null)
+ {
+ episode.SeriesId = series.Id;
+ }
+ if (season != null)
+ {
+ episode.SeasonId = season.Id;
+ }
+ }
+
return episode;
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 7d13b11ad..eeac1345e 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
if (args.Parent is Series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
-
+ var series = ((Series)args.Parent);
+
var season = new Season
{
- IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber
+ IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
+ SeriesId = series.Id
};
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index f32a4b59e..64af35a9a 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -1214,8 +1214,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
list.Add(item);
-
- _libraryManager.RegisterItem(item);
}
catch (OperationCanceledException)
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index a3e5589e8..69b6fb5a9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -17,6 +17,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Net;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -106,18 +107,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
{
- using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ try
{
- Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
- CancellationToken = cancellationToken,
- CacheLength = TimeSpan.FromDays(1),
- CacheMode = CacheMode.Unconditional,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
- }))
+ using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ {
+ Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
+ CancellationToken = cancellationToken,
+ CacheLength = TimeSpan.FromDays(1),
+ CacheMode = CacheMode.Unconditional,
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
+ }))
+ {
+ var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+
+ return response.ModelNumber;
+ }
+ }
+ catch (HttpException ex)
{
- var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
+ {
+ // HDHR4 doesn't have this api
+ return "HDHR";
+ }
- return response.ModelNumber;
+ throw;
}
}
@@ -455,16 +469,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return;
}
- // Test it by pulling down the lineup
- using (var stream = await _httpClient.Get(new HttpRequestOptions
+ try
{
- Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
- CancellationToken = CancellationToken.None
- }))
+ // Test it by pulling down the lineup
+ using (var stream = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
+ CancellationToken = CancellationToken.None
+ }))
+ {
+ var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+
+ info.DeviceId = response.DeviceID;
+ }
+ }
+ catch (HttpException ex)
{
- var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
+ {
+ // HDHR4 doesn't have this api
+ return;
+ }
- info.DeviceId = response.DeviceID;
+ throw;
}
}
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index c78306911..0f91d5285 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -56,8 +56,8 @@
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference>
- <Reference Include="MediaBrowser.Naming, Version=1.0.6012.15754, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>..\packages\MediaBrowser.Naming.1.0.0.52\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MoreLinq">
@@ -73,8 +73,8 @@
<HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
- <Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>..\packages\SocketHttpListener.1.0.0.30\lib\net45\SocketHttpListener.dll</HintPath>
+ <Reference Include="SocketHttpListener, Version=1.0.6046.26351, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -156,6 +156,7 @@
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
+ <Compile Include="HttpServer\AsyncStreamWriterFunc.cs" />
<Compile Include="HttpServer\IHttpListener.cs" />
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
<Compile Include="HttpServer\ContainerAdapter.cs" />
@@ -757,9 +758,7 @@
<EmbeddedResource Include="Localization\iso6392.txt" />
<EmbeddedResource Include="Localization\Ratings\be.txt" />
</ItemGroup>
- <ItemGroup>
- <Folder Include="HttpServer\NetListener\" />
- </ItemGroup>
+ <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
index b1b2072c4..7f709d084 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -152,6 +152,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
chapter.ImagePath = path;
+ chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true;
}
catch (Exception ex)
@@ -170,6 +171,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
{
chapter.ImagePath = path;
+ chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true;
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
index b11a3e496..bf2afb5ac 100644
--- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
@@ -155,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_logger.Debug("Upgrading schema for {0} items", numItems);
+ var list = new List<BaseItem>();
+
foreach (var itemId in itemIds)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -166,19 +168,26 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (item != null)
{
- try
- {
- await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving item", ex);
- }
+ list.Add(item);
+ }
+ }
+
+ if (list.Count >= 1000)
+ {
+ try
+ {
+ await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
}
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error saving item", ex);
+ }
+
+ list.Clear();
}
numComplete++;
@@ -187,6 +196,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
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);
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index 93737b9b8..a33e4f3e4 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _updateInheritedRatingCommand;
private IDbCommand _updateInheritedTagsCommand;
- public const int LatestSchemaVersion = 97;
+ public const int LatestSchemaVersion = 108;
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@@ -269,10 +269,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
_connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeasonName", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text");
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
_connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
+ _connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
+
string[] postQueries =
{
@@ -403,7 +409,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Album",
"CriticRating",
"CriticRatingSummary",
- "IsVirtualItem"
+ "IsVirtualItem",
+ "SeriesName",
+ "SeasonName",
+ "SeasonId",
+ "SeriesId",
+ "SeriesSortName"
};
private readonly string[] _mediaStreamSaveColumns =
@@ -523,7 +534,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Album",
"IsVirtualItem",
"SeriesName",
- "UserDataKey"
+ "UserDataKey",
+ "SeasonName",
+ "SeasonId",
+ "SeriesId",
+ "SeriesSortName"
};
_saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -582,6 +597,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
+ _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
// MediaStreams
_deleteStreamsCommand = _connection.CreateCommand();
@@ -945,7 +961,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
var hasSeries = item as IHasSeries;
if (hasSeries != null)
{
- _saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName;
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
}
else
{
@@ -954,6 +970,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
+ var episode = item as Episode;
+ if (episode != null)
+ {
+ _saveItemCommand.GetParameter(index++).Value = episode.FindSeasonName();
+ _saveItemCommand.GetParameter(index++).Value = episode.FindSeasonId();
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ if (hasSeries != null)
+ {
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId();
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesSortName();
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
_saveItemCommand.Transaction = transaction;
_saveItemCommand.ExecuteNonQuery();
@@ -1376,6 +1415,44 @@ namespace MediaBrowser.Server.Implementations.Persistence
item.IsVirtualItem = reader.GetBoolean(58);
}
+ var hasSeries = item as IHasSeries;
+ if (hasSeries != null)
+ {
+ if (!reader.IsDBNull(59))
+ {
+ hasSeries.SeriesName = reader.GetString(59);
+ }
+ }
+
+ var episode = item as Episode;
+ if (episode != null)
+ {
+ if (!reader.IsDBNull(60))
+ {
+ episode.SeasonName = reader.GetString(60);
+ }
+ if (!reader.IsDBNull(61))
+ {
+ episode.SeasonId = reader.GetGuid(61);
+ }
+ }
+
+ if (hasSeries != null)
+ {
+ if (!reader.IsDBNull(62))
+ {
+ hasSeries.SeriesId = reader.GetGuid(62);
+ }
+ }
+
+ if (hasSeries != null)
+ {
+ if (!reader.IsDBNull(63))
+ {
+ hasSeries.SeriesSortName = reader.GetString(63);
+ }
+ }
+
return item;
}
@@ -1437,7 +1514,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
+ cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
@@ -1470,7 +1547,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
+ cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
@@ -1508,6 +1585,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
chapter.ImagePath = reader.GetString(2);
}
+ if (!reader.IsDBNull(3))
+ {
+ chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
+ }
+
return chapter;
}
@@ -1567,6 +1649,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
_saveChapterCommand.GetParameter(3).Value = chapter.Name;
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
+ _saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
_saveChapterCommand.Transaction = transaction;
@@ -2061,7 +2144,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
if (query.User != null)
{
- query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random };
+ query.SortBy = new[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
}
else
{
@@ -2986,6 +3069,39 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("LocationType<>'Virtual'");
}
}
+ if (query.IsUnaired.HasValue)
+ {
+ if (query.IsUnaired.Value)
+ {
+ whereClauses.Add("PremiereDate >= DATETIME('now')");
+ }
+ else
+ {
+ whereClauses.Add("PremiereDate < DATETIME('now')");
+ }
+ }
+ if (query.IsMissing.HasValue && _config.Configuration.SchemaVersion >= 90)
+ {
+ if (query.IsMissing.Value)
+ {
+ whereClauses.Add("(IsVirtualItem=1 AND PremiereDate < DATETIME('now'))");
+ }
+ else
+ {
+ whereClauses.Add("(IsVirtualItem=0 OR PremiereDate >= DATETIME('now'))");
+ }
+ }
+ if (query.IsVirtualUnaired.HasValue && _config.Configuration.SchemaVersion >= 90)
+ {
+ if (query.IsVirtualUnaired.Value)
+ {
+ whereClauses.Add("(IsVirtualItem=1 AND PremiereDate >= DATETIME('now'))");
+ }
+ else
+ {
+ whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
+ }
+ }
if (query.MediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
@@ -4063,6 +4179,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("values");
}
+ // Just in case there might be case-insensitive duplicates, strip them out now
+ var newValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ foreach (var pair in values)
+ {
+ newValues[pair.Key] = pair.Value;
+ }
+
CheckDisposed();
// First delete
@@ -4071,7 +4194,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteProviderIdsCommand.ExecuteNonQuery();
- foreach (var pair in values)
+ foreach (var pair in newValues)
{
_saveProviderIdsCommand.GetParameter(0).Value = itemId;
_saveProviderIdsCommand.GetParameter(1).Value = pair.Key;
diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
index 53c03b91c..ba1559bd0 100644
--- a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
@@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
return;
}
- if (newIndex > oldIndex)
- {
- newIndex--;
- }
-
var item = playlist.LinkedChildren[oldIndex];
playlist.LinkedChildren.Remove(item);
- playlist.LinkedChildren.Insert(newIndex, item);
+
+ if (newIndex >= playlist.LinkedChildren.Count)
+ {
+ playlist.LinkedChildren.Add(item);
+ }
+ else
+ {
+ playlist.LinkedChildren.Insert(newIndex, item);
+ }
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
index 4efc3218b..6bc1264a4 100644
--- a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
@@ -21,28 +20,9 @@ namespace MediaBrowser.Server.Implementations.Sorting
private string GetValue(BaseItem item)
{
- Series series = null;
+ var hasSeries = item as IHasSeries;
- var season = item as Season;
-
- if (season != null)
- {
- series = season.Series;
- }
-
- var episode = item as Episode;
-
- if (episode != null)
- {
- series = episode.Series;
- }
-
- if (series == null)
- {
- series = item as Series;
- }
-
- return series != null ? series.SortName : null;
+ return hasSeries != null ? hasSeries.SeriesSortName : null;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
index d51f61b9a..c2a4339f0 100644
--- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
+++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
@@ -50,6 +50,11 @@ namespace MediaBrowser.Server.Implementations.TV
}
}
+ if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+ {
+ limit = limit.Value + 10;
+ }
+
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Series).Name },
@@ -89,6 +94,11 @@ namespace MediaBrowser.Server.Implementations.TV
}
}
+ if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+ {
+ limit = limit.Value + 10;
+ }
+
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Series).Name },
@@ -115,7 +125,8 @@ namespace MediaBrowser.Server.Implementations.TV
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
.OrderByDescending(i => i.Item2)
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
- .Select(i => i.Item1);
+ .Select(i => i.Item1)
+ .Take(request.Limit ?? int.MaxValue);
}
private string GetUniqueSeriesKey(BaseItem series)
@@ -143,7 +154,6 @@ namespace MediaBrowser.Server.Implementations.TV
SortOrder = SortOrder.Descending,
IsPlayed = true,
Limit = 1,
- IsVirtualItem = false,
ParentIndexNumberNotEquals = 0
}).FirstOrDefault();
diff --git a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
index a66884f89..29716d33e 100644
--- a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
@@ -54,11 +54,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
{
return series;
}
- var episodeSeason = episode.Season;
- if (episodeSeason != null)
- {
- return episodeSeason;
- }
return episode;
}
diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
index 161f771a9..ea4da19b2 100644
--- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
{
return series;
}
- var episodeSeason = episode.Season;
- if (episodeSeason != null)
- {
- return episodeSeason;
- }
return episode;
}
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 9592ecb16..9ae0a126a 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -4,10 +4,10 @@
<package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
- <package id="MediaBrowser.Naming" version="1.0.0.52" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.53" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
- <package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
+ <package id="SocketHttpListener" version="1.0.0.35" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index bcd2ed8bd..b7ea5bdad 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -274,7 +274,7 @@ namespace MediaBrowser.Server.Startup.Common
{
get
{
- return "Media Browser Server";
+ return "Emby Server";
}
}
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index fb4fb86ff..bdfd7d1bb 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -12,6 +12,7 @@ using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Threading;
@@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication
if (IsAlreadyRunning(applicationPath, currentProcess))
{
- logger.Info("Shutting down because another instance of Media Browser Server is already running.");
+ logger.Info("Shutting down because another instance of Emby Server is already running.");
return;
}
@@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
{
- var filename = Path.GetFileName(applicationPath);
-
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
{
try
{
- return string.Equals(filename, Path.GetFileName(i.MainModule.FileName)) && currentProcess.Id != i.Id;
+ if (currentProcess.Id == i.Id)
+ {
+ return false;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ try
+ {
+ //_logger.Info("Module: {0}", i.MainModule.FileName);
+ if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ return false;
}
catch (Exception)
{
@@ -155,6 +171,41 @@ namespace MediaBrowser.ServerApplication
}
}
+ if (!_isRunningAsService)
+ {
+ return IsAlreadyRunningAsService(applicationPath);
+ }
+
+ return false;
+ }
+
+ private static bool IsAlreadyRunningAsService(string applicationPath)
+ {
+ var serviceName = BackgroundService.GetExistingServiceName();
+
+ WqlObjectQuery wqlObjectQuery = new WqlObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE State = 'Running' AND Name = '{0}'", serviceName));
+ ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(wqlObjectQuery);
+ ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
+
+ foreach (ManagementObject managementObject in managementObjectCollection)
+ {
+ var obj = managementObject.GetPropertyValue("PathName");
+ if (obj == null)
+ {
+ continue;
+ }
+ var path = obj.ToString();
+
+ _logger.Info("Service path: {0}", path);
+ // Need to use indexOf instead of equality because the path will have the full service command line
+ if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ _logger.Info("The windows service is already running");
+ MessageBox.Show("Emby Server is already running as a Windows Service. Only one instance is allowed at a time. To run as a tray icon, shut down the Windows Service.");
+ return true;
+ }
+ }
+
return false;
}
@@ -593,14 +644,32 @@ namespace MediaBrowser.ServerApplication
private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
{
+ // Reference
+ // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
+
try
{
- var version = ImageMagickEncoder.GetVersion();
- return;
+ var subkey = Environment.Is64BitProcess
+ ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
+ : "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
+
+ using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+ .OpenSubKey(subkey))
+ {
+ if (ndpKey != null && ndpKey.GetValue("Version") != null)
+ {
+ var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
+ if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
}
catch (Exception ex)
{
- logger.ErrorException("Error loading ImageMagick", ex);
+ logger.ErrorException("Error getting .NET Framework version", ex);
+ return;
}
try
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index fcd6f7faf..b01d8c43f 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -88,6 +88,7 @@
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
+ <Reference Include="System.Management" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 1a227126e..34ad32111 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -349,21 +349,16 @@ namespace MediaBrowser.WebDashboard.Api
}
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
+ //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
+ //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
- DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
- DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
- DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
-
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true);
+ //DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
+ //DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
+ //DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 9c786dbae..723c4f936 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -104,6 +104,9 @@
<Content Include="dashboard-ui\components\apphost.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\components\categorysyncbuttons.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -902,9 +905,6 @@
<Content Include="dashboard-ui\scripts\favorites.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\librarylist.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\librarymenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1043,42 +1043,6 @@
<Content Include="dashboard-ui\userpassword.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\voice\commands\controlcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\disablecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\enablecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\playcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\searchcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\showcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\togglecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\grammarprocessor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voicedialog.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voice.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voice.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voicecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\wizardagreement.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1674,15 +1638,6 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <None Include="dashboard-ui\voice\grammar\en-US.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\voice\grammar\grammar.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\voice\Readme.md">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />