diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-09-20 13:22:39 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-09-20 13:22:39 -0400 |
| commit | eb2a1330045d802bfe0366df7105c220a36f111f (patch) | |
| tree | 2c1638c424ee9c0837c5de6d6e08a2398da69cdb | |
| parent | ec426d5c92875639ceac64477ce10fab3e639335 (diff) | |
| parent | a015e1208885bc6a8788db683c4fe47e93dc26b7 (diff) | |
Merge pull request #2897 from MediaBrowser/beta
Beta
276 files changed, 3347 insertions, 5793 deletions
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index d547ad8d4..fbd709010 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -17,7 +17,7 @@ using MediaBrowser.Model.Xml; namespace Emby.Dlna.ContentDirectory { - public class ContentDirectory : BaseService, IContentDirectory, IDisposable + public class ContentDirectory : BaseService, IContentDirectory { private readonly ILibraryManager _libraryManager; private readonly IImageProcessor _imageProcessor; @@ -143,10 +143,5 @@ namespace Emby.Dlna.ContentDirectory return null; } - - public void Dispose() - { - - } } } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 205f4e890..47d199e6e 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -458,8 +458,7 @@ namespace Emby.Dlna.ContentDirectory { Limit = limit, StartIndex = startIndex, - SortBy = sortOrders.ToArray(sortOrders.Count), - SortOrder = sort.SortOrder, + OrderBy = sortOrders.Select(i => new Tuple<string, SortOrder>(i, sort.SortOrder)).ToArray(), User = user, Recursive = true, IsMissing = false, @@ -828,7 +827,7 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.OrderBy = new List<Tuple<string, SortOrder>> + query.OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder> (ItemSortBy.DatePlayed, SortOrder.Descending), new Tuple<string, SortOrder> (ItemSortBy.SortName, SortOrder.Ascending) @@ -1077,7 +1076,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.SortBy = new string[] { }; + query.OrderBy = new Tuple<string, SortOrder>[] { }; var items = _userViewManager.GetLatestItems(new LatestItemsQuery { @@ -1094,7 +1093,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query) { - query.SortBy = new string[] { }; + query.OrderBy = new Tuple<string, SortOrder>[] { }; var result = _tvSeriesManager.GetNextUp(new NextUpQuery { @@ -1109,7 +1108,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetTvLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.SortBy = new string[] { }; + query.OrderBy = new Tuple<string, SortOrder>[] { }; var items = _userViewManager.GetLatestItems(new LatestItemsQuery { @@ -1126,7 +1125,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.SortBy = new string[] { }; + query.OrderBy = new Tuple<string, SortOrder>[] { }; var items = _userViewManager.GetLatestItems(new LatestItemsQuery { @@ -1236,8 +1235,7 @@ namespace Emby.Dlna.ContentDirectory sortOrders.Add(ItemSortBy.SortName); } - query.SortBy = sortOrders.ToArray(sortOrders.Count); - query.SortOrder = sort.SortOrder; + query.OrderBy = sortOrders.Select(i => new Tuple<string, SortOrder>(i, sort.SortOrder)).ToArray(); } private QueryResult<ServerItem> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit) @@ -1246,7 +1244,7 @@ namespace Emby.Dlna.ContentDirectory { PersonIds = new[] { person.Id.ToString("N") }, IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name, typeof(Trailer).Name }, - SortBy = new[] { ItemSortBy.SortName }, + OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions() diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 847f63619..bdc523c8b 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -316,7 +316,6 @@ namespace Emby.Dlna profile = ReserializeProfile(tempProfile); profile.Id = path.ToLower().GetMD5().ToString("N"); - profile.ProfileType = type; _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); @@ -597,6 +596,7 @@ namespace Emby.Dlna public void Dispose() { + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index a6facab7d..3dd36a27b 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -394,6 +394,7 @@ namespace Emby.Dlna.Main _communicationsServer.Dispose(); _communicationsServer = null; } + GC.SuppressFinalize(this); } public void DisposeDlnaServer() diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 365354efd..4ed74a684 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.Xml; namespace Emby.Dlna.MediaReceiverRegistrar { - public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar, IDisposable + public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar { private readonly IServerConfigurationManager _config; protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; @@ -33,10 +33,5 @@ namespace Emby.Dlna.MediaReceiverRegistrar Logger, XmlReaderSettingsFactory) .ProcessControlRequest(request); } - - public void Dispose() - { - - } } } diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 213e7367f..b6b1c0c03 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -113,7 +113,7 @@ namespace Emby.Dlna.PlayTo private int GetInactiveTimerIntervalMs() { - return Timeout.Infinite; + return 60000; } public void Start() @@ -1105,6 +1105,7 @@ namespace Emby.Dlna.PlayTo _disposed = true; DisposeTimer(); + GC.SuppressFinalize(this); } } diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 4b77f998d..95b164212 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -48,23 +48,7 @@ namespace Emby.Dlna.PlayTo { get { - var lastDateKnownActivity = _creationTime > _device.DateLastActivity ? _creationTime : _device.DateLastActivity; - - if (DateTime.UtcNow >= lastDateKnownActivity.AddSeconds(120)) - { - try - { - // Session is inactive, mark it for Disposal and don't start the elapsed timer. - _sessionManager.ReportSessionEnded(_session.Id); - } - catch (Exception ex) - { - _logger.ErrorException("Error in ReportSessionEnded", ex); - } - return false; - } - - return _device != null; + return !_disposed && _device != null; } } @@ -386,6 +370,9 @@ namespace Emby.Dlna.PlayTo case PlaystateCommand.Unpause: return _device.SetPlay(); + case PlaystateCommand.PlayPause: + return _device.IsPaused ? _device.SetPlay() : _device.SetPause(); + case PlaystateCommand.Seek: { return Seek(command.SeekPositionTicks ?? 0); @@ -682,6 +669,7 @@ namespace Emby.Dlna.PlayTo _device.OnDeviceUnavailable = null; _device.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index e29ef78a8..dd30dfc3d 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -194,7 +194,10 @@ namespace Emby.Dlna.PlayTo GeneralCommandType.SetSubtitleStreamIndex.ToString() }, - SupportsMediaControl = true + SupportsMediaControl = true, + + // xbox one creates a new uuid everytime it restarts + SupportsPersistentIdentifier = (device.Properties.ModelName ?? string.Empty).IndexOf("xbox", StringComparison.OrdinalIgnoreCase) == -1 }); _logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName); @@ -225,6 +228,7 @@ namespace Emby.Dlna.PlayTo { _disposed = true; _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj b/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj index 98e99c92b..c4cef629a 100644 --- a/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj +++ b/Emby.Drawing.ImageMagick/Emby.Drawing.ImageMagick.csproj @@ -31,8 +31,7 @@ </PropertyGroup> <ItemGroup> <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\ImageMagickSharp.1.0.0.19\lib\net45\ImageMagickSharp.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> @@ -56,9 +55,6 @@ <EmbeddedResource Include="fonts\robotoregular.ttf" /> </ItemGroup> <ItemGroup> - <None Include="packages.config" /> - </ItemGroup> - <ItemGroup> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> <Name>MediaBrowser.Common</Name> @@ -72,6 +68,9 @@ <Name>MediaBrowser.Model</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </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/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs index 9abf85e70..2c40bf570 100644 --- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs +++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.System; namespace Emby.Drawing.ImageMagick { - public class ImageMagickEncoder : IImageEncoder + public class ImageMagickEncoder : IImageEncoder, IDisposable { private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; @@ -38,7 +38,8 @@ namespace Emby.Drawing.ImageMagick // Some common file name extensions for RAW picture files include: .cr2, .crw, .dng, .nef, .orf, .rw2, .pef, .arw, .sr2, .srf, and .tif. return new[] { - "tiff", + "tiff", + "tif", "jpeg", "jpg", "png", @@ -327,6 +328,7 @@ namespace Emby.Drawing.ImageMagick { _disposed = true; Wand.CloseEnvironment(); + GC.SuppressFinalize(this); } private void CheckDisposed() diff --git a/Emby.Drawing.ImageMagick/packages.config b/Emby.Drawing.ImageMagick/packages.config index 619310d28..aaf7ab7ba 100644 --- a/Emby.Drawing.ImageMagick/packages.config +++ b/Emby.Drawing.ImageMagick/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net452" /> + <package id="ImageMagickSharp" version="1.0.0.19" targetFramework="net452" /> </packages>
\ No newline at end of file diff --git a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj index a4ab19f83..f2b32d52c 100644 --- a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj +++ b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj @@ -64,7 +64,7 @@ </ItemGroup> <ItemGroup> <Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> - <HintPath>..\packages\SkiaSharp.1.58.0\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll</HintPath> + <HintPath>..\packages\SkiaSharp.1.58.1\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs index 77ab1919a..eb0602afe 100644 --- a/Emby.Drawing.Skia/SkiaEncoder.cs +++ b/Emby.Drawing.Skia/SkiaEncoder.cs @@ -48,7 +48,10 @@ namespace Emby.Drawing.Skia "astc", "ktx", "pkm", - "wbmp" + "wbmp", + + // working on windows at least + "cr2" }; } } @@ -76,7 +79,7 @@ namespace Emby.Drawing.Skia private static bool IsTransparent(SKColor color) { - + return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; } @@ -193,16 +196,31 @@ namespace Emby.Drawing.Skia { using (var stream = new SKFileStream(path)) { - var codec = SKCodec.Create(stream); + using (var codec = SKCodec.Create(stream)) + { + if (codec == null) + { + origin = SKCodecOrigin.TopLeft; + return null; + } - // create the bitmap - var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); - // decode - codec.GetPixels(bitmap.Info, bitmap.GetPixels()); + // create the bitmap + var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack); - origin = codec.Origin; + if (bitmap != null) + { + // decode + codec.GetPixels(bitmap.Info, bitmap.GetPixels()); - return bitmap; + origin = codec.Origin; + } + else + { + origin = SKCodecOrigin.TopLeft; + } + + return bitmap; + } } } @@ -239,7 +257,7 @@ namespace Emby.Drawing.Skia return Decode(path, forceAnalyzeBitmap, out origin); } - private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation) + private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient) { SKCodecOrigin origin; @@ -247,11 +265,14 @@ namespace Emby.Drawing.Skia { var bitmap = GetBitmap(path, cropWhitespace, true, out origin); - if (origin != SKCodecOrigin.TopLeft) + if (bitmap != null) { - using (bitmap) + if (origin != SKCodecOrigin.TopLeft) { - return RotateAndFlip(bitmap, origin); + using (bitmap) + { + return OrientImage(bitmap, origin); + } } } @@ -261,82 +282,153 @@ namespace Emby.Drawing.Skia return GetBitmap(path, cropWhitespace, false, out origin); } - private SKBitmap RotateAndFlip(SKBitmap original, SKCodecOrigin origin) + private SKBitmap OrientImage(SKBitmap bitmap, SKCodecOrigin origin) { - // these are the origins that represent a 90 degree turn in some fashion - var differentOrientations = new SKCodecOrigin[] + //var transformations = { + // 2: { rotate: 0, flip: true}, + // 3: { rotate: 180, flip: false}, + // 4: { rotate: 180, flip: true}, + // 5: { rotate: 90, flip: true}, + // 6: { rotate: 90, flip: false}, + // 7: { rotate: 270, flip: true}, + // 8: { rotate: 270, flip: false}, + //} + + switch (origin) { - SKCodecOrigin.LeftBottom, - SKCodecOrigin.LeftTop, - SKCodecOrigin.RightBottom, - SKCodecOrigin.RightTop - }; - // check if we need to turn the image - bool isDifferentOrientation = differentOrientations.Any(o => o == origin); + case SKCodecOrigin.TopRight: + { + var rotated = new SKBitmap(bitmap.Width, bitmap.Height); + using (var surface = new SKCanvas(rotated)) + { + surface.Translate(rotated.Width, 0); + surface.Scale(-1, 1); + surface.DrawBitmap(bitmap, 0, 0); + } - // define new width/height - var width = isDifferentOrientation ? original.Height : original.Width; - var height = isDifferentOrientation ? original.Width : original.Height; + return rotated; + } - var bitmap = new SKBitmap(width, height, true); + case SKCodecOrigin.BottomRight: + { + var rotated = new SKBitmap(bitmap.Width, bitmap.Height); + using (var surface = new SKCanvas(rotated)) + { + float px = bitmap.Width; + px /= 2; - // todo: the stuff in this switch statement should be rewritten to use pointers - switch (origin) - { - case SKCodecOrigin.LeftBottom: + float py = bitmap.Height; + py /= 2; - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(y, original.Width - 1 - x, original.GetPixel(x, y)); - break; + surface.RotateDegrees(180, px, py); + surface.DrawBitmap(bitmap, 0, 0); + } - case SKCodecOrigin.RightTop: + return rotated; + } - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(original.Height - 1 - y, x, original.GetPixel(x, y)); - break; + case SKCodecOrigin.BottomLeft: + { + var rotated = new SKBitmap(bitmap.Width, bitmap.Height); + using (var surface = new SKCanvas(rotated)) + { + float px = bitmap.Width; + px /= 2; - case SKCodecOrigin.RightBottom: + float py = bitmap.Height; + py /= 2; - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(original.Height - 1 - y, original.Width - 1 - x, original.GetPixel(x, y)); + surface.Translate(rotated.Width, 0); + surface.Scale(-1, 1); - break; + surface.RotateDegrees(180, px, py); + surface.DrawBitmap(bitmap, 0, 0); + } + + return rotated; + } case SKCodecOrigin.LeftTop: + { + // TODO: Remove dual canvases, had trouble with flipping + using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) + { + using (var surface = new SKCanvas(rotated)) + { + surface.Translate(rotated.Width, 0); - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(y, x, original.GetPixel(x, y)); - break; + surface.RotateDegrees(90); - case SKCodecOrigin.BottomLeft: + surface.DrawBitmap(bitmap, 0, 0); - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(x, original.Height - 1 - y, original.GetPixel(x, y)); - break; + } - case SKCodecOrigin.BottomRight: + var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height); + using (var flippedCanvas = new SKCanvas(flippedBitmap)) + { + flippedCanvas.Translate(flippedBitmap.Width, 0); + flippedCanvas.Scale(-1, 1); + flippedCanvas.DrawBitmap(rotated, 0, 0); + } - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(original.Width - 1 - x, original.Height - 1 - y, original.GetPixel(x, y)); - break; + return flippedBitmap; + } + } - case SKCodecOrigin.TopRight: + case SKCodecOrigin.RightTop: + { + var rotated = new SKBitmap(bitmap.Height, bitmap.Width); + using (var surface = new SKCanvas(rotated)) + { + surface.Translate(rotated.Width, 0); + surface.RotateDegrees(90); + surface.DrawBitmap(bitmap, 0, 0); + } - for (var x = 0; x < original.Width; x++) - for (var y = 0; y < original.Height; y++) - bitmap.SetPixel(original.Width - 1 - x, y, original.GetPixel(x, y)); - break; + return rotated; + } - } + case SKCodecOrigin.RightBottom: + { + // TODO: Remove dual canvases, had trouble with flipping + using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width)) + { + using (var surface = new SKCanvas(rotated)) + { + surface.Translate(0, rotated.Height); + surface.RotateDegrees(270); + surface.DrawBitmap(bitmap, 0, 0); + } + + var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height); + using (var flippedCanvas = new SKCanvas(flippedBitmap)) + { + flippedCanvas.Translate(flippedBitmap.Width, 0); + flippedCanvas.Scale(-1, 1); + flippedCanvas.DrawBitmap(rotated, 0, 0); + } - return bitmap; + return flippedBitmap; + } + } + + case SKCodecOrigin.LeftBottom: + { + var rotated = new SKBitmap(bitmap.Height, bitmap.Width); + using (var surface = new SKCanvas(rotated)) + { + surface.Translate(0, rotated.Height); + surface.RotateDegrees(270); + surface.DrawBitmap(bitmap, 0, 0); + } + + return rotated; + } + + default: + return bitmap; + } } public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) @@ -357,11 +449,11 @@ namespace Emby.Drawing.Skia var blur = options.Blur ?? 0; var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); - using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation)) + using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient)) { if (bitmap == null) { - throw new Exception(string.Format("Skia unable to read image {0}", inputPath)); + throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath)); } //_logger.Info("Color type {0}", bitmap.Info.ColorType); @@ -505,10 +597,6 @@ namespace Emby.Drawing.Skia get { return "Skia"; } } - public void Dispose() - { - } - public bool SupportsImageCollageCreation { get { return true; } diff --git a/Emby.Drawing.Skia/packages.config b/Emby.Drawing.Skia/packages.config index 9d21b2864..2b9b0aee4 100644 --- a/Emby.Drawing.Skia/packages.config +++ b/Emby.Drawing.Skia/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="SkiaSharp" version="1.58.0" targetFramework="portable45-net45+win8" /> + <package id="SkiaSharp" version="1.58.1" targetFramework="portable45-net45+win8" /> </packages>
\ No newline at end of file diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 8f3042e2a..f5a05db05 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -126,6 +126,7 @@ namespace Emby.Drawing return new string[] { "tiff", + "tif", "jpeg", "jpg", "png", @@ -202,10 +203,9 @@ namespace Emby.Drawing } private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp" }; - private bool SupportsTransparency(string path) + public bool SupportsTransparency(string path) { return TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); - ; } public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options) @@ -238,6 +238,7 @@ namespace Emby.Drawing var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = supportedImageInfo.Item1; dateModified = supportedImageInfo.Item2; + var requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath) ?? string.Empty); if (options.Enhancers.Count > 0) { @@ -252,10 +253,11 @@ namespace Emby.Drawing Type = originalImage.Type, Path = originalImagePath - }, item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); + }, requiresTransparency, item, options.ImageIndex, options.Enhancers).ConfigureAwait(false); originalImagePath = tuple.Item1; dateModified = tuple.Item2; + requiresTransparency = tuple.Item3; } var photo = item as Photo; @@ -267,7 +269,7 @@ namespace Emby.Drawing orientation = photo.Orientation; } - if (options.HasDefaultOptions(originalImagePath) && !autoOrient) + if (options.HasDefaultOptions(originalImagePath) && (!autoOrient || !options.RequiresAutoOrientation)) { // Just spit out the original file if all the options are default return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); @@ -284,7 +286,7 @@ namespace Emby.Drawing var newSize = ImageHelper.GetNewImageSize(options, originalImageSize); var quality = options.Quality; - var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]); + var outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); try @@ -296,11 +298,6 @@ namespace Emby.Drawing var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); - if (item == null && string.Equals(options.ItemType, typeof(Photo).Name, StringComparison.OrdinalIgnoreCase)) - { - item = _libraryManager().GetItemById(options.ItemId); - } - if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath)) { options.CropWhiteSpace = false; @@ -321,6 +318,15 @@ namespace Emby.Drawing return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } + catch (ArgumentOutOfRangeException ex) + { + // Decoder failed to decode it +#if DEBUG + _logger.ErrorException("Error encoding image", ex); +#endif + // Just spit out the original file if all the options are default + return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } catch (Exception ex) { // If it fails for whatever reason, return the original image @@ -331,6 +337,34 @@ namespace Emby.Drawing } } + private ImageFormat GetOutputFormat(ImageFormat[] clientSupportedFormats, bool requiresTransparency) + { + var serverFormats = GetSupportedImageOutputFormats(); + + // Client doesn't care about format, so start with webp if supported + if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp)) + { + return ImageFormat.Webp; + } + + // If transparency is needed and webp isn't supported, than png is the only option + if (requiresTransparency) + { + return ImageFormat.Png; + } + + foreach (var format in clientSupportedFormats) + { + if (serverFormats.Contains(format)) + { + return format; + } + } + + // We should never actually get here + return ImageFormat.Jpg; + } + private void CopyFile(string src, string destination) { try @@ -384,21 +418,6 @@ namespace Emby.Drawing return MimeTypes.GetMimeType(path); } - private ImageFormat GetOutputFormat(ImageFormat requestedFormat) - { - if (requestedFormat == ImageFormat.Webp && !_imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp)) - { - return ImageFormat.Png; - } - - return requestedFormat; - } - - private Tuple<string, DateTime> GetResult(string path) - { - return new Tuple<string, DateTime>(path, _fileSystem.GetLastWriteTimeUtc(path)); - } - /// <summary> /// Increment this when there's a change requiring caches to be invalidated /// </summary> @@ -705,13 +724,20 @@ namespace Emby.Drawing .TrimStart('.') .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); + // These are just jpg files renamed as tbn + if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase)) + { + return new Tuple<string, DateTime>(originalImagePath, dateModified); + } + if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase)) { try { var filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N"); - var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + ".webp"); + var cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png"; + var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); var file = _fileSystem.GetFileInfo(outputPath); if (!file.Exists) @@ -748,12 +774,15 @@ namespace Emby.Drawing var imageInfo = item.GetImageInfo(imageType, imageIndex); - var result = await GetEnhancedImage(imageInfo, item, imageIndex, enhancers); + var inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path); + + var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers); return result.Item1; } - private async Task<Tuple<string, DateTime>> GetEnhancedImage(ItemImageInfo image, + private async Task<Tuple<string, DateTime, bool>> GetEnhancedImage(ItemImageInfo image, + bool inputImageSupportsTransparency, IHasMetadata item, int imageIndex, List<IImageEnhancer> enhancers) @@ -767,20 +796,24 @@ namespace Emby.Drawing var cacheGuid = GetImageCacheTag(item, image, enhancers); // Enhance if we have enhancers - var ehnancedImagePath = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid).ConfigureAwait(false); + var ehnancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid).ConfigureAwait(false); + + var ehnancedImagePath = ehnancedImageInfo.Item1; // If the path changed update dateModified if (!string.Equals(ehnancedImagePath, originalImagePath, StringComparison.OrdinalIgnoreCase)) { - return GetResult(ehnancedImagePath); + var treatmentRequiresTransparency = ehnancedImageInfo.Item2; + + return new Tuple<string, DateTime, bool>(ehnancedImagePath, _fileSystem.GetLastWriteTimeUtc(ehnancedImagePath), treatmentRequiresTransparency); } } catch (Exception ex) { - _logger.Error("Error enhancing image", ex); + _logger.ErrorException("Error enhancing image", ex); } - return new Tuple<string, DateTime>(originalImagePath, dateModified); + return new Tuple<string, DateTime, bool>(originalImagePath, dateModified, inputImageSupportsTransparency); } /// <summary> @@ -798,11 +831,11 @@ namespace Emby.Drawing /// or /// item /// </exception> - private async Task<string> GetEnhancedImageInternal(string originalImagePath, + private async Task<Tuple<string, bool>> GetEnhancedImageInternal(string originalImagePath, IHasMetadata item, ImageType imageType, int imageIndex, - IEnumerable<IImageEnhancer> supportedEnhancers, + List<IImageEnhancer> supportedEnhancers, string cacheGuid) { if (string.IsNullOrEmpty(originalImagePath)) @@ -815,13 +848,26 @@ namespace Emby.Drawing throw new ArgumentNullException("item"); } + var treatmentRequiresTransparency = false; + foreach (var enhancer in supportedEnhancers) + { + if (!treatmentRequiresTransparency) + { + treatmentRequiresTransparency = enhancer.GetEnhancedImageInfo(item, originalImagePath, imageType, imageIndex).RequiresTransparency; + } + } + // All enhanced images are saved as png to allow transparency - var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + ".png"); + var cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ? + ".webp" : + (treatmentRequiresTransparency ? ".png" : ".jpg"); + + var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension); // Check again in case of contention if (_fileSystem.FileExists(enhancedImagePath)) { - return enhancedImagePath; + return new Tuple<string, bool>(enhancedImagePath, treatmentRequiresTransparency); } _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(enhancedImagePath)); @@ -840,7 +886,7 @@ namespace Emby.Drawing } - return tmpPath; + return new Tuple<string, bool>(tmpPath, treatmentRequiresTransparency); } /// <summary> @@ -963,8 +1009,15 @@ namespace Emby.Drawing public void Dispose() { _disposed = true; - _imageEncoder.Dispose(); + + var disposable = _imageEncoder as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + _saveImageSizeTimer.Dispose(); + GC.SuppressFinalize(this); } private void CheckDisposed() diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index f04e8aaf1..95ea42ecf 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -61,9 +61,5 @@ namespace Emby.Drawing { throw new NotImplementedException(); } - - public void Dispose() - { - } } } diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 1b6daca73..4e448ac64 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -491,6 +491,7 @@ namespace Emby.Server.Implementations.Activity //_logManager.LoggerLoaded -= _logManager_LoggerLoaded; _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index fcc637b25..d3950929d 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -182,8 +182,7 @@ namespace Emby.Server.Implementations.Channels { }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) - .ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -427,6 +426,8 @@ namespace Emby.Server.Implementations.Channels item.Name = channelInfo.Name; } + item.OnMetadataChanged(); + if (isNew) { _libraryManager.CreateItem(item, cancellationToken); @@ -467,7 +468,7 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, - SortBy = new[] { ItemSortBy.SortName } + OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray(); } @@ -567,7 +568,7 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields }; - var returnItems = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -832,8 +833,7 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) - .ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -934,14 +934,15 @@ namespace Emby.Server.Implementations.Channels ChannelItemSortField? sortField = null; ChannelItemSortField parsedField; - if (query.SortBy.Length == 1 && - Enum.TryParse(query.SortBy[0], true, out parsedField)) + var sortDescending = false; + + if (query.OrderBy.Length == 1 && + Enum.TryParse(query.OrderBy[0].Item1, true, out parsedField)) { sortField = parsedField; + sortDescending = query.OrderBy[0].Item2 == SortOrder.Descending; } - var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending; - var itemsResult = await GetChannelItems(channelProvider, user, query.FolderId, @@ -984,8 +985,7 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) - .ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -1169,7 +1169,7 @@ namespace Emby.Server.Implementations.Channels { items = ApplyFilters(items, query.Filters, user); - items = _libraryManager.Sort(items, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending); + items = _libraryManager.Sort(items, user, query.OrderBy); var all = items.ToList(); var totalCount = totalCountFromProvider ?? all.Count; @@ -1386,6 +1386,8 @@ namespace Emby.Server.Implementations.Channels item.SetImagePath(ImageType.Primary, info.ImageUrl); } + item.OnMetadataChanged(); + if (isNew) { _libraryManager.CreateItem(item, cancellationToken); @@ -1626,6 +1628,7 @@ namespace Emby.Server.Implementations.Channels public void Dispose() { + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index c7378956d..f47e2d10a 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.Collections return subItem; } - var parent = subItem.GetParent(); + var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent(); if (parent != null && parent.HasImage(ImageType.Primary)) { diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 91a2dfdf6..5d0fc8ebc 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -77,6 +77,7 @@ namespace Emby.Server.Implementations.Data { Close(); } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 74e009bd9..89ffb0fce 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -139,34 +139,34 @@ namespace Emby.Server.Implementations.Data RunDefaultInitialization(connection); var createMediaStreamsTableCommand - = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; + = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; string[] queries = { - "PRAGMA locking_mode=EXCLUSIVE", + "PRAGMA locking_mode=EXCLUSIVE", - "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", + "create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)", - "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))", - "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", - "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", + "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))", + "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)", + "create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)", - "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)", + "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)", - "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", + "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)", - "drop index if exists idxPeopleItemId", - "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", - "create index if not exists idxPeopleName on People(Name)", + "drop index if exists idxPeopleItemId", + "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)", + "create index if not exists idxPeopleName on People(Name)", - "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", + "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", - createMediaStreamsTableCommand, + createMediaStreamsTableCommand, - "create index if not exists idx_mediastreams1 on mediastreams(ItemId)", + "create index if not exists idx_mediastreams1 on mediastreams(ItemId)", - "pragma shrink_memory" + "pragma shrink_memory" - }; + }; connection.RunQueries(queries); @@ -252,6 +252,8 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames); existingColumnNames = GetColumnNames(db, "ItemValues"); AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames); @@ -276,80 +278,80 @@ namespace Emby.Server.Implementations.Data string[] postQueries = - { - // obsolete - "drop index if exists idx_TypedBaseItems", - "drop index if exists idx_mediastreams", - "drop index if exists idx_"+ChaptersTableName, - "drop index if exists idx_UserDataKeys1", - "drop index if exists idx_UserDataKeys2", - "drop index if exists idx_TypeTopParentId3", - "drop index if exists idx_TypeTopParentId2", - "drop index if exists idx_TypeTopParentId4", - "drop index if exists idx_Type", - "drop index if exists idx_TypeTopParentId", - "drop index if exists idx_GuidType", - "drop index if exists idx_TopParentId", - "drop index if exists idx_TypeTopParentId6", - "drop index if exists idx_ItemValues2", - "drop index if exists Idx_ProviderIds", - "drop index if exists idx_ItemValues3", - "drop index if exists idx_ItemValues4", - "drop index if exists idx_ItemValues5", - "drop index if exists idx_UserDataKeys3", - "drop table if exists UserDataKeys", - "drop table if exists ProviderIds", - "drop index if exists Idx_ProviderIds1", - "drop table if exists Images", - "drop index if exists idx_Images", - "drop index if exists idx_TypeSeriesPresentationUniqueKey", - "drop index if exists idx_SeriesPresentationUniqueKey", - "drop index if exists idx_TypeSeriesPresentationUniqueKey2", - "drop index if exists idx_AncestorIds3", - "drop index if exists idx_AncestorIds4", - "drop index if exists idx_AncestorIds2", - - "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", - "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", - - "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", - "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", - //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", - "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", - - // covering index - "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", - - // series - "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", - - // series counts - // seriesdateplayed sort order - "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)", - - // live tv programs - "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", - - // covering index for getitemvalues - "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", - - // used by movie suggestions - "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", - "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", - - // latest items - "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", - "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)", - - // resume - "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)", - - // items by name - "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", - "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", - - // Used to update inherited tags - "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", + { + // obsolete + "drop index if exists idx_TypedBaseItems", + "drop index if exists idx_mediastreams", + "drop index if exists idx_"+ChaptersTableName, + "drop index if exists idx_UserDataKeys1", + "drop index if exists idx_UserDataKeys2", + "drop index if exists idx_TypeTopParentId3", + "drop index if exists idx_TypeTopParentId2", + "drop index if exists idx_TypeTopParentId4", + "drop index if exists idx_Type", + "drop index if exists idx_TypeTopParentId", + "drop index if exists idx_GuidType", + "drop index if exists idx_TopParentId", + "drop index if exists idx_TypeTopParentId6", + "drop index if exists idx_ItemValues2", + "drop index if exists Idx_ProviderIds", + "drop index if exists idx_ItemValues3", + "drop index if exists idx_ItemValues4", + "drop index if exists idx_ItemValues5", + "drop index if exists idx_UserDataKeys3", + "drop table if exists UserDataKeys", + "drop table if exists ProviderIds", + "drop index if exists Idx_ProviderIds1", + "drop table if exists Images", + "drop index if exists idx_Images", + "drop index if exists idx_TypeSeriesPresentationUniqueKey", + "drop index if exists idx_SeriesPresentationUniqueKey", + "drop index if exists idx_TypeSeriesPresentationUniqueKey2", + "drop index if exists idx_AncestorIds3", + "drop index if exists idx_AncestorIds4", + "drop index if exists idx_AncestorIds2", + + "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", + "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", + + "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)", + "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)", + //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)", + "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)", + + // covering index + "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", + + // series + "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", + + // series counts + // seriesdateplayed sort order + "create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)", + + // live tv programs + "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", + + // covering index for getitemvalues + "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)", + + // used by movie suggestions + "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)", + "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)", + + // latest items + "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)", + "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)", + + // resume + "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)", + + // items by name + "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)", + "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)", + + // Used to update inherited tags + "create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)", }; connection.RunQueries(postQueries); @@ -457,7 +459,9 @@ namespace Emby.Server.Implementations.Data "Artists", "AlbumArtists", "ExternalId", - "SeriesPresentationUniqueKey" + "SeriesPresentationUniqueKey", + "ShowId", + "OwnerId" }; private readonly string[] _mediaStreamSaveColumns = @@ -577,7 +581,9 @@ namespace Emby.Server.Implementations.Data "Artists", "AlbumArtists", "ExternalId", - "SeriesPresentationUniqueKey" + "SeriesPresentationUniqueKey", + "ShowId", + "OwnerId" }; var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -781,13 +787,14 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@PremiereDate", item.PremiereDate); saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); - if (item.ParentId == Guid.Empty) + var parentId = item.ParentId; + if (parentId == Guid.Empty) { saveItemStatement.TryBindNull("@ParentId"); } else { - saveItemStatement.TryBind("@ParentId", item.ParentId); + saveItemStatement.TryBind("@ParentId", parentId); } if (item.Genres.Count > 0) @@ -1044,6 +1051,26 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@AlbumArtists", albumArtists); saveItemStatement.TryBind("@ExternalId", item.ExternalId); + var program = item as LiveTvProgram; + if (program != null) + { + saveItemStatement.TryBind("@ShowId", program.ShowId); + } + else + { + saveItemStatement.TryBindNull("@ShowId"); + } + + var ownerId = item.OwnerId; + if (ownerId != Guid.Empty) + { + saveItemStatement.TryBind("@OwnerId", ownerId); + } + else + { + saveItemStatement.TryBindNull("@OwnerId"); + } + saveItemStatement.MoveNext(); } @@ -1140,19 +1167,17 @@ namespace Emby.Server.Implementations.Data } return path + - delimeter + - image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + - delimeter + - image.Type + - delimeter + - image.IsPlaceholder; + delimeter + + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + + delimeter + + image.Type; } public ItemImageInfo ItemImageInfoFromValueString(string value) { var parts = value.Split(new[] { '*' }, StringSplitOptions.None); - if (parts.Length != 4) + if (parts.Length < 3) { return null; } @@ -1160,9 +1185,18 @@ namespace Emby.Server.Implementations.Data var image = new ItemImageInfo(); image.Path = parts[0]; - image.DateModified = new DateTime(long.Parse(parts[1], CultureInfo.InvariantCulture), DateTimeKind.Utc); - image.Type = (ImageType)Enum.Parse(typeof(ImageType), parts[2], true); - image.IsPlaceholder = string.Equals(parts[3], true.ToString(), StringComparison.OrdinalIgnoreCase); + + long ticks; + if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out ticks)) + { + image.DateModified = new DateTime(ticks, DateTimeKind.Utc); + } + + ImageType type; + if (Enum.TryParse(parts[2], true, out type)) + { + image.Type = type; + } return image; } @@ -1696,17 +1730,17 @@ namespace Emby.Server.Implementations.Data if (!reader.IsDBNull(index)) { trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select( - i => - { - TrailerType parsedValue; - - if (Enum.TryParse(i, true, out parsedValue)) + i => { - return parsedValue; - } - return (TrailerType?)null; + TrailerType parsedValue; + + if (Enum.TryParse(i, true, out parsedValue)) + { + return parsedValue; + } + return (TrailerType?)null; - }).Where(i => i.HasValue).Select(i => i.Value).ToList(); + }).Where(i => i.HasValue).Select(i => i.Value).ToList(); } } index++; @@ -1935,6 +1969,29 @@ namespace Emby.Server.Implementations.Data index++; } + if (enableProgramAttributes) + { + var program = item as LiveTvProgram; + if (program != null) + { + if (!reader.IsDBNull(index)) + { + program.ShowId = reader.GetString(index); + } + index++; + } + else + { + index++; + } + } + + if (!reader.IsDBNull(index)) + { + item.OwnerId = reader.GetGuid(index); + } + index++; + return item; } @@ -2135,8 +2192,7 @@ namespace Emby.Server.Implementations.Data //return true; } - var sortingFields = query.SortBy.ToList(); - sortingFields.AddRange(query.OrderBy.Select(i => i.Item1)); + var sortingFields = query.OrderBy.Select(i => i.Item1).ToList(); if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) { @@ -2192,8 +2248,8 @@ namespace Emby.Server.Implementations.Data } private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); private IEnumerable<string> GetColumnNamesFromField(ItemFields field) { @@ -2295,13 +2351,13 @@ namespace Emby.Server.Implementations.Data private bool HasStartDate(InternalItemsQuery query) { var excludeParentTypes = new string[] - { + { "Series", "Season", "MusicAlbum", "MusicArtist", "PhotoAlbum" - }; + }; if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -2358,11 +2414,11 @@ namespace Emby.Server.Implementations.Data private bool HasArtistFields(InternalItemsQuery query) { var excludeParentTypes = new string[] - { + { "Series", "Season", "PhotoAlbum" - }; + }; if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -2391,9 +2447,9 @@ namespace Emby.Server.Implementations.Data private bool HasSeriesFields(InternalItemsQuery query) { var excludeParentTypes = new string[] - { + { "PhotoAlbum" - }; + }; if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -2442,6 +2498,7 @@ namespace Emby.Server.Implementations.Data list.Remove("IsPremiere"); list.Remove("EpisodeTitle"); list.Remove("IsRepeat"); + list.Remove("ShowId"); } if (!HasEpisodeAttributes(query)) @@ -2975,16 +3032,7 @@ namespace Emby.Server.Implementations.Data private string GetOrderByText(InternalItemsQuery query) { var orderBy = query.OrderBy.ToList(); - var enableOrderInversion = true; - - if (orderBy.Count == 0) - { - orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder))); - } - else - { - enableOrderInversion = false; - } + var enableOrderInversion = false; if (query.SimilarTo != null) { @@ -2993,12 +3041,10 @@ namespace Emby.Server.Implementations.Data orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)); orderBy.Add(new Tuple<string, SortOrder>("SimilarityScore", SortOrder.Descending)); //orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)); - query.SortOrder = SortOrder.Descending; - enableOrderInversion = false; } } - query.OrderBy = orderBy; + query.OrderBy = orderBy.ToArray(); if (orderBy.Count == 0) { @@ -4235,6 +4281,54 @@ namespace Emby.Server.Implementations.Data } } + if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage); + } + } + + if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage); + } + } + + if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage); + } + } + + if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage)) + { + whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)"); + if (statement != null) + { + statement.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage); + } + } + + if (query.HasChapterImages.HasValue) + { + if (query.HasChapterImages.Value) + { + whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) not null)"); + } + else + { + whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) is null)"); + } + } + if (query.HasDeadParentId.HasValue && query.HasDeadParentId.Value) { whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)"); @@ -4316,7 +4410,7 @@ namespace Emby.Server.Implementations.Data index++; } - whereClauses.Add(string.Join(" OR ", includeIds.ToArray())); + whereClauses.Add("(" + string.Join(" OR ", includeIds.ToArray()) + ")"); } if (query.ExcludeItemIds.Length > 0) { @@ -4400,7 +4494,6 @@ namespace Emby.Server.Implementations.Data } } - var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; @@ -4654,63 +4747,24 @@ namespace Emby.Server.Implementations.Data private void UpdateInheritedTags(CancellationToken cancellationToken) { - var newValues = new List<Tuple<Guid, string[]>>(); - - var commandText = @"select guid, -(select group_concat(Value, '|') from ItemValues where (ItemValues.ItemId = Outer.Guid OR ItemValues.ItemId in ((Select AncestorId from AncestorIds where AncestorIds.ItemId=Outer.guid))) and ItemValues.Type = 4) NewInheritedTags, -(select group_concat(Value, '|') from ItemValues where ItemValues.ItemId = Outer.Guid and ItemValues.Type = 6) CurrentInheritedTags -from typedbaseitems as Outer -where (NewInheritedTags <> CurrentInheritedTags or (NewInheritedTags is null) <> (CurrentInheritedTags is null)) -limit 100"; - using (WriteLock.Write()) { using (var connection = CreateConnection()) { connection.RunInTransaction(db => { - foreach (var row in connection.Query(commandText)) - { - var id = row.GetGuid(0); - string value = row.IsDBNull(1) ? null : row.GetString(1); - - var valuesArray = string.IsNullOrWhiteSpace(value) ? new string[] { } : value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - - newValues.Add(new Tuple<Guid, string[]>(id, valuesArray)); - } - - Logger.Debug("UpdateInheritedTags - {0} rows", newValues.Count); - if (newValues.Count == 0) + connection.ExecuteAll(string.Join(";", new string[] { - return; - } - - using (var insertStatement = PrepareStatement(connection, "insert into ItemValues (ItemId, Type, Value, CleanValue) values (@ItemId, 6, @Value, @CleanValue)")) - { - using (var deleteStatement = PrepareStatement(connection, "delete from ItemValues where ItemId=@ItemId and Type=6")) - { - foreach (var item in newValues) - { - var guidBlob = item.Item1.ToGuidBlob(); + "delete from itemvalues where type = 6", - deleteStatement.Reset(); - deleteStatement.TryBind("@ItemId", guidBlob); - deleteStatement.MoveNext(); + "insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4", - foreach (var itemValue in item.Item2) - { - insertStatement.Reset(); - - insertStatement.TryBind("@ItemId", guidBlob); - insertStatement.TryBind("@Value", itemValue); + @"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue +FROM AncestorIds +LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId) +where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 " - insertStatement.TryBind("@CleanValue", GetCleanValue(itemValue)); - - insertStatement.MoveNext(); - } - } - } - } + })); }, TransactionMode); } @@ -5503,7 +5557,7 @@ limit 100"; var listIndex = 0; using (var statement = PrepareStatement(connection, - "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)")) + "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)")) { foreach (var person in people) { @@ -5632,8 +5686,8 @@ limit 100"; connection.Execute("delete from mediastreams where ItemId=@ItemId", id.ToGuidBlob()); using (var statement = PrepareStatement(connection, string.Format("replace into mediastreams ({0}) values ({1})", - string.Join(",", _mediaStreamSaveColumns), - string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray())))) + string.Join(",", _mediaStreamSaveColumns), + string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray())))) { foreach (var stream in streams) { diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 09b8bf22c..a0a5f32ef 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -104,6 +104,7 @@ namespace Emby.Server.Implementations.Diagnostics public void Dispose() { _process.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index a0e994d88..c5fcc0e4c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -87,17 +87,17 @@ namespace Emby.Server.Implementations.Dto return GetBaseItemDto(item, options, user, owner); } - public Task<BaseItemDto[]> GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) { return GetBaseItemDtos(items, items.Count, options, user, owner); } - public Task<BaseItemDto[]> GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null) { return GetBaseItemDtos(items, items.Length, options, user, owner); } - public async Task<BaseItemDto[]> GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null) { if (items == null) { @@ -157,12 +157,13 @@ namespace Emby.Server.Implementations.Dto if (programTuples.Count > 0) { - await _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).ConfigureAwait(false); + var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user); + Task.WaitAll(task); } if (channelTuples.Count > 0) { - await _livetvManager().AddChannelInfo(channelTuples, options, user).ConfigureAwait(false); + _livetvManager().AddChannelInfo(channelTuples, options, user); } return returnItems; @@ -177,8 +178,7 @@ namespace Emby.Server.Implementations.Dto if (tvChannel != null) { var list = new List<Tuple<BaseItemDto, LiveTvChannel>> { new Tuple<BaseItemDto, LiveTvChannel>(dto, tvChannel) }; - var task = _livetvManager().AddChannelInfo(list, options, user); - Task.WaitAll(task); + _livetvManager().AddChannelInfo(list, options, user); } else if (item is LiveTvProgram) { @@ -275,8 +275,7 @@ namespace Emby.Server.Implementations.Dto { var hasFullSyncInfo = options.Fields.Contains(ItemFields.SyncInfo); - if (!options.Fields.Contains(ItemFields.BasicSyncInfo) && - !hasFullSyncInfo) + if (!hasFullSyncInfo && !options.Fields.Contains(ItemFields.BasicSyncInfo)) { return; } @@ -418,6 +417,8 @@ namespace Emby.Server.Implementations.Dto { dto.Type = "Recording"; dto.CanDownload = false; + dto.RunTimeTicks = null; + if (!string.IsNullOrWhiteSpace(dto.SeriesName)) { dto.EpisodeTitle = dto.Name; @@ -1486,7 +1487,7 @@ namespace Emby.Server.Implementations.Dto } } - var parent = currentItem.DisplayParent ?? currentItem.GetParent(); + var parent = currentItem.DisplayParent ?? (currentItem.IsOwnedItem ? currentItem.GetOwner() : currentItem.GetParent()); if (parent == null && !(originalItem is UserRootFolder) && !(originalItem is UserView) && !(originalItem is AggregateFolder) && !(originalItem is ICollectionFolder) && !(originalItem is Channel)) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 84ec214c9..ccff29eef 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -129,7 +129,6 @@ <Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" /> <Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="Images\BaseDynamicImageProvider.cs" /> - <Compile Include="IO\AsyncStreamCopier.cs" /> <Compile Include="IO\FileRefresher.cs" /> <Compile Include="IO\IsoManager.cs" /> <Compile Include="IO\LibraryMonitor.cs" /> @@ -440,7 +439,6 @@ <Compile Include="Networking\NetworkManager.cs" /> <Compile Include="Net\DisposableManagedObjectBase.cs" /> <Compile Include="Net\NetAcceptSocket.cs" /> - <Compile Include="Net\SocketAcceptor.cs" /> <Compile Include="Net\SocketFactory.cs" /> <Compile Include="Net\UdpSocket.cs" /> <Compile Include="News\NewsEntryPoint.cs" /> @@ -497,6 +495,7 @@ <Compile Include="Services\ServiceController.cs" /> <Compile Include="Services\ServiceExec.cs" /> <Compile Include="Services\StringMapTypeDeserializer.cs" /> + <Compile Include="Services\SwaggerService.cs" /> <Compile Include="Services\UrlExtensions.cs" /> <Compile Include="Session\HttpSessionController.cs" /> <Compile Include="Session\SessionManager.cs" /> @@ -655,16 +654,15 @@ <Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project> <Name>SocketHttpListener</Name> </ProjectReference> + <Reference Include="Emby.Naming"> + <HintPath>..\ThirdParty\emby\Emby.Naming.dll</HintPath> + </Reference> <Reference Include="Emby.Server.MediaEncoding"> <HintPath>..\ThirdParty\emby\Emby.Server.MediaEncoding.dll</HintPath> </Reference> <Reference Include="Emby.XmlTv, Version=1.0.6387.29335, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Emby.XmlTv.1.0.10\lib\portable-net45+netstandard2.0+win8\Emby.XmlTv.dll</HintPath> </Reference> - <Reference Include="MediaBrowser.Naming, Version=1.0.6447.2217, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\MediaBrowser.Naming.1.0.7\lib\portable-net45+netstandard2.0+win8\MediaBrowser.Naming.dll</HintPath> - <Private>True</Private> - </Reference> <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath> <Private>True</Private> diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index 561f5ee12..c2cee00c8 100644 --- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -112,6 +112,7 @@ namespace Emby.Server.Implementations.EntryPoints _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged; DisposeTimer(); + GC.SuppressFinalize(this); } private void DisposeTimer() diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index c96799b2f..9b434d606 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -294,6 +294,7 @@ namespace Emby.Server.Implementations.EntryPoints { _disposed = true; DisposeNat(); + GC.SuppressFinalize(this); } private void DisposeNat() diff --git a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs index 8ae85e390..221580681 100644 --- a/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs +++ b/Emby.Server.Implementations/EntryPoints/KeepServerAwake.cs @@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 80a188bc0..299da0744 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -198,9 +198,10 @@ namespace Emby.Server.Implementations.EntryPoints LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); } - if (e.Item.Parent != null) + var parent = e.Item.GetParent() as Folder; + if (parent != null) { - _foldersAddedTo.Add(e.Item.Parent); + _foldersAddedTo.Add(parent); } _itemsAdded.Add(e.Item); @@ -259,9 +260,10 @@ namespace Emby.Server.Implementations.EntryPoints LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite); } - if (e.Item.Parent != null) + var parent = e.Item.GetParent() as Folder; + if (parent != null) { - _foldersRemovedFrom.Add(e.Item.Parent); + _foldersRemovedFrom.Add(parent); } _itemsRemoved.Add(e.Item); @@ -426,6 +428,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs index 0203b5192..21e075cf5 100644 --- a/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs +++ b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs @@ -68,6 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index b674fc39b..f73b40b46 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -72,6 +72,7 @@ namespace Emby.Server.Implementations.EntryPoints _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled; _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated; _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index 77de849a1..4c16b1d39 100644 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -1,41 +1,74 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; +using System; +using MediaBrowser.Controller.Library; using System.Threading; +using MediaBrowser.Model.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.EntryPoints { /// <summary> /// Class RefreshUsersMetadata /// </summary> - public class RefreshUsersMetadata : IServerEntryPoint + public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask { /// <summary> /// The _user manager /// </summary> private readonly IUserManager _userManager; + private IFileSystem _fileSystem; + + public string Name => "Refresh Users"; + + public string Key => "RefreshUsers"; + + public string Description => "Refresh user infos"; + + public string Category + { + get { return "Library"; } + } + + public bool IsHidden => true; + + public bool IsEnabled => true; + + public bool IsLogged => true; /// <summary> /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class. /// </summary> - /// <param name="userManager">The user manager.</param> - public RefreshUsersMetadata(IUserManager userManager) + public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem) { _userManager = userManager; + _fileSystem = fileSystem; } - /// <summary> - /// Runs this instance. - /// </summary> - public async void Run() + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { - await _userManager.RefreshUsersMetadata(CancellationToken.None).ConfigureAwait(false); + var users = _userManager.Users.ToList(); + + foreach (var user in users) + { + cancellationToken.ThrowIfCancellationRequested(); + + await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken).ConfigureAwait(false); + } } - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { + return new List<TaskTriggerInfo> + { + new TaskTriggerInfo + { + IntervalTicks = TimeSpan.FromDays(1).Ticks, + Type = TaskTriggerInfo.TriggerInterval + } + }; } } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 4d640bc95..514321e20 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -174,6 +174,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 424153f22..614c04fd2 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -1,4 +1,5 @@ -using Emby.Server.Implementations.Browser; +using System; +using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; @@ -54,6 +55,7 @@ namespace Emby.Server.Implementations.EntryPoints /// </summary> public void Dispose() { + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs index 4ab6d32f3..08f3edb3d 100644 --- a/Emby.Server.Implementations/EntryPoints/SystemEvents.cs +++ b/Emby.Server.Implementations/EntryPoints/SystemEvents.cs @@ -23,18 +23,9 @@ namespace Emby.Server.Implementations.EntryPoints public void Run() { - _systemEvents.SessionLogoff += _systemEvents_SessionLogoff; _systemEvents.SystemShutdown += _systemEvents_SystemShutdown; } - private void _systemEvents_SessionLogoff(object sender, EventArgs e) - { - if (!_appHost.IsRunningAsService) - { - _appHost.Shutdown(); - } - } - private void _systemEvents_SystemShutdown(object sender, EventArgs e) { _appHost.Shutdown(); @@ -43,6 +34,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _systemEvents.SystemShutdown -= _systemEvents_SystemShutdown; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index df5a7c985..d04df0d2b 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -65,6 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs index 99d39ffe0..fb9402986 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -130,6 +130,7 @@ namespace Emby.Server.Implementations.EntryPoints public void Dispose() { _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index accdc5e9d..13c72bf3c 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.EntryPoints // Go up one level for indicators if (baseItem != null) { - var parent = baseItem.GetParent(); + var parent = baseItem.IsOwnedItem ? baseItem.GetOwner() : baseItem.GetParent(); if (parent != null) { @@ -160,6 +160,7 @@ namespace Emby.Server.Implementations.EntryPoints } _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index f512b723d..fe545ecff 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -823,27 +823,6 @@ namespace Emby.Server.Implementations.HttpClientManager } /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _httpClients.Clear(); - } - } - - /// <summary> /// Throws the cancellation exception. /// </summary> /// <param name="options">The options.</param> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 8cb7b5dbf..aa679e1b9 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -160,6 +160,9 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) { Logger.Info("Transmit file {0}", Path); + + //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; + await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); return; } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index f1fea2085..86df798d0 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,10 +7,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Net; @@ -24,8 +24,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; -using SocketHttpListener.Net; -using SocketHttpListener.Primitives; namespace Emby.Server.Implementations.HttpServer { @@ -34,7 +32,7 @@ namespace Emby.Server.Implementations.HttpServer private string DefaultRedirectPath { get; set; } private readonly ILogger _logger; - public IEnumerable<string> UrlPrefixes { get; private set; } + public string[] UrlPrefixes { get; private set; } private readonly List<IService> _restServices = new List<IService>(); @@ -56,14 +54,13 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; - private readonly ICertificate _certificate; + private readonly X509Certificate _certificate; private readonly IEnvironmentInfo _environment; - private readonly IStreamFactory _streamFactory; private readonly Func<Type, Func<string, object>> _funcParseFn; private readonly bool _enableDualModeSockets; - public List<Action<IRequest, IResponse, object>> RequestFilters { get; set; } - public List<Action<IRequest, IResponse, object>> ResponseFilters { get; set; } + public Action<IRequest, IResponse, object>[] RequestFilters { get; set; } + public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; } private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>(); public static HttpListenerHost Instance { get; protected set; } @@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer ILogger logger, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, X509Certificate certificate, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) { Instance = this; @@ -87,7 +84,6 @@ namespace Emby.Server.Implementations.HttpServer _xmlSerializer = xmlSerializer; _environment = environment; _certificate = certificate; - _streamFactory = streamFactory; _funcParseFn = funcParseFn; _enableDualModeSockets = enableDualModeSockets; _fileSystem = fileSystem; @@ -95,8 +91,8 @@ namespace Emby.Server.Implementations.HttpServer _logger = logger; - RequestFilters = new List<Action<IRequest, IResponse, object>>(); - ResponseFilters = new List<Action<IRequest, IResponse, object>>(); + RequestFilters = new Action<IRequest, IResponse, object>[] { }; + ResponseFilters = new Action<IRequest, IResponse, object>[] { }; } public string GlobalResponse { get; set; } @@ -135,7 +131,9 @@ namespace Emby.Server.Implementations.HttpServer //Exec all RequestFilter attributes with Priority < 0 var attributes = GetRequestFilterAttributes(requestDto.GetType()); var i = 0; - for (; i < attributes.Length && attributes[i].Priority < 0; i++) + var count = attributes.Count; + + for (; i < count && attributes[i].Priority < 0; i++) { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); @@ -148,7 +146,7 @@ namespace Emby.Server.Implementations.HttpServer } //Exec remaining RequestFilter attributes with Priority >= 0 - for (; i < attributes.Length && attributes[i].Priority >= 0; i++) + for (; i < count && attributes[i].Priority >= 0; i++) { var attribute = attributes[i]; attribute.RequestFilter(req, res, requestDto); @@ -167,7 +165,7 @@ namespace Emby.Server.Implementations.HttpServer ServiceOperationsMap[requestType] = serviceType; } - private IHasRequestFilter[] GetRequestFilterAttributes(Type requestDtoType) + private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType) { var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList(); @@ -179,40 +177,13 @@ namespace Emby.Server.Implementations.HttpServer attributes.Sort((x, y) => x.Priority - y.Priority); - return attributes.ToArray(attributes.Count); - } - - /// <summary> - /// Starts the Web Service - /// </summary> - private void StartListener() - { - WebSocketSharpRequest.HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes.First()); - - _listener = GetListener(); - - _listener.WebSocketConnected = OnWebSocketConnected; - _listener.WebSocketConnecting = OnWebSocketConnecting; - _listener.ErrorHandler = ErrorHandler; - _listener.RequestHandler = RequestHandler; - - _listener.Start(UrlPrefixes); - } - - public static string GetHandlerPathIfAny(string listenerUrl) - { - if (listenerUrl == null) return null; - var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - if (pos == -1) return null; - var startHostUrl = listenerUrl.Substring(pos + "://".Length); - var endPos = startHostUrl.IndexOf('/'); - if (endPos == -1) return null; - var endHostUrl = startHostUrl.Substring(endPos + 1); - return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); + return attributes; } private IHttpListener GetListener() { + //return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem); + return new WebSocketSharpListener(_logger, _certificate, _memoryStreamProvider, @@ -220,22 +191,11 @@ namespace Emby.Server.Implementations.HttpServer _networkManager, _socketFactory, _cryptoProvider, - _streamFactory, _enableDualModeSockets, - GetRequest, _fileSystem, _environment); } - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var operationName = httpContext.Request.GetOperationName(); - - var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); - - return req; - } - private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) { if (_disposed) @@ -348,7 +308,8 @@ namespace Emby.Server.Implementations.HttpServer if (_listener != null) { _logger.Info("Stopping HttpListener..."); - _listener.Stop(); + var task = _listener.Stop(); + Task.WaitAll(task); _logger.Info("HttpListener stopped"); } } @@ -434,7 +395,7 @@ namespace Emby.Server.Implementations.HttpServer return address.Trim('/'); } - private bool ValidateHost(Uri url) + private bool ValidateHost(string host) { var hosts = _config .Configuration @@ -447,7 +408,7 @@ namespace Emby.Server.Implementations.HttpServer return true; } - var host = url.Host ?? string.Empty; + host = host ?? string.Empty; _logger.Debug("Validating host {0}", host); @@ -465,7 +426,7 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Overridable method that can be used to implement a custom hnandler /// </summary> - protected async Task RequestHandler(IHttpRequest httpReq, Uri url, CancellationToken cancellationToken) + protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { var date = DateTime.Now; var httpRes = httpReq.Response; @@ -484,7 +445,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (!ValidateHost(url)) + if (!ValidateHost(host)) { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; @@ -504,9 +465,7 @@ namespace Emby.Server.Implementations.HttpServer } var operationName = httpReq.OperationName; - var localPath = url.LocalPath; - var urlString = url.OriginalString; enableLog = EnableLogging(urlString, localPath); urlToLog = urlString; logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; @@ -698,13 +657,18 @@ namespace Emby.Server.Implementations.HttpServer ServiceController.Init(this, types); - var requestFilters = _appHost.GetExports<IRequestFilter>().ToList(); - foreach (var filter in requestFilters) + var list = new List<Action<IRequest, IResponse, object>>(); + foreach (var filter in _appHost.GetExports<IRequestFilter>()) { - RequestFilters.Add(filter.Filter); + list.Add(filter.Filter); } - ResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); + RequestFilters = list.ToArray(); + + ResponseFilters = new Action<IRequest, IResponse, object>[] + { + new ResponseFilter(_logger).FilterResponse + }; } public RouteAttribute[] GetRouteAttributes(Type requestType) @@ -721,19 +685,19 @@ namespace Emby.Server.Implementations.HttpServer Summary = route.Summary }); - routes.Add(new RouteAttribute(NormalizeRoutePath(route.Path), route.Verbs) + routes.Add(new RouteAttribute(NormalizeMediaBrowserRoutePath(route.Path), route.Verbs) { Notes = route.Notes, Priority = route.Priority, Summary = route.Summary }); - routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) - { - Notes = route.Notes, - Priority = route.Priority, - Summary = route.Summary - }); + //routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs) + //{ + // Notes = route.Notes, + // Priority = route.Priority, + // Summary = route.Summary + //}); } return routes.ToArray(routes.Count); @@ -774,24 +738,24 @@ namespace Emby.Server.Implementations.HttpServer return "emby/" + path; } - private string DoubleNormalizeEmbyRoutePath(string path) + private string NormalizeMediaBrowserRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/emby/emby" + path; + return "/mediabrowser" + path; } - return "emby/emby/" + path; + return "mediabrowser/" + path; } - private string NormalizeRoutePath(string path) + private string DoubleNormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) { - return "/mediabrowser" + path; + return "/emby/emby" + path; } - return "mediabrowser/" + path; + return "emby/emby/" + path; } private bool _disposed; @@ -819,10 +783,18 @@ namespace Emby.Server.Implementations.HttpServer GC.SuppressFinalize(this); } - public void StartServer(IEnumerable<string> urlPrefixes) + public void StartServer(string[] urlPrefixes) { - UrlPrefixes = urlPrefixes.ToList(); - StartListener(); + UrlPrefixes = urlPrefixes; + + _listener = GetListener(); + + _listener.WebSocketConnected = OnWebSocketConnected; + _listener.WebSocketConnecting = OnWebSocketConnecting; + _listener.ErrorHandler = ErrorHandler; + _listener.RequestHandler = RequestHandler; + + _listener.Start(UrlPrefixes); } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 7bd8fe2bf..f5a1fe246 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -424,11 +424,14 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - // Quotes are valid in linux. They'll possibly cause issues here - var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); - if (!string.IsNullOrWhiteSpace(filename)) + if (!options.ResponseHeaders.ContainsKey("Content-Disposition")) { - options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\""; + // Quotes are valid in linux. They'll possibly cause issues here + var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); + if (!string.IsNullOrWhiteSpace(filename)) + { + options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\""; + } } return GetStaticResult(requestContext, options); @@ -487,69 +490,8 @@ namespace Emby.Server.Implementations.HttpServer return result; } - var compress = ShouldCompressResponse(requestContext, contentType); - var hasHeaders = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false); - AddResponseHeaders(hasHeaders, options.ResponseHeaders); - - return hasHeaders; - } - - /// <summary> - /// Shoulds the compress response. - /// </summary> - /// <param name="requestContext">The request context.</param> - /// <param name="contentType">Type of the content.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - private bool ShouldCompressResponse(IRequest requestContext, string contentType) - { - // It will take some work to support compression with byte range requests - if (!string.IsNullOrWhiteSpace(requestContext.Headers.Get("Range"))) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Don't compress images - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(contentType, "application/x-javascript", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (string.Equals(contentType, "application/xml", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - return false; - } - - return true; - } - - /// <summary> - /// The us culture - /// </summary> - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private async Task<IHasHeaders> GetStaticResult(IRequest requestContext, StaticResultOptions options, bool compress) - { var isHeadRequest = options.IsHeadRequest; var factoryFn = options.ContentFactory; - var contentType = options.ContentType; var responseHeaders = options.ResponseHeaders; //var requestedCompressionType = GetCompressionType(requestContext); @@ -558,22 +500,28 @@ namespace Emby.Server.Implementations.HttpServer if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) { - return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) { OnComplete = options.OnComplete, OnError = options.OnError, FileShare = options.FileShare }; + + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } if (!string.IsNullOrWhiteSpace(rangeHeader)) { var stream = await factoryFn().ConfigureAwait(false); - return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) + var hasHeaders = new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { OnComplete = options.OnComplete }; + + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } else { @@ -588,15 +536,23 @@ namespace Emby.Server.Implementations.HttpServer return GetHttpResult(new byte[] { }, contentType, true); } - return new StreamWriter(stream, contentType, _logger) + var hasHeaders = new StreamWriter(stream, contentType, _logger) { OnComplete = options.OnComplete, OnError = options.OnError }; + + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + return hasHeaders; } } /// <summary> + /// The us culture + /// </summary> + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// <summary> /// Adds the caching responseHeaders. /// </summary> private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, bool noCache) diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 82175dbed..9feb2311d 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the request handler. /// </summary> /// <value>The request handler.</value> - Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } + Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; } /// <summary> /// Gets or sets the web socket handler. @@ -42,6 +42,6 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Stops this instance. /// </summary> - void Stop(); + Task Stop(); } } diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs index de30dc30a..46bb4c7f9 100644 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -1,10 +1,8 @@ using MediaBrowser.Model.Logging; using System; using System.Globalization; -using System.Linq; using MediaBrowser.Model.Services; using SocketHttpListener.Net; -using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.HttpServer { @@ -30,7 +28,20 @@ namespace Emby.Server.Implementations.HttpServer } else { - var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray(headers.Count)); + var headerText = string.Empty; + var index = 0; + + foreach (var i in headers) + { + if (index > 0) + { + headerText += ", "; + } + + headerText += i.Name + "=" + i.Value; + + index++; + } logger.Info("HTTP {0} {1}. {2}", method, url, headerText); } @@ -49,7 +60,8 @@ namespace Emby.Server.Implementations.HttpServer var durationMs = duration.TotalMilliseconds; var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + var headerText = string.Empty; logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 4d00c9b19..fadab4482 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -7,8 +7,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using System; -using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer.Security { @@ -38,19 +38,19 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> public string HtmlRedirect { get; set; } - public void Authenticate(IServiceRequest request, + public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) { ValidateUser(request, authAttribtues); } - private void ValidateUser(IServiceRequest request, + private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = AuthorizationContext.GetAuthorizationInfo(request); - if (!IsExemptFromAuthenticationToken(auth, authAttribtues)) + if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request)) { var valid = IsValidConnectKey(auth.Token); @@ -76,9 +76,9 @@ namespace Emby.Server.Implementations.HttpServer.Security var info = GetTokenInfo(request); - if (!IsExemptFromRoles(auth, authAttribtues, info)) + if (!IsExemptFromRoles(auth, authAttribtues, request, info)) { - var roles = authAttribtues.GetRoles().ToList(); + var roles = authAttribtues.GetRoles(); ValidateRoles(roles, user); } @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private void ValidateUserAccess(User user, IServiceRequest request, + private void ValidateUserAccess(User user, IRequest request, IAuthenticationAttributes authAttribtues, AuthorizationInfo auth) { @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security !authAttribtues.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { - request.AddResponseHeader("X-Application-Error-Code", "ParentalControl"); + request.Response.AddHeader("X-Application-Error-Code", "ParentalControl"); throw new SecurityException("This user account is not allowed access at this time.") { @@ -132,23 +132,33 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues) + private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { return true; } + if (authAttribtues.AllowLocal && request.IsLocal) + { + return true; + } + return false; } - private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo) + private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request, AuthenticationInfo tokenInfo) { if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard) { return true; } + if (authAttribtues.AllowLocal && request.IsLocal) + { + return true; + } + if (string.IsNullOrWhiteSpace(auth.Token)) { return true; @@ -162,7 +172,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return false; } - private void ValidateRoles(List<string> roles, User user) + private void ValidateRoles(string[] roles, User user) { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { @@ -196,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } } - private AuthenticationInfo GetTokenInfo(IServiceRequest request) + private AuthenticationInfo GetTokenInfo(IRequest request) { object info; request.Items.TryGetValue("OriginalAuthenticationInfo", out info); @@ -213,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return ConnectManager.IsAuthorizationTokenValid(token); } - private void ValidateSecurityToken(IServiceRequest request, string token) + private void ValidateSecurityToken(IRequest request, string token) { if (string.IsNullOrWhiteSpace(token)) { diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index ede85fb67..c9d5ed007 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -3,8 +3,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Services; +using System.Linq; namespace Emby.Server.Implementations.HttpServer.Security { @@ -21,11 +21,10 @@ namespace Emby.Server.Implementations.HttpServer.Security public AuthorizationInfo GetAuthorizationInfo(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetAuthorizationInfo(req); + return GetAuthorizationInfo((IRequest)requestContext); } - public AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext) + public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) { object cached; if (requestContext.Items.TryGetValue("AuthorizationInfo", out cached)) @@ -41,7 +40,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private AuthorizationInfo GetAuthorization(IServiceRequest httpReq) + private AuthorizationInfo GetAuthorization(IRequest httpReq) { var auth = GetAuthorizationDictionary(httpReq); @@ -90,7 +89,7 @@ namespace Emby.Server.Implementations.HttpServer.Security AccessToken = token }); - var tokenInfo = result.Items.FirstOrDefault(); + var tokenInfo = result.Items.Length > 0 ? result.Items[0] : null; if (tokenInfo != null) { @@ -135,7 +134,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(IServiceRequest httpReq) + private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -161,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer.Security // There should be at least to parts if (parts.Length != 2) return null; - var acceptedNames = new[] { "MediaBrowser", "Emby"}; + var acceptedNames = new[] { "MediaBrowser", "Emby" }; // It has to be a digest request if (!acceptedNames.Contains(parts[0] ?? string.Empty, StringComparer.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 33dd4e2d7..dd5d64bf6 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public Task<SessionInfo> GetSession(IServiceRequest requestContext) + public Task<SessionInfo> GetSession(IRequest requestContext) { var authorization = _authContext.GetAuthorizationInfo(requestContext); @@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user); } - private AuthenticationInfo GetTokenInfo(IServiceRequest request) + private AuthenticationInfo GetTokenInfo(IRequest request) { object info; request.Items.TryGetValue("OriginalAuthenticationInfo", out info); @@ -47,11 +47,10 @@ namespace Emby.Server.Implementations.HttpServer.Security public Task<SessionInfo> GetSession(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetSession(req); + return GetSession((IRequest)requestContext); } - public async Task<User> GetUser(IServiceRequest requestContext) + public async Task<User> GetUser(IRequest requestContext) { var session = await GetSession(requestContext).ConfigureAwait(false); @@ -60,8 +59,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public Task<User> GetUser(object requestContext) { - var req = new ServiceRequest((IRequest)requestContext); - return GetUser(req); + return GetUser((IRequest)requestContext); } } } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs index 9823a2ff5..cc7a4557e 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/SharpWebSocket.cs @@ -123,6 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index e648838b2..8fb0d4f3e 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -4,6 +4,7 @@ using SocketHttpListener.Net; using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; @@ -22,22 +23,20 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private HttpListener _listener; private readonly ILogger _logger; - private readonly ICertificate _certificate; + private readonly X509Certificate _certificate; private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly ITextEncoding _textEncoding; private readonly INetworkManager _networkManager; private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamFactory _streamFactory; private readonly IFileSystem _fileSystem; - private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory; private readonly bool _enableDualMode; private readonly IEnvironmentInfo _environment; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationToken _disposeCancellationToken; - public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem, IEnvironmentInfo environment) + public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; _certificate = certificate; @@ -46,9 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _networkManager = networkManager; _socketFactory = socketFactory; _cryptoProvider = cryptoProvider; - _streamFactory = streamFactory; _enableDualMode = enableDualMode; - _httpRequestFactory = httpRequestFactory; _fileSystem = fileSystem; _environment = environment; @@ -56,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } public Action<Exception, IRequest, bool> ErrorHandler { get; set; } - public Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; } + public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; } public Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; } @@ -65,7 +62,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable<string> urlPrefixes) { if (_listener == null) - _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); + _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); _listener.EnableDualMode = _enableDualMode; @@ -87,8 +84,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private void ProcessContext(HttpListenerContext context) { - InitTask(context, _disposeCancellationToken); - //Task.Run(() => InitTask(context, _disposeCancellationToken)); + //InitTask(context, _disposeCancellationToken); + Task.Run(() => InitTask(context, _disposeCancellationToken)); } private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) @@ -117,7 +114,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return Task.FromResult(true); } - return RequestHandler(httpReq, request.Url, cancellationToken); + var uri = request.Url; + + return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); } private void ProcessWebSocketRequest(HttpListenerContext ctx) @@ -173,22 +172,23 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private IHttpRequest GetRequest(HttpListenerContext httpContext) { - return _httpRequestFactory(httpContext); + var operationName = httpContext.Request.GetOperationName(); + + var req = new WebSocketSharpRequest(httpContext, operationName, _logger, _memoryStreamProvider); + + return req; } - public void Stop() + public Task Stop() { _disposeCancellationTokenSource.Cancel(); if (_listener != null) { - foreach (var prefix in _listener.Prefixes.ToList()) - { - _listener.Prefixes.Remove(prefix); - } - _listener.Close(); } + + return Task.FromResult(true); } public void Dispose() diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 2dfe6a9e3..522377f0c 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -27,6 +27,20 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _memoryStreamProvider = memoryStreamProvider; this.request = httpContext.Request; this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); + + //HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); + } + + private static string GetHandlerPathIfAny(string listenerUrl) + { + if (listenerUrl == null) return null; + var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); + if (pos == -1) return null; + var startHostUrl = listenerUrl.Substring(pos + "://".Length); + var endPos = startHostUrl.IndexOf('/'); + if (endPos == -1) return null; + var endHostUrl = startHostUrl.Substring(endPos + 1); + return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); } public HttpListenerRequest HttpRequest @@ -108,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return remoteIp ?? (remoteIp = (CheckBadChars(XForwardedFor)) ?? (NormalizeIp(CheckBadChars(XRealIp)) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.IpAddress.ToString()) : null))); + (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); } } @@ -232,13 +246,12 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp set { this.responseContentType = value; - HasExplicitResponseContentType = true; } } public const string FormUrlEncoded = "application/x-www-form-urlencoded"; public const string MultiPartFormData = "multipart/form-data"; - private static string GetResponseContentType(IRequest httpReq) + public static string GetResponseContentType(IRequest httpReq) { var specifiedContentType = GetQueryStringContentType(httpReq); if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType; @@ -346,8 +359,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp : strVal.Substring(0, pos); } - public bool HasExplicitResponseContentType { get; private set; } - public static string HandlerFactoryPath; private string pathInfo; @@ -490,13 +501,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return HttpMethod; } } - public string Param(string name) - { - return Headers[name] - ?? QueryString[name] - ?? FormData[name]; - } - public string ContentType { get { return request.ContentType; } @@ -584,18 +588,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp return stream; } - public static string GetHandlerPathIfAny(string listenerUrl) - { - if (listenerUrl == null) return null; - var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - if (pos == -1) return null; - var startHostUrl = listenerUrl.Substring(pos + "://".Length); - var endPos = startHostUrl.IndexOf('/'); - if (endPos == -1) return null; - var endHostUrl = startHostUrl.Substring(endPos + 1); - return String.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); - } - public static string NormalizePathInfo(string pathInfo, string handlerPath) { if (handlerPath != null && pathInfo.TrimStart('/').StartsWith( diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index d6762d94b..5b51c0cf1 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } public IRequest Request { get; private set; } - public bool UseBufferedStream { get; set; } public Dictionary<string, object> Items { get; private set; } public object OriginalResponse { diff --git a/Emby.Server.Implementations/HttpServerFactory.cs b/Emby.Server.Implementations/HttpServerFactory.cs index 007f5c829..717c50e7b 100644 --- a/Emby.Server.Implementations/HttpServerFactory.cs +++ b/Emby.Server.Implementations/HttpServerFactory.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations IJsonSerializer json, IXmlSerializer xml, IEnvironmentInfo environment, - ICertificate certificate, + X509Certificate certificate, IFileSystem fileSystem, bool enableDualModeSockets) { @@ -63,7 +63,6 @@ namespace Emby.Server.Implementations xml, environment, certificate, - new StreamFactory(), GetParseFn, enableDualModeSockets, fileSystem); @@ -74,37 +73,4 @@ namespace Emby.Server.Implementations return s => JsvReader.GetParseFn(propertyType)(s); } } - - public class StreamFactory : IStreamFactory - { - public Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket) - { - var netSocket = (NetAcceptSocket)acceptSocket; - - return new SocketStream(netSocket.Socket, ownsSocket); - } - - public Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate) - { - var sslStream = (SslStream)stream; - var cert = (Certificate)certificate; - - return sslStream.AuthenticateAsServerAsync(cert.X509Certificate); - } - - public Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen) - { - return new SslStream(innerStream, leaveInnerStreamOpen); - } - } - - public class Certificate : ICertificate - { - public Certificate(X509Certificate x509Certificate) - { - X509Certificate = x509Certificate; - } - - public X509Certificate X509Certificate { get; private set; } - } } diff --git a/Emby.Server.Implementations/IO/AsyncStreamCopier.cs b/Emby.Server.Implementations/IO/AsyncStreamCopier.cs deleted file mode 100644 index 9e5ce0604..000000000 --- a/Emby.Server.Implementations/IO/AsyncStreamCopier.cs +++ /dev/null @@ -1,459 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.IO -{ - public class AsyncStreamCopier : IDisposable - { - // size in bytes of the buffers in the buffer pool - private const int DefaultBufferSize = 81920; - private readonly int _bufferSize; - // number of buffers in the pool - private const int DefaultBufferCount = 4; - private readonly int _bufferCount; - - // indexes of the next buffer to read into/write from - private int _nextReadBuffer = -1; - private int _nextWriteBuffer = -1; - - // the buffer pool, implemented as an array, and used in a cyclic way - private readonly byte[][] _buffers; - // sizes in bytes of the available (read) data in the buffers - private readonly int[] _sizes; - // the streams... - private Stream _source; - private Stream _target; - private readonly bool _closeStreamsOnEnd; - - // number of buffers that are ready to be written - private int _buffersToWrite; - // flag indicating that there is still a read operation to be scheduled - // (source end of stream not reached) - private volatile bool _moreDataToRead; - // the result of the whole operation, returned by BeginCopy() - private AsyncResult _asyncResult; - // any exception that occurs during an async operation - // stored here for rethrow - private Exception _exception; - - public TaskCompletionSource<long> TaskCompletionSource; - private long _bytesToRead; - private long _totalBytesWritten; - private CancellationToken _cancellationToken; - public int IndividualReadOffset = 0; - - public AsyncStreamCopier(Stream source, - Stream target, - long bytesToRead, - CancellationToken cancellationToken, - bool closeStreamsOnEnd = false, - int bufferSize = DefaultBufferSize, - int bufferCount = DefaultBufferCount) - { - if (source == null) - throw new ArgumentNullException("source"); - if (target == null) - throw new ArgumentNullException("target"); - if (!source.CanRead) - throw new ArgumentException("Cannot copy from a non-readable stream."); - if (!target.CanWrite) - throw new ArgumentException("Cannot copy to a non-writable stream."); - _source = source; - _target = target; - _moreDataToRead = true; - _closeStreamsOnEnd = closeStreamsOnEnd; - _bufferSize = bufferSize; - _bufferCount = bufferCount; - _buffers = new byte[_bufferCount][]; - _sizes = new int[_bufferCount]; - _bytesToRead = bytesToRead; - _cancellationToken = cancellationToken; - } - - ~AsyncStreamCopier() - { - // ensure any exception cannot be ignored - ThrowExceptionIfNeeded(); - } - - public static Task<long> CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, CancellationToken cancellationToken) - { - return CopyStream(source, target, 0, bufferSize, bufferCount, cancellationToken); - } - - public static Task<long> CopyStream(Stream source, Stream target, long size, int bufferSize, int bufferCount, CancellationToken cancellationToken) - { - var copier = new AsyncStreamCopier(source, target, size, cancellationToken, false, bufferSize, bufferCount); - var taskCompletion = new TaskCompletionSource<long>(); - - copier.TaskCompletionSource = taskCompletion; - - var result = copier.BeginCopy(StreamCopyCallback, copier); - - if (result.CompletedSynchronously) - { - StreamCopyCallback(result); - } - - cancellationToken.Register(() => taskCompletion.TrySetCanceled()); - - return taskCompletion.Task; - } - - private static void StreamCopyCallback(IAsyncResult result) - { - var copier = (AsyncStreamCopier)result.AsyncState; - var taskCompletion = copier.TaskCompletionSource; - - try - { - copier.EndCopy(result); - taskCompletion.TrySetResult(copier._totalBytesWritten); - } - catch (Exception ex) - { - taskCompletion.TrySetException(ex); - } - } - - public void Dispose() - { - if (_asyncResult != null) - _asyncResult.Dispose(); - if (_closeStreamsOnEnd) - { - if (_source != null) - { - _source.Dispose(); - _source = null; - } - if (_target != null) - { - _target.Dispose(); - _target = null; - } - } - GC.SuppressFinalize(this); - ThrowExceptionIfNeeded(); - } - - public IAsyncResult BeginCopy(AsyncCallback callback, object state) - { - // avoid concurrent start of the copy on separate threads - if (Interlocked.CompareExchange(ref _asyncResult, new AsyncResult(callback, state), null) != null) - throw new InvalidOperationException("A copy operation has already been started on this object."); - // allocate buffers - for (int i = 0; i < _bufferCount; i++) - _buffers[i] = new byte[_bufferSize]; - - // we pass false to BeginRead() to avoid completing the async result - // immediately which would result in invoking the callback - // when the method fails synchronously - BeginRead(false); - // throw exception synchronously if there is one - ThrowExceptionIfNeeded(); - return _asyncResult; - } - - public void EndCopy(IAsyncResult ar) - { - if (ar != _asyncResult) - throw new InvalidOperationException("Invalid IAsyncResult object."); - - if (!_asyncResult.IsCompleted) - _asyncResult.AsyncWaitHandle.WaitOne(); - - if (_closeStreamsOnEnd) - { - _source.Close(); - _source = null; - _target.Close(); - _target = null; - } - - //_logger.Info("AsyncStreamCopier {0} bytes requested. {1} bytes transferred", _bytesToRead, _totalBytesWritten); - ThrowExceptionIfNeeded(); - } - - /// <summary> - /// Here we'll throw a pending exception if there is one, - /// and remove it from our instance, so we know it has been consumed. - /// </summary> - private void ThrowExceptionIfNeeded() - { - if (_exception != null) - { - var exception = _exception; - _exception = null; - throw exception; - } - } - - private void BeginRead(bool completeOnError = true) - { - if (!_moreDataToRead) - { - return; - } - if (_asyncResult.IsCompleted) - return; - int bufferIndex = Interlocked.Increment(ref _nextReadBuffer) % _bufferCount; - - try - { - _source.BeginRead(_buffers[bufferIndex], 0, _bufferSize, EndRead, bufferIndex); - } - catch (Exception exception) - { - _exception = exception; - if (completeOnError) - _asyncResult.Complete(false); - } - } - - private void BeginWrite() - { - if (_asyncResult.IsCompleted) - return; - // this method can actually be called concurrently!! - // indeed, let's say we call a BeginWrite, and the thread gets interrupted - // just after making the IO request. - // At that moment, the thread is still in the method. And then the IO request - // ends (extremely fast io, or caching...), EndWrite gets called - // on another thread, and calls BeginWrite again! There we have it! - // That is the reason why an Interlocked is needed here. - int bufferIndex = Interlocked.Increment(ref _nextWriteBuffer) % _bufferCount; - - try - { - int bytesToWrite; - if (_bytesToRead > 0) - { - var bytesLeftToWrite = _bytesToRead - _totalBytesWritten; - bytesToWrite = Convert.ToInt32(Math.Min(_sizes[bufferIndex], bytesLeftToWrite)); - } - else - { - bytesToWrite = _sizes[bufferIndex]; - } - - _target.BeginWrite(_buffers[bufferIndex], IndividualReadOffset, bytesToWrite - IndividualReadOffset, EndWrite, null); - - _totalBytesWritten += bytesToWrite; - } - catch (Exception exception) - { - _exception = exception; - _asyncResult.Complete(false); - } - } - - private void EndRead(IAsyncResult ar) - { - try - { - int read = _source.EndRead(ar); - _moreDataToRead = read > 0; - var bufferIndex = (int)ar.AsyncState; - _sizes[bufferIndex] = read; - } - catch (Exception exception) - { - _exception = exception; - _asyncResult.Complete(false); - return; - } - - if (_moreDataToRead && !_cancellationToken.IsCancellationRequested) - { - int usedBuffers = Interlocked.Increment(ref _buffersToWrite); - // if we incremented from zero to one, then it means we just - // added the single buffer to write, so a writer could not - // be busy, and we have to schedule one. - if (usedBuffers == 1) - BeginWrite(); - // test if there is at least a free buffer, and schedule - // a read, as we have read some data - if (usedBuffers < _bufferCount) - BeginRead(); - } - else - { - // we did not add a buffer, because no data was read, and - // there is no buffer left to write so this is the end... - if (Thread.VolatileRead(ref _buffersToWrite) == 0) - { - _asyncResult.Complete(false); - } - } - } - - private void EndWrite(IAsyncResult ar) - { - try - { - _target.EndWrite(ar); - } - catch (Exception exception) - { - _exception = exception; - _asyncResult.Complete(false); - return; - } - - int buffersLeftToWrite = Interlocked.Decrement(ref _buffersToWrite); - // no reader could be active if all buffers were full of data waiting to be written - bool noReaderIsBusy = buffersLeftToWrite == _bufferCount - 1; - // note that it is possible that both a reader and - // a writer see the end of the copy and call Complete - // on the _asyncResult object. That race condition is handled by - // Complete that ensures it is only executed fully once. - - long bytesLeftToWrite; - if (_bytesToRead > 0) - { - bytesLeftToWrite = _bytesToRead - _totalBytesWritten; - } - else - { - bytesLeftToWrite = 1; - } - - if (!_moreDataToRead || bytesLeftToWrite <= 0 || _cancellationToken.IsCancellationRequested) - { - // at this point we know no reader can schedule a read or write - if (Thread.VolatileRead(ref _buffersToWrite) == 0) - { - // nothing left to write, so it is the end - _asyncResult.Complete(false); - return; - } - } - else - // here, we know we have something left to read, - // so schedule a read if no read is busy - if (noReaderIsBusy) - BeginRead(); - - // also schedule a write if we are sure we did not write the last buffer - // note that if buffersLeftToWrite is zero and a reader has put another - // buffer to write between the time we decremented _buffersToWrite - // and now, that reader will also schedule another write, - // as it will increment _buffersToWrite from zero to one - if (buffersLeftToWrite > 0) - BeginWrite(); - } - } - - internal class AsyncResult : IAsyncResult, IDisposable - { - // Fields set at construction which never change while - // operation is pending - private readonly AsyncCallback _asyncCallback; - private readonly object _asyncState; - - // Fields set at construction which do change after - // operation completes - private const int StatePending = 0; - private const int StateCompletedSynchronously = 1; - private const int StateCompletedAsynchronously = 2; - private int _completedState = StatePending; - - // Field that may or may not get set depending on usage - private ManualResetEvent _waitHandle; - - internal AsyncResult( - AsyncCallback asyncCallback, - object state) - { - _asyncCallback = asyncCallback; - _asyncState = state; - } - - internal bool Complete(bool completedSynchronously) - { - bool result = false; - - // The _completedState field MUST be set prior calling the callback - int prevState = Interlocked.CompareExchange(ref _completedState, - completedSynchronously ? StateCompletedSynchronously : - StateCompletedAsynchronously, StatePending); - if (prevState == StatePending) - { - // If the event exists, set it - if (_waitHandle != null) - _waitHandle.Set(); - - if (_asyncCallback != null) - _asyncCallback(this); - - result = true; - } - - return result; - } - - #region Implementation of IAsyncResult - - public Object AsyncState { get { return _asyncState; } } - - public bool CompletedSynchronously - { - get - { - return Thread.VolatileRead(ref _completedState) == - StateCompletedSynchronously; - } - } - - public WaitHandle AsyncWaitHandle - { - get - { - if (_waitHandle == null) - { - bool done = IsCompleted; - var mre = new ManualResetEvent(done); - if (Interlocked.CompareExchange(ref _waitHandle, - mre, null) != null) - { - // Another thread created this object's event; dispose - // the event we just created - mre.Close(); - } - else - { - if (!done && IsCompleted) - { - // If the operation wasn't done when we created - // the event but now it is done, set the event - _waitHandle.Set(); - } - } - } - return _waitHandle; - } - } - - public bool IsCompleted - { - get - { - return Thread.VolatileRead(ref _completedState) != - StatePending; - } - } - #endregion - - public void Dispose() - { - if (_waitHandle != null) - { - _waitHandle.Dispose(); - _waitHandle = null; - } - } - } -} diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 0ec62d895..315bee103 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -209,7 +209,7 @@ namespace Emby.Server.Implementations.IO // If the item has been deleted find the first valid parent that still exists while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) { - item = item.GetParent(); + item = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); if (item == null) { @@ -238,6 +238,7 @@ namespace Emby.Server.Implementations.IO { _disposed = true; DisposeTimer(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/IsoManager.cs b/Emby.Server.Implementations/IO/IsoManager.cs index 903d5f301..dc0b9e122 100644 --- a/Emby.Server.Implementations/IO/IsoManager.cs +++ b/Emby.Server.Implementations/IO/IsoManager.cs @@ -70,6 +70,7 @@ namespace Emby.Server.Implementations.IO { mounter.Dispose(); } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 3994e2b00..56b10a7e6 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -649,6 +649,7 @@ namespace Emby.Server.Implementations.IO public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 57e42985d..eab52e5e8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -14,10 +14,10 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; -using MediaBrowser.Naming.Audio; -using MediaBrowser.Naming.Common; -using MediaBrowser.Naming.TV; -using MediaBrowser.Naming.Video; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Emby.Naming.TV; +using Emby.Naming.Video; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -38,7 +38,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Library; using MediaBrowser.Model.Net; using SortOrder = MediaBrowser.Model.Entities.SortOrder; -using VideoResolver = MediaBrowser.Naming.Video.VideoResolver; +using VideoResolver = Emby.Naming.Video.VideoResolver; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dto; @@ -386,7 +386,7 @@ namespace Emby.Server.Implementations.Library item.Id); } - var parent = item.Parent; + var parent = item.IsOwnedItem ? item.GetOwner() : item.GetParent(); var locationType = item.LocationType; @@ -453,12 +453,28 @@ namespace Emby.Server.Implementations.Library if (parent != null) { - await parent.ValidateChildren(new SimpleProgress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false); + var parentFolder = parent as Folder; + if (parentFolder != null) + { + await parentFolder.ValidateChildren(new SimpleProgress<double>(), CancellationToken.None, new MetadataRefreshOptions(_fileSystem), false).ConfigureAwait(false); + } + else + { + await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); + } } } else if (parent != null) { - parent.RemoveChild(item); + var parentFolder = parent as Folder; + if (parentFolder != null) + { + parentFolder.RemoveChild(item); + } + else + { + await parent.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), CancellationToken.None).ConfigureAwait(false); + } } ItemRepository.DeleteItem(item.Id, CancellationToken.None); @@ -620,37 +636,12 @@ namespace Emby.Server.Implementations.Library return ResolveItem(args, resolvers); } - private readonly List<string> _ignoredPaths = new List<string>(); - - public void RegisterIgnoredPath(string path) - { - lock (_ignoredPaths) - { - _ignoredPaths.Add(path); - } - } - public void UnRegisterIgnoredPath(string path) - { - lock (_ignoredPaths) - { - _ignoredPaths.Remove(path); - } - } - public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) { if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent))) { return true; } - - //lock (_ignoredPaths) - { - if (_ignoredPaths.Contains(file.FullName, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - } return false; } @@ -846,8 +837,7 @@ namespace Emby.Server.Implementations.Library { Path = path, IsFolder = isFolder, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -1777,6 +1767,37 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<Tuple<string, SortOrder>> orderByList) + { + var isFirst = true; + + IOrderedEnumerable<BaseItem> orderedItems = null; + + foreach (var orderBy in orderByList) + { + var comparer = GetComparer(orderBy.Item1, user); + if (comparer == null) + { + continue; + } + + var sortOrder = orderBy.Item2; + + if (isFirst) + { + orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer); + } + else + { + orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, comparer) : orderedItems.ThenBy(i => i, comparer); + } + + isFirst = false; + } + + return orderedItems ?? items; + } + /// <summary> /// Gets the comparer. /// </summary> @@ -2341,7 +2362,7 @@ namespace Emby.Server.Implementations.Library public bool IsVideoFile(string path, LibraryOptions libraryOptions) { - var resolver = new VideoResolver(GetNamingOptions(), new NullLogger()); + var resolver = new VideoResolver(GetNamingOptions()); return resolver.IsVideoFile(path); } @@ -2368,8 +2389,7 @@ namespace Emby.Server.Implementations.Library public bool FillMissingEpisodeNumbersFromPath(Episode episode) { - var resolver = new EpisodeResolver(GetNamingOptions(), - new NullLogger()); + var resolver = new EpisodeResolver(GetNamingOptions()); var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; @@ -2377,11 +2397,11 @@ namespace Emby.Server.Implementations.Library var episodeInfo = locationType == LocationType.FileSystem || locationType == LocationType.Offline ? resolver.Resolve(episode.Path, isFolder) : - new MediaBrowser.Naming.TV.EpisodeInfo(); + new Emby.Naming.TV.EpisodeInfo(); if (episodeInfo == null) { - episodeInfo = new MediaBrowser.Naming.TV.EpisodeInfo(); + episodeInfo = new Emby.Naming.TV.EpisodeInfo(); } var changed = false; @@ -2552,7 +2572,7 @@ namespace Emby.Server.Implementations.Library public ItemLookupInfo ParseName(string name) { - var resolver = new VideoResolver(GetNamingOptions(), new NullLogger()); + var resolver = new VideoResolver(GetNamingOptions()); var result = resolver.CleanDateTime(name); var cleanName = resolver.CleanString(result.Name); @@ -2573,7 +2593,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); - var videoListResolver = new VideoListResolver(namingOptions, new NullLogger()); + var videoListResolver = new VideoListResolver(namingOptions); var videos = videoListResolver.Resolve(fileSystemChildren); @@ -2600,8 +2620,11 @@ namespace Emby.Server.Implementations.Library { video = dbItem; } - - video.ExtraType = ExtraType.Trailer; + else + { + // item is new + video.ExtraType = ExtraType.Trailer; + } video.TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer }; return video; @@ -2621,7 +2644,7 @@ namespace Emby.Server.Implementations.Library .SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .ToList(); - var videoListResolver = new VideoListResolver(namingOptions, new NullLogger()); + var videoListResolver = new VideoListResolver(namingOptions); var videos = videoListResolver.Resolve(fileSystemChildren); @@ -2747,7 +2770,7 @@ namespace Emby.Server.Implementations.Library private void SetExtraTypeFromFilename(Video item) { - var resolver = new ExtraResolver(GetNamingOptions(), new NullLogger(), new RegexProvider()); + var resolver = new ExtraResolver(GetNamingOptions(), new RegexProvider()); var result = resolver.GetExtraInfo(item.Path); @@ -2842,13 +2865,6 @@ namespace Emby.Server.Implementations.Library await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false); - var newImage = item.GetImageInfo(image.Type, imageIndex); - - if (newImage != null) - { - newImage.IsPlaceholder = image.IsPlaceholder; - } - await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false); return item.GetImageInfo(image.Type, imageIndex); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 6e0489d88..d60a04353 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -524,6 +524,7 @@ namespace Emby.Server.Implementations.Library public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } private readonly object _disposeLock = new object(); diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 7f77170ad..1cbf4235a 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Library @@ -88,7 +89,7 @@ namespace Emby.Server.Implementations.Library Limit = 200, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new [] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, DtoOptions = dtoOptions diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 70fe8d9ef..b8ec41805 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -4,7 +4,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Naming.Audio; +using Emby.Naming.Audio; using System; using System.Collections.Generic; using System.IO; @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); - var parser = new AlbumParser(namingOptions, new NullLogger()); + var parser = new AlbumParser(namingOptions); var result = parser.ParseMultiPart(path); return result.IsMultiPart; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index fa4f026f4..e3200a099 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; -using MediaBrowser.Naming.Video; +using Emby.Naming.Video; using System; using System.IO; using System.Linq; @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library.Resolvers var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); // If the path is a file check for a matching extensions - var parser = new MediaBrowser.Naming.Video.VideoResolver(namingOptions, new NullLogger()); + var parser = new Emby.Naming.Video.VideoResolver(namingOptions); if (args.IsDirectory) { @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - var resolver = new Format3DParser(namingOptions, new NullLogger()); + var resolver = new Format3DParser(namingOptions); var result = resolver.Parse(video.Path); Set3DFormat(video, result.Is3D, result.Format3D); diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 1e5c0beed..cd1264754 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -6,7 +6,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; -using MediaBrowser.Naming.Video; +using Emby.Naming.Video; using System; using System.Collections.Generic; using System.IO; @@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - var resolver = new VideoListResolver(namingOptions, new NullLogger()); + var resolver = new VideoListResolver(namingOptions); var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList(); var result = new MultiItemResolverResult @@ -490,7 +490,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies } var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); - var resolver = new StackResolver(namingOptions, new NullLogger()); + var resolver = new StackResolver(namingOptions); var result = resolver.ResolveDirectories(folderPaths); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 830bd9d85..bbe1bba85 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -3,8 +3,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Globalization; -using MediaBrowser.Naming.Common; -using MediaBrowser.Naming.TV; +using Emby.Naming.Common; +using Emby.Naming.TV; namespace Emby.Server.Implementations.Library.Resolvers.TV { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index e1c18c913..a693e3b26 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -4,8 +4,8 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; -using MediaBrowser.Naming.Common; -using MediaBrowser.Naming.TV; +using Emby.Naming.Common; +using Emby.Naming.TV; using System; using System.Collections.Generic; using System.IO; @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var allowOptimisticEpisodeDetection = isTvContentType; var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(allowOptimisticEpisodeDetection); - var episodeResolver = new MediaBrowser.Naming.TV.EpisodeResolver(namingOptions, new NullLogger()); + var episodeResolver = new Emby.Naming.TV.EpisodeResolver(namingOptions); var episodeInfo = episodeResolver.Resolve(fullName, false, false); if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue) { @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV } } - logger.Debug("{0} is not a series folder.", path); + //logger.Debug("{0} is not a series folder.", path); return false; } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index d4c4f2794..b1ed034ca 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Extensions; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Library @@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.Library Limit = query.Limit, IncludeItemsByName = string.IsNullOrWhiteSpace(query.ParentId), ParentId = string.IsNullOrWhiteSpace(query.ParentId) ? (Guid?)null : new Guid(query.ParentId), - SortBy = new[] { ItemSortBy.SortName }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }, Recursive = true, IsKids = query.IsKids, diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index e5fe2969f..0f48ff46b 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -180,11 +180,6 @@ namespace Emby.Server.Implementations.Library } } - public Task<User> AuthenticateUser(string username, string passwordSha1, string remoteEndPoint) - { - return AuthenticateUser(username, passwordSha1, null, remoteEndPoint); - } - public bool IsValidUsername(string username) { // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) @@ -223,7 +218,7 @@ namespace Emby.Server.Implementations.Library return builder.ToString(); } - public async Task<User> AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint) + public async Task<User> AuthenticateUser(string username, string password, string hashedPassword, string passwordMd5, string remoteEndPoint) { if (string.IsNullOrWhiteSpace(username)) { @@ -237,23 +232,23 @@ namespace Emby.Server.Implementations.Library if (user != null) { + if (password != null) + { + hashedPassword = GetHashedString(user, password); + } + // Authenticate using local credentials if not a guest if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest) { - success = string.Equals(GetPasswordHash(user), passwordSha1.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - success = string.Equals(GetLocalPasswordHash(user), passwordSha1.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } + success = AuthenticateLocalUser(user, password, hashedPassword, remoteEndPoint); } // Maybe user accidently entered connect credentials. let's be flexible - if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(passwordMd5) && !string.IsNullOrWhiteSpace(user.ConnectUserName)) + if (!success && user.ConnectLinkType.HasValue && !string.IsNullOrWhiteSpace(user.ConnectUserName)) { try { - await _connectFactory().Authenticate(user.ConnectUserName, passwordMd5).ConfigureAwait(false); + await _connectFactory().Authenticate(user.ConnectUserName, password, passwordMd5).ConfigureAwait(false); success = true; } catch @@ -268,7 +263,7 @@ namespace Emby.Server.Implementations.Library { try { - var connectAuthResult = await _connectFactory().Authenticate(username, passwordMd5).ConfigureAwait(false); + var connectAuthResult = await _connectFactory().Authenticate(username, password, passwordMd5).ConfigureAwait(false); user = Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectAuthResult.User.Id, StringComparison.OrdinalIgnoreCase)); @@ -307,6 +302,36 @@ namespace Emby.Server.Implementations.Library return success ? user : null; } + private bool AuthenticateLocalUser(User user, string password, string hashedPassword, string remoteEndPoint) + { + bool success; + + if (password == null) + { + // legacy + success = string.Equals(GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + if (password == null) + { + // legacy + success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetLocalPasswordHash(user), GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + } + + return success; + } + private void UpdateInvalidLoginAttemptCount(User user, int newValue) { if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0) @@ -342,29 +367,39 @@ namespace Emby.Server.Implementations.Library private string GetPasswordHash(User user) { return string.IsNullOrEmpty(user.Password) - ? GetSha1String(string.Empty) + ? GetEmptyHashedString(user) : user.Password; } private string GetLocalPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) - ? GetSha1String(string.Empty) + ? GetEmptyHashedString(user) : user.EasyPassword; } - private bool IsPasswordEmpty(string passwordHash) + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + private string GetEmptyHashedString(User user) { - return string.Equals(passwordHash, GetSha1String(string.Empty), StringComparison.OrdinalIgnoreCase); + return GetHashedString(user, string.Empty); } /// <summary> - /// Gets the sha1 string. + /// Gets the hashed string. /// </summary> - /// <param name="str">The STR.</param> - /// <returns>System.String.</returns> - private string GetSha1String(string str) + private string GetHashedString(User user, string str) { + var salt = user.Salt; + if (salt != null) + { + // return BCrypt.HashPassword(str, salt); + } + + // legacy return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); } @@ -407,8 +442,8 @@ namespace Emby.Server.Implementations.Library var passwordHash = GetPasswordHash(user); - var hasConfiguredPassword = !IsPasswordEmpty(passwordHash); - var hasConfiguredEasyPassword = !IsPasswordEmpty(GetLocalPasswordHash(user)); + var hasConfiguredPassword = !IsPasswordEmpty(user, passwordHash); + var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : @@ -460,14 +495,6 @@ namespace Emby.Server.Implementations.Library { var dto = GetUserDto(user); - var offlinePasswordHash = GetLocalPasswordHash(user); - dto.HasPassword = !IsPasswordEmpty(offlinePasswordHash); - - dto.OfflinePasswordSalt = Guid.NewGuid().ToString("N"); - - // Hash the pin with the device Id to create a unique result for this device - dto.OfflinePassword = GetSha1String((offlinePasswordHash + dto.OfflinePasswordSalt).ToLower()); - dto.ServerName = _appHost.FriendlyName; return dto; @@ -491,11 +518,12 @@ namespace Emby.Server.Implementations.Library /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public Task RefreshUsersMetadata(CancellationToken cancellationToken) + public async Task RefreshUsersMetadata(CancellationToken cancellationToken) { - var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken)).ToList(); - - return Task.WhenAll(tasks); + foreach (var user in Users) + { + await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken).ConfigureAwait(false); + } } /// <summary> @@ -666,8 +694,7 @@ namespace Emby.Server.Implementations.Library DeleteUserPolicy(user); - // Force this to be lazy loaded again - Users = LoadUsers(); + Users = allUsers.Where(i => i.Id != user.Id).ToList(); OnUserDeleted(user); } @@ -683,23 +710,29 @@ namespace Emby.Server.Implementations.Library /// <returns>Task.</returns> public void ResetPassword(User user) { - ChangePassword(user, GetSha1String(string.Empty)); + ChangePassword(user, string.Empty, null); } public void ResetEasyPassword(User user) { - ChangeEasyPassword(user, GetSha1String(string.Empty)); + ChangeEasyPassword(user, string.Empty, null); } - public void ChangePassword(User user, string newPasswordSha1) + public void ChangePassword(User user, string newPassword, string newPasswordHash) { if (user == null) { throw new ArgumentNullException("user"); } - if (string.IsNullOrWhiteSpace(newPasswordSha1)) + + if (newPassword != null) { - throw new ArgumentNullException("newPasswordSha1"); + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException("newPasswordHash"); } if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) @@ -707,25 +740,31 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException("Passwords for guests cannot be changed."); } - user.Password = newPasswordSha1; + user.Password = newPasswordHash; UpdateUser(user); EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger); } - public void ChangeEasyPassword(User user, string newPasswordSha1) + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { if (user == null) { throw new ArgumentNullException("user"); } - if (string.IsNullOrWhiteSpace(newPasswordSha1)) + + if (newPassword != null) + { + newPasswordHash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) { - throw new ArgumentNullException("newPasswordSha1"); + throw new ArgumentNullException("newPasswordHash"); } - user.EasyPassword = newPasswordSha1; + user.EasyPassword = newPasswordHash; UpdateUser(user); @@ -745,7 +784,8 @@ namespace Emby.Server.Implementations.Library Id = Guid.NewGuid(), DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() }; } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index b02c114bb..8c9377291 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -319,8 +319,7 @@ namespace Emby.Server.Implementations.Library var query = new InternalItemsQuery(user) { IncludeItemTypes = includeItemTypes, - SortOrder = SortOrder.Descending, - SortBy = new[] { ItemSortBy.DateCreated }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null, ExcludeItemTypes = excludeItemTypes, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 2a2e1886f..f0578d9ef 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile)); + using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { onStarted(); @@ -76,6 +78,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _logger.Info("Opened recording stream from tuner provider"); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(targetFile)); + using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { onStarted(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 2e12f46bf..1975a6b01 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -305,26 +305,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var seriesTimers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); - List<ChannelInfo> channels = null; - foreach (var timer in seriesTimers) { - List<ProgramInfo> epgData; - - if (timer.RecordAnyChannel) - { - if (channels == null) - { - channels = (await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false)).ToList(); - } - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(timer.ChannelId); - } - await UpdateTimersForSeriesTimer(epgData, timer, false, true).ConfigureAwait(false); + await UpdateTimersForSeriesTimer(timer, false, true).ConfigureAwait(false); } } @@ -332,6 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false); + var tempChannelCache = new Dictionary<string, LiveTvChannel>(); + foreach (var timer in timers) { if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id)) @@ -345,15 +330,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV continue; } - var epg = GetEpgDataForChannel(timer.ChannelId); - var program = epg.FirstOrDefault(i => string.Equals(i.Id, timer.ProgramId, StringComparison.OrdinalIgnoreCase)); + var program = GetProgramInfoFromCache(timer); if (program == null) { OnTimerOutOfDate(timer); continue; } - RecordingHelper.CopyProgramInfoToTimerInfo(program, timer); + CopyProgramInfoToTimerInfo(program, timer, tempChannelCache); _timerProvider.Update(timer); } } @@ -621,7 +605,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV ActiveRecordingInfo activeRecordingInfo; if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo)) - { + { activeRecordingInfo.Timer = timer; activeRecordingInfo.CancellationTokenSource.Cancel(); } @@ -672,11 +656,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV timer.Id = Guid.NewGuid().ToString("N"); - ProgramInfo programInfo = null; + LiveTvProgram programInfo = null; if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer); } if (programInfo == null) { @@ -686,7 +670,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programInfo != null) { - RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); + CopyProgramInfoToTimerInfo(programInfo, timer); } timer.IsManual = true; @@ -698,24 +682,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { info.Id = Guid.NewGuid().ToString("N"); - List<ProgramInfo> epgData; - if (info.RecordAnyChannel) - { - var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(info.ChannelId); - } - // populate info.seriesID - var program = epgData.FirstOrDefault(i => string.Equals(i.Id, info.ProgramId, StringComparison.OrdinalIgnoreCase)); + var program = GetProgramInfoFromCache(info.ProgramId); if (program != null) { - info.SeriesId = program.SeriesId; + info.SeriesId = program.ExternalSeriesId; } else { @@ -750,7 +722,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _timerProvider.AddOrUpdate(timer, false); } - await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); + await UpdateTimersForSeriesTimer(info, true, false).ConfigureAwait(false); return info.Id; } @@ -779,19 +751,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _seriesTimerProvider.Update(instance); - List<ProgramInfo> epgData; - if (instance.RecordAnyChannel) - { - var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false); - var channelIds = channels.Select(i => i.Id).ToList(); - epgData = GetEpgDataForChannels(channelIds); - } - else - { - epgData = GetEpgDataForChannel(instance.ChannelId); - } - - await UpdateTimersForSeriesTimer(epgData, instance, true, true).ConfigureAwait(false); + await UpdateTimersForSeriesTimer(instance, true, true).ConfigureAwait(false); } } @@ -962,23 +922,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll()); } - public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) - { - try - { - return await GetProgramsAsyncInternal(channelId, startDateUtc, endDateUtc, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting programs", ex); - return GetEpgDataForChannel(channelId).Where(i => i.StartDate <= endDateUtc && i.EndDate >= startDateUtc); - } - } - private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId) { if (info.EnableAllTuners) @@ -994,7 +937,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase); } - private async Task<IEnumerable<ProgramInfo>> GetProgramsAsyncInternal(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) + public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false); var channel = channels.First(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)); @@ -1037,8 +980,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programs.Count > 0) { - SaveEpgDataForChannel(channelId, programs); - return programs; } } @@ -1464,11 +1405,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException("timer"); } - ProgramInfo programInfo = null; + LiveTvProgram programInfo = null; if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer); } if (programInfo == null) { @@ -1478,8 +1419,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (programInfo != null) { - RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); - activeRecordingInfo.Program = programInfo; + CopyProgramInfoToTimerInfo(programInfo, timer); + //activeRecordingInfo.Program = programInfo; } string seriesPath = null; @@ -1488,14 +1429,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV string liveStreamId = null; - OnRecordingStatusChanged(); - try { var recorder = await GetRecorder().ConfigureAwait(false); var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); + _logger.Info("Opening recording stream from tuner provider"); var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) .ConfigureAwait(false); @@ -1509,23 +1449,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV recordPath = EnsureFileUnique(recordPath, timer.Id); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow; - _logger.Info("Beginning recording. Will record for {0} minutes.", - duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); _logger.Info("Writing file to path: " + recordPath); - _logger.Info("Opening recording stream from tuner provider"); - Action onStarted = () => + Action onStarted = async () => { timer.Status = RecordingStatus.InProgress; _timerProvider.AddOrUpdate(timer, false); - SaveRecordingMetadata(timer, recordPath, seriesPath); + await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false); TriggerRefresh(recordPath); EnforceKeepUpTo(timer, seriesPath); }; @@ -1559,7 +1496,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } TriggerRefresh(recordPath); - _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); + _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false); ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); @@ -1585,17 +1522,29 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _timerProvider.Delete(timer); } - - OnRecordingStatusChanged(); } private void TriggerRefresh(string path) { + _logger.Debug("Triggering refresh on {0}", path); + var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path)); if (item != null) { - item.ChangedExternally(); + _logger.Debug("Refreshing recording parent {0}", item.Path); + + _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem) + { + ValidateChildren = true, + RefreshPaths = new List<string> + { + path, + _fileSystem.GetDirectoryName(path), + _fileSystem.GetDirectoryName(_fileSystem.GetDirectoryName(path)) + } + + }, RefreshPriority.High); } } @@ -1603,6 +1552,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { BaseItem item = null; + var parentPath = _fileSystem.GetDirectoryName(path); + while (item == null && !string.IsNullOrEmpty(path)) { item = _libraryManager.FindByPath(path, null); @@ -1612,14 +1563,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (item != null) { - // If the item has been deleted find the first valid parent that still exists - while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path)) + if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase)) { - item = item.GetParent(); - - if (item == null) + var parentItem = item.GetParent(); + if (parentItem != null && !(parentItem is AggregateFolder)) { - break; + item = parentItem; } } } @@ -1627,14 +1576,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return item; } - private void OnRecordingStatusChanged() - { - EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs - { - - }, _logger); - } - private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath) { if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) @@ -1687,8 +1628,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery { - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, IsVirtualItem = false, IsFolder = false, Recursive = true, @@ -1810,7 +1750,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var config = GetConfiguration(); - var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); + var regInfo = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false); if (regInfo.IsValid) { @@ -2020,7 +1960,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async void SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath) + private async Task SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath) { try { @@ -2337,18 +2277,49 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) + private LiveTvProgram GetProgramInfoFromCache(string programId) { - var epgData = GetEpgDataForChannel(channelId); - return epgData.FirstOrDefault(p => string.Equals(p.Id, programId, StringComparison.OrdinalIgnoreCase)); + var query = new InternalItemsQuery + { + ItemIds = new[] { _liveTvManager.GetInternalProgramId(Name, programId).ToString("N") }, + Limit = 1, + DtoOptions = new DtoOptions() + }; + + return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault(); } - private ProgramInfo GetProgramInfoFromCache(string channelId, DateTime startDateUtc) + private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer) { - var epgData = GetEpgDataForChannel(channelId); - var startDateTicks = startDateUtc.Ticks; - // Find the first program that starts within 3 minutes - return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks); + return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId); + } + + private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId) + { + return GetProgramInfoFromCache(programId); + } + + private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc) + { + var query = new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, + Limit = 1, + DtoOptions = new DtoOptions(true) + { + EnableImages = false + }, + MinStartDate = startDateUtc.AddMinutes(-3), + MaxStartDate = startDateUtc.AddMinutes(3), + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) } + }; + + if (!string.IsNullOrWhiteSpace(channelId)) + { + query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, channelId).ToString("N") }; + } + + return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault(); } private LiveTvOptions GetConfiguration() @@ -2422,9 +2393,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) + private async Task UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) { - var allTimers = GetTimersForSeries(seriesTimer, epgData) + var allTimers = GetTimersForSeries(seriesTimer) .ToList(); var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); @@ -2521,23 +2492,160 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms) + private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer) { if (seriesTimer == null) { throw new ArgumentNullException("seriesTimer"); } - if (allPrograms == null) + + if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) { - throw new ArgumentNullException("allPrograms"); + return new List<TimerInfo>(); } - // Exclude programs that have already ended - allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow); + var query = new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, + ExternalSeriesId = seriesTimer.SeriesId, + DtoOptions = new DtoOptions(true) + { + EnableImages = false + }, + MinEndDate = DateTime.UtcNow + }; - allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); + if (!seriesTimer.RecordAnyChannel) + { + query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, seriesTimer.ChannelId).ToString("N") }; + } - return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer)); + var tempChannelCache = new Dictionary<string, LiveTvChannel>(); + + return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().Select(i => CreateTimer(i, seriesTimer, tempChannelCache)); + } + + private TimerInfo CreateTimer(LiveTvProgram parent, SeriesTimerInfo seriesTimer, Dictionary<string, LiveTvChannel> tempChannelCache) + { + string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId; + + if (string.IsNullOrWhiteSpace(channelId) && !string.IsNullOrWhiteSpace(parent.ChannelId)) + { + LiveTvChannel channel; + + if (!tempChannelCache.TryGetValue(parent.ChannelId, out channel)) + { + channel = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name }, + ItemIds = new[] { parent.ChannelId }, + DtoOptions = new DtoOptions() + + }).Cast<LiveTvChannel>().FirstOrDefault(); + + if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId)) + { + tempChannelCache[parent.ChannelId] = channel; + } + } + + if (channel != null || tempChannelCache.TryGetValue(parent.ChannelId, out channel)) + { + channelId = channel.ExternalId; + } + } + + var timer = new TimerInfo + { + ChannelId = channelId, + Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"), + StartDate = parent.StartDate, + EndDate = parent.EndDate.Value, + ProgramId = parent.ExternalId, + PrePaddingSeconds = seriesTimer.PrePaddingSeconds, + PostPaddingSeconds = seriesTimer.PostPaddingSeconds, + IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired, + IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired, + KeepUntil = seriesTimer.KeepUntil, + Priority = seriesTimer.Priority, + Name = parent.Name, + Overview = parent.Overview, + SeriesId = parent.ExternalSeriesId, + SeriesTimerId = seriesTimer.Id, + ShowId = parent.ShowId + }; + + CopyProgramInfoToTimerInfo(parent, timer, tempChannelCache); + + return timer; + } + + private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo) + { + var tempChannelCache = new Dictionary<string, LiveTvChannel>(); + CopyProgramInfoToTimerInfo(programInfo, timerInfo, tempChannelCache); + } + + private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo, Dictionary<string, LiveTvChannel> tempChannelCache) + { + string channelId = null; + + if (!string.IsNullOrWhiteSpace(programInfo.ChannelId)) + { + LiveTvChannel channel; + + if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out channel)) + { + channel = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name }, + ItemIds = new[] { programInfo.ChannelId }, + DtoOptions = new DtoOptions() + + }).Cast<LiveTvChannel>().FirstOrDefault(); + + if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId)) + { + tempChannelCache[programInfo.ChannelId] = channel; + } + } + + if (channel != null || tempChannelCache.TryGetValue(programInfo.ChannelId, out channel)) + { + channelId = channel.ExternalId; + } + } + + timerInfo.Name = programInfo.Name; + timerInfo.StartDate = programInfo.StartDate; + timerInfo.EndDate = programInfo.EndDate.Value; + + if (!string.IsNullOrWhiteSpace(channelId)) + { + timerInfo.ChannelId = channelId; + } + + timerInfo.SeasonNumber = programInfo.ParentIndexNumber; + timerInfo.EpisodeNumber = programInfo.IndexNumber; + timerInfo.IsMovie = programInfo.IsMovie; + timerInfo.IsKids = programInfo.IsKids; + timerInfo.IsNews = programInfo.IsNews; + timerInfo.IsSports = programInfo.IsSports; + timerInfo.ProductionYear = programInfo.ProductionYear; + timerInfo.EpisodeTitle = programInfo.EpisodeTitle; + timerInfo.OriginalAirDate = programInfo.PremiereDate; + timerInfo.IsProgramSeries = programInfo.IsSeries; + + timerInfo.IsSeries = programInfo.IsSeries; + timerInfo.IsLive = programInfo.IsLive; + timerInfo.IsPremiere = programInfo.IsPremiere; + + timerInfo.HomePageUrl = programInfo.HomePageUrl; + timerInfo.CommunityRating = programInfo.CommunityRating; + timerInfo.Overview = programInfo.Overview; + timerInfo.OfficialRating = programInfo.OfficialRating; + timerInfo.IsRepeat = programInfo.IsRepeat; + timerInfo.SeriesId = programInfo.ExternalSeriesId; } private bool IsProgramAlreadyInLibrary(TimerInfo program) @@ -2578,51 +2686,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return false; } - private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms) - { - if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) - { - _logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series"); - return new List<ProgramInfo>(); - } - - return allPrograms.Where(i => string.Equals(i.SeriesId, seriesTimer.SeriesId, StringComparison.OrdinalIgnoreCase)); - } - - private string GetChannelEpgCachePath(string channelId) - { - return Path.Combine(_config.CommonApplicationPaths.CachePath, "embytvepg", channelId + ".json"); - } - - private readonly object _epgLock = new object(); - private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData) - { - var path = GetChannelEpgCachePath(channelId); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); - lock (_epgLock) - { - _jsonSerializer.SerializeToFile(epgData, path); - } - } - private List<ProgramInfo> GetEpgDataForChannel(string channelId) - { - try - { - lock (_epgLock) - { - return _jsonSerializer.DeserializeFromFile<List<ProgramInfo>>(GetChannelEpgCachePath(channelId)); - } - } - catch - { - return new List<ProgramInfo>(); - } - } - private List<ProgramInfo> GetEpgDataForChannels(List<string> channelIds) - { - return channelIds.SelectMany(GetEpgDataForChannel).ToList(); - } - private bool _disposed; public void Dispose() { @@ -2631,6 +2694,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { pair.Value.CancellationTokenSource.Cancel(); } + GC.SuppressFinalize(this); } public List<VirtualFolderInfo> GetRecordingFolders() diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 48eba4117..149f69e5b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -284,8 +284,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV protected string GetOutputSizeParam() { var filters = new List<string>(); - - filters.Add("yadif=0:-1:0"); + + if (string.Equals(GetEncodingOptions().DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("yadif=1:-1:0"); + } + else + { + filters.Add("yadif=0:-1:0"); + } var output = string.Empty; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs index 139cf570e..7c5f630a7 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Plugins; +using System; +using MediaBrowser.Controller.Plugins; namespace Emby.Server.Implementations.LiveTv.EmbyTV { @@ -11,6 +12,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index b5de6ef01..a5712b480 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,8 +1,6 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.LiveTv; using System; using System.Globalization; -using MediaBrowser.Model.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { @@ -13,63 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds); } - public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo seriesTimer) - { - var timer = new TimerInfo - { - ChannelId = parent.ChannelId, - Id = (seriesTimer.Id + parent.Id).GetMD5().ToString("N"), - StartDate = parent.StartDate, - EndDate = parent.EndDate, - ProgramId = parent.Id, - PrePaddingSeconds = seriesTimer.PrePaddingSeconds, - PostPaddingSeconds = seriesTimer.PostPaddingSeconds, - IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired, - IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired, - KeepUntil = seriesTimer.KeepUntil, - Priority = seriesTimer.Priority, - Name = parent.Name, - Overview = parent.Overview, - SeriesId = parent.SeriesId, - SeriesTimerId = seriesTimer.Id, - ShowId = parent.ShowId - }; - - CopyProgramInfoToTimerInfo(parent, timer); - - return timer; - } - - public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo) - { - timerInfo.Name = programInfo.Name; - timerInfo.StartDate = programInfo.StartDate; - timerInfo.EndDate = programInfo.EndDate; - timerInfo.ChannelId = programInfo.ChannelId; - - timerInfo.SeasonNumber = programInfo.SeasonNumber; - timerInfo.EpisodeNumber = programInfo.EpisodeNumber; - timerInfo.IsMovie = programInfo.IsMovie; - timerInfo.IsKids = programInfo.IsKids; - timerInfo.IsNews = programInfo.IsNews; - timerInfo.IsSports = programInfo.IsSports; - timerInfo.ProductionYear = programInfo.ProductionYear; - timerInfo.EpisodeTitle = programInfo.EpisodeTitle; - timerInfo.OriginalAirDate = programInfo.OriginalAirDate; - timerInfo.IsProgramSeries = programInfo.IsSeries; - - timerInfo.IsSeries = programInfo.IsSeries; - timerInfo.IsLive = programInfo.IsLive; - timerInfo.IsPremiere = programInfo.IsPremiere; - - timerInfo.HomePageUrl = programInfo.HomePageUrl; - timerInfo.CommunityRating = programInfo.CommunityRating; - timerInfo.Overview = programInfo.Overview; - timerInfo.OfficialRating = programInfo.OfficialRating; - timerInfo.IsRepeat = programInfo.IsRepeat; - timerInfo.SeriesId = programInfo.SeriesId; - } - public static string GetRecordingName(TimerInfo info) { var name = info.Name; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index eff2909fd..e6479feaa 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -9,14 +9,12 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.LiveTv { @@ -25,15 +23,13 @@ namespace Emby.Server.Implementations.LiveTv private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; private readonly IApplicationHost _appHost; private readonly ILibraryManager _libraryManager; - public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger, IApplicationHost appHost, ILibraryManager libraryManager) + public LiveTvDtoService(IDtoService dtoService, IImageProcessor imageProcessor, ILogger logger, IApplicationHost appHost, ILibraryManager libraryManager) { _dtoService = dtoService; - _userDataManager = userDataManager; _imageProcessor = imageProcessor; _logger = logger; _appHost = appHost; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index ac98d1043..38d2fd3c6 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.LiveTv _dtoService = dtoService; _userDataManager = userDataManager; - _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, appHost, _libraryManager); + _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, logger, appHost, _libraryManager); } /// <summary> @@ -187,7 +187,6 @@ namespace Emby.Server.Implementations.LiveTv IsSports = query.IsSports, IsSeries = query.IsSeries, IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }, - SortOrder = query.SortOrder ?? SortOrder.Ascending, TopParentIds = new[] { topFolder.Id.ToString("N") }, IsFavorite = query.IsFavorite, IsLiked = query.IsLiked, @@ -196,18 +195,22 @@ namespace Emby.Server.Implementations.LiveTv DtoOptions = dtoOptions }; - internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending))); + var orderBy = internalQuery.OrderBy.ToList(); + + orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending))); if (query.EnableFavoriteSorting) { - internalQuery.OrderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); + orderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); } if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) { - internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); + orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); } + internalQuery.OrderBy = orderBy.ToArray(); + return _libraryManager.GetItemsResult(internalQuery); } @@ -597,6 +600,12 @@ namespace Emby.Server.Implementations.LiveTv }; } + if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase)) + { + item.ShowId = info.ShowId; + forceUpdate = true; + } + var seriesId = info.SeriesId; if (!item.ParentId.Equals(channel.Id)) @@ -743,6 +752,11 @@ namespace Emby.Server.Implementations.LiveTv } } + if (isNew || isUpdated) + { + item.OnMetadataChanged(); + } + return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated); } @@ -829,8 +843,7 @@ namespace Emby.Server.Implementations.LiveTv item.SetImage(new ItemImageInfo { Path = info.ImagePath, - Type = ImageType.Primary, - IsPlaceholder = true + Type = ImageType.Primary }, 0); } else if (!string.IsNullOrWhiteSpace(info.ImageUrl)) @@ -838,8 +851,7 @@ namespace Emby.Server.Implementations.LiveTv item.SetImage(new ItemImageInfo { Path = info.ImageUrl, - Type = ImageType.Primary, - IsPlaceholder = true + Type = ImageType.Primary }, 0); } } @@ -918,10 +930,10 @@ namespace Emby.Server.Implementations.LiveTv var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); - if (query.SortBy.Length == 0) + if (query.OrderBy.Length == 0) { // Unless something else was specified, order by start date to take advantage of a specialized index - query.SortBy = new[] { ItemSortBy.StartDate }; + query.OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }; } RemoveFields(options); @@ -942,8 +954,7 @@ namespace Emby.Server.Implementations.LiveTv Genres = query.Genres, StartIndex = query.StartIndex, Limit = query.Limit, - SortBy = query.SortBy, - SortOrder = query.SortOrder ?? SortOrder.Ascending, + OrderBy = query.OrderBy, EnableTotalRecordCount = query.EnableTotalRecordCount, TopParentIds = new[] { topFolder.Id.ToString("N") }, Name = query.Name, @@ -985,8 +996,7 @@ namespace Emby.Server.Implementations.LiveTv var queryResult = _libraryManager.QueryItems(internalQuery); - var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user); var result = new QueryResult<BaseItemDto> { @@ -1013,7 +1023,7 @@ namespace Emby.Server.Implementations.LiveTv IsSports = query.IsSports, IsKids = query.IsKids, EnableTotalRecordCount = query.EnableTotalRecordCount, - SortBy = new[] { ItemSortBy.StartDate }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { topFolder.Id.ToString("N") }, DtoOptions = options }; @@ -1070,8 +1080,7 @@ namespace Emby.Server.Implementations.LiveTv var user = _userManager.GetUserById(query.UserId); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); var result = new QueryResult<BaseItemDto> { @@ -1646,8 +1655,7 @@ namespace Emby.Server.Implementations.LiveTv IsVirtualItem = false, Limit = query.Limit, StartIndex = query.StartIndex, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), @@ -1656,7 +1664,7 @@ namespace Emby.Server.Implementations.LiveTv }); } - public async Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) + public QueryResult<BaseItemDto> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); if (user != null && !IsLiveTvEnabled(user)) @@ -1694,16 +1702,14 @@ namespace Emby.Server.Implementations.LiveTv Recursive = true, AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(folders.Count), Limit = query.Limit, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) }, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), DtoOptions = options }); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); return new QueryResult<BaseItemDto> { @@ -1930,11 +1936,11 @@ namespace Emby.Server.Implementations.LiveTv var info = recording; - dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) + dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) || service == null ? null : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"); - dto.TimerId = string.IsNullOrEmpty(info.TimerId) + dto.TimerId = string.IsNullOrEmpty(info.TimerId) || service == null ? null : _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N"); @@ -2040,8 +2046,7 @@ namespace Emby.Server.Implementations.LiveTv var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user); return new QueryResult<BaseItemDto> { @@ -2368,7 +2373,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - public async Task AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> tuples, DtoOptions options, User user) + public void AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> tuples, DtoOptions options, User user) { var now = DateTime.UtcNow; @@ -2381,7 +2386,7 @@ namespace Emby.Server.Implementations.LiveTv MaxStartDate = now, MinEndDate = now, Limit = channelIds.Length, - SortBy = new[] { "StartDate" }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }, DtoOptions = options @@ -2425,7 +2430,7 @@ namespace Emby.Server.Implementations.LiveTv if (addCurrentProgram) { - var currentProgramDtos = await _dtoService.GetBaseItemDtos(currentProgramsList, options, user).ConfigureAwait(false); + var currentProgramDtos = _dtoService.GetBaseItemDtos(currentProgramsList, options, user); foreach (var programDto in currentProgramDtos) { @@ -2783,6 +2788,7 @@ namespace Emby.Server.Implementations.LiveTv public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } private bool _isDisposed = false; @@ -3146,5 +3152,15 @@ namespace Emby.Server.Implementations.LiveTv var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase)); return provider.GetChannels(info, cancellationToken); } + + public Guid GetInternalChannelId(string serviceName, string externalId) + { + return _tvDtoService.GetInternalChannelId(serviceName, externalId); + } + + public Guid GetInternalProgramId(string serviceName, string externalId) + { + return _tvDtoService.GetInternalProgramId(serviceName, externalId); + } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 0e52f874d..ed8b3074b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -142,6 +142,8 @@ namespace Emby.Server.Implementations.LiveTv var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false); stream = info.Item1; directStreamProvider = info.Item2; + + //allowLiveStreamProbe = false; } else { diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index cad28c809..225360159 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv return new[] { // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} }; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index eae29bee7..f974b5c2c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun SupportsTranscoding = true, IsInfiniteStream = true, IgnoreDts = true, - //SupportsProbing = false, + SupportsProbing = false, //AnalyzeDurationMs = 2000000 //IgnoreIndex = true, //ReadAtNativeFramerate = true diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs index d2e9c8bf0..af064755d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs @@ -26,10 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); - private readonly MulticastStream _multicastStream; - private readonly string _tempFilePath; - private bool _enableFileBuffer = false; public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(mediaSource, environment, fileSystem) @@ -39,7 +36,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _appHost = appHost; OriginalStreamId = originalStreamId; - _multicastStream = new MulticastStream(_logger); _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); } @@ -63,6 +59,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; + + //OpenedMediaSource.Path = _tempFilePath; + //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.SupportsDirectPlay = false; //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; @@ -107,19 +106,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _logger.Info("Beginning multicastStream.CopyUntilCancelled"); - if (_enableFileBuffer) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); - using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); - } - } - else - { - Resolve(openTaskCompletionSource); - - await _multicastStream.CopyUntilCancelled(response.Content, null, cancellationToken).ConfigureAwait(false); + StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken); } } } @@ -144,7 +134,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } _liveStreamTaskCompletionSource.TrySetResult(true); - //await DeleteTempFile(_tempFilePath).ConfigureAwait(false); + await DeleteTempFile(_tempFilePath).ConfigureAwait(false); }); } @@ -158,56 +148,31 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - if (_enableFileBuffer) - { - return CopyFileTo(_tempFilePath, stream, cancellationToken); - } - return _multicastStream.CopyToAsync(stream, cancellationToken); - //return CopyFileTo(_tempFilePath, stream, cancellationToken); + return CopyFileTo(_tempFilePath, stream, cancellationToken); } protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken) { long startPosition = -20000; - if (startPosition < 0) - { - var length = FileSystem.GetFileInfo(path).Length; - startPosition = Math.Max(length - startPosition, 0); - } _logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture)); - var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - using (var inputStream = GetInputStream(path, startPosition, allowAsync)) + using (var inputStream = (FileStream)GetInputStream(path, allowAsync)) { if (startPosition > 0) { - inputStream.Position = startPosition; + inputStream.Seek(-20000, SeekOrigin.End); } while (!cancellationToken.IsCancellationRequested) { - long bytesRead; - - if (allowAsync) - { - bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false); - } - else - { - StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); - bytesRead = 1; - } + StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 41b058baf..c737c4cba 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -99,6 +99,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var task = StopStreaming(); Task.WaitAll(task); + GC.SuppressFinalize(this); } public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 5ad6e2e16..6c21066fb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly INetworkManager _networkManager; private readonly string _tempFilePath; - private bool _enableFileBuffer = false; - private readonly MulticastStream _multicastStream; public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(mediaSource, environment, fileSystem) @@ -48,7 +46,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _channelCommands = channelCommands; _numTuners = numTuners; _tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts"); - _multicastStream = new MulticastStream(_logger); } protected override async Task OpenInternal(CancellationToken openCancellationToken) @@ -126,17 +123,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (!cancellationToken.IsCancellationRequested) { - if (_enableFileBuffer) + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); + using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) { - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath)); - using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None)) - { - CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken); - } - } - else - { - await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false); + CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken); } } } @@ -178,56 +168,33 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - public async Task CopyToAsync(Stream outputStream, CancellationToken cancellationToken) + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - if (!_enableFileBuffer) - { - await _multicastStream.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); - return; - } - - var path = _tempFilePath; + return CopyFileTo(_tempFilePath, stream, cancellationToken); + } + protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken) + { long startPosition = -20000; - if (startPosition < 0) - { - var length = FileSystem.GetFileInfo(path).Length; - startPosition = Math.Max(length - startPosition, 0); - } _logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture)); - var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; + var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - using (var inputStream = GetInputStream(path, startPosition, allowAsync)) + using (var inputStream = (FileStream)GetInputStream(path, allowAsync)) { if (startPosition > 0) { - inputStream.Position = startPosition; + inputStream.Seek(-20000, SeekOrigin.End); } while (!cancellationToken.IsCancellationRequested) { - long bytesRead; - - if (allowAsync) - { - bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false); - } - else - { - StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); - bytesRead = 1; - } + StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } } } } @@ -285,22 +252,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun //return taskCompletion.Task; } - private void StreamCopyCallback(IAsyncResult result) - { - var copier = (AsyncStreamCopier)result.AsyncState; - var taskCompletion = copier.TaskCompletionSource; - - try - { - copier.EndCopy(result); - taskCompletion.TrySetResult(0); - } - catch (Exception ex) - { - taskCompletion.TrySetException(ex); - } - } - public class UdpClientStream : Stream { private static int RtpHeaderBytes = 12; diff --git a/Emby.Server.Implementations/Logging/SimpleLogManager.cs b/Emby.Server.Implementations/Logging/SimpleLogManager.cs index 3a6992657..6129f38c4 100644 --- a/Emby.Server.Implementations/Logging/SimpleLogManager.cs +++ b/Emby.Server.Implementations/Logging/SimpleLogManager.cs @@ -107,6 +107,7 @@ namespace Emby.Server.Implementations.Logging } _fileLogger = null; + GC.SuppressFinalize(this); } } @@ -130,13 +131,18 @@ namespace Emby.Server.Implementations.Logging private void LogInternal() { - while (!_cancellationTokenSource.IsCancellationRequested) + while (!_cancellationTokenSource.IsCancellationRequested && !_disposed) { try { foreach (var message in _queue.GetConsumingEnumerable()) { var bytes = Encoding.UTF8.GetBytes(message + Environment.NewLine); + if (_disposed) + { + return; + } + _fileStream.Write(bytes, 0, bytes.Length); _fileStream.Flush(true); @@ -166,17 +172,18 @@ namespace Emby.Server.Implementations.Logging return; } - _fileStream.Flush(); + _fileStream.Flush(true); } public void Dispose() { _cancellationTokenSource.Cancel(); - _disposed = true; + Flush(); - _fileStream.Flush(); + _disposed = true; _fileStream.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/Emby.Server.Implementations/Net/NetAcceptSocket.cs b/Emby.Server.Implementations/Net/NetAcceptSocket.cs index 936a66c0b..d80341a07 100644 --- a/Emby.Server.Implementations/Net/NetAcceptSocket.cs +++ b/Emby.Server.Implementations/Net/NetAcceptSocket.cs @@ -89,66 +89,10 @@ namespace Emby.Server.Implementations.Net Socket.Bind(nativeEndpoint); } - private SocketAcceptor _acceptor; - public void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed) - { - _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode); - - _acceptor.StartAccept(); - } - - public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) - { - var options = TransmitFileOptions.UseDefaultWorkerThread; - - var completionSource = new TaskCompletionSource<bool>(); - - var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple<Socket, string, TaskCompletionSource<bool>>(Socket, path, completionSource)); - - return completionSource.Task; - } - - public IAsyncResult BeginSendFile(string path, byte[] preBuffer, byte[] postBuffer, AsyncCallback callback, object state) - { - var options = TransmitFileOptions.UseDefaultWorkerThread; - - return Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), state); - } - - public void EndSendFile(IAsyncResult result) - { - Socket.EndSendFile(result); - } - - private void FileSendCallback(IAsyncResult ar) - { - // Retrieve the socket from the state object. - Tuple<Socket, string, TaskCompletionSource<bool>> data = (Tuple<Socket, string, TaskCompletionSource<bool>>)ar.AsyncState; - - var client = data.Item1; - var path = data.Item2; - var taskCompletion = data.Item3; - - // Complete sending the data to the remote device. - try - { - client.EndSendFile(ar); - taskCompletion.TrySetResult(true); - } - catch (SocketException ex) - { - _logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode); - taskCompletion.TrySetException(ex); - } - catch (Exception ex) - { - taskCompletion.TrySetException(ex); - } - } - public void Dispose() { Socket.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/News/NewsEntryPoint.cs b/Emby.Server.Implementations/News/NewsEntryPoint.cs index 3c9a3bbf1..03c79c2c1 100644 --- a/Emby.Server.Implementations/News/NewsEntryPoint.cs +++ b/Emby.Server.Implementations/News/NewsEntryPoint.cs @@ -271,6 +271,7 @@ namespace Emby.Server.Implementations.News _timer.Dispose(); _timer = null; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Notifications/Notifications.cs b/Emby.Server.Implementations/Notifications/Notifications.cs index ac3cc7564..09c1f07e0 100644 --- a/Emby.Server.Implementations/Notifications/Notifications.cs +++ b/Emby.Server.Implementations/Notifications/Notifications.cs @@ -551,6 +551,7 @@ namespace Emby.Server.Implementations.Notifications _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; _userManager.UserLockedOut -= _userManager_UserLockedOut; + GC.SuppressFinalize(this); } private void DisposeLibraryUpdateTimer() diff --git a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs index 0d89ba84f..6e57e7f81 100644 --- a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs +++ b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Net; +using System; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using System.Linq; @@ -49,6 +50,7 @@ namespace Emby.Server.Implementations.Notifications public void Dispose() { _notificationsRepo.NotificationAdded -= _notificationsRepo_NotificationAdded; + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index c2e860339..36e8b4dd8 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Configuration; +using System; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.Playlists return subItem; } - var parent = subItem.GetParent(); + var parent = subItem.IsOwnedItem ? subItem.GetOwner() : subItem.GetParent(); if (parent != null && parent.HasImage(ImageType.Primary)) { @@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Playlists { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name }, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, @@ -118,7 +119,7 @@ namespace Emby.Server.Implementations.Playlists { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name }, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index ec371c741..bf7bf9ff8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -58,8 +58,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - + return new[] { + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, @@ -88,6 +88,12 @@ namespace Emby.Server.Implementations.ScheduledTasks IsFolder = false, Recursive = true, DtoOptions = new DtoOptions(false) + { + EnableImages = false + }, + SourceTypes = new SourceType[] { SourceType.Library }, + HasChapterImages = false, + IsVirtualItem = false }) .OfType<Video>() diff --git a/Emby.Server.Implementations/ServerManager/ServerManager.cs b/Emby.Server.Implementations/ServerManager/ServerManager.cs index 7cd94c526..b267f928b 100644 --- a/Emby.Server.Implementations/ServerManager/ServerManager.cs +++ b/Emby.Server.Implementations/ServerManager/ServerManager.cs @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.ServerManager /// <summary> /// Starts this instance. /// </summary> - public void Start(IEnumerable<string> urlPrefixes) + public void Start(string[] urlPrefixes) { ReloadHttpServer(urlPrefixes); } @@ -120,7 +120,7 @@ namespace Emby.Server.Implementations.ServerManager /// <summary> /// Restarts the Http Server, or starts it if not currently running /// </summary> - private void ReloadHttpServer(IEnumerable<string> urlPrefixes) + private void ReloadHttpServer(string[] urlPrefixes) { _logger.Info("Loading Http Server"); diff --git a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs index 4d5192fea..076f50d93 100644 --- a/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/Emby.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -136,7 +136,7 @@ namespace Emby.Server.Implementations.ServerManager return; } - var charset = _textEncoding.GetDetectedEncodingName(bytes, null, false); + var charset = _textEncoding.GetDetectedEncodingName(bytes, bytes.Length, null, false); if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index dfad09f7b..91314c15a 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -45,10 +45,15 @@ namespace Emby.Server.Implementations.Services var bytesResponse = this.Response as byte[]; if (bytesResponse != null) { + var contentLength = bytesResponse.Length; + if (response != null) - response.SetContentLength(bytesResponse.Length); + response.SetContentLength(contentLength); - await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false); + if (contentLength > 0) + { + await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false); + } return; } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 84dc343c3..22e1bc4aa 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -41,11 +41,11 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; response.StatusDescription = httpResult.StatusCode.ToString(); - if (string.IsNullOrEmpty(httpResult.ContentType)) - { - httpResult.ContentType = defaultContentType; - } - response.ContentType = httpResult.ContentType; + //if (string.IsNullOrEmpty(httpResult.ContentType)) + //{ + // httpResult.ContentType = defaultContentType; + //} + //response.ContentType = httpResult.ContentType; if (httpResult.Cookies != null) { @@ -124,7 +124,10 @@ namespace Emby.Server.Implementations.Services response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + if (bytes.Length > 0) + { + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + } return; } @@ -133,7 +136,10 @@ namespace Emby.Server.Implementations.Services { bytes = Encoding.UTF8.GetBytes(responseText); response.SetContentLength(bytes.Length); - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + if (bytes.Length > 0) + { + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + } return; } @@ -150,8 +156,15 @@ namespace Emby.Server.Implementations.Services serializer(result, ms); ms.Position = 0; - response.SetContentLength(ms.Length); - await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + + var contentLength = ms.Length; + + response.SetContentLength(contentLength); + + if (contentLength > 0) + { + await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + } } //serializer(result, outputStream); diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index c3970b22f..3fd6d88f8 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -75,11 +75,7 @@ namespace Emby.Server.Implementations.Services var attrs = appHost.GetRouteAttributes(requestType); foreach (RouteAttribute attr in attrs) { - var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes); - - if (!restPath.IsValid) - throw new NotSupportedException(string.Format( - "RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetMethodName())); + var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.IsHidden, attr.Summary, attr.Description); RegisterRestPath(restPath); } @@ -92,8 +88,7 @@ namespace Emby.Server.Implementations.Services if (!restPath.Path.StartsWith("/")) throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName())); if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) - throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " + - "See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetMethodName())); + throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName())); List<RestPath> pathsAtFirstMatch; if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch)) diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 4a2199c89..5709d3e0a 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Services { public static class ServiceExecExtensions { - public static HashSet<string> AllVerbs = new HashSet<string>(new[] { + public static string[] AllVerbs = new[] { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616 "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518 "VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT", @@ -22,27 +22,43 @@ namespace Emby.Server.Implementations.Services "SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/ "BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY", "POLL", "SUBSCRIBE", "UNSUBSCRIBE" - }); + }; - public static IEnumerable<MethodInfo> GetActions(this Type serviceType) + public static HashSet<string> AllVerbsSet = new HashSet<string>(AllVerbs); + + public static List<MethodInfo> GetActions(this Type serviceType) { - foreach (var mi in serviceType.GetRuntimeMethods().Where(i => i.IsPublic && !i.IsStatic)) + var list = new List<MethodInfo>(); + + foreach (var mi in serviceType.GetRuntimeMethods()) { + if (!mi.IsPublic) + { + continue; + } + + if (mi.IsStatic) + { + continue; + } + if (mi.GetParameters().Length != 1) continue; var actionName = mi.Name; - if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase) && !string.Equals(actionName, ServiceMethod.AnyAction, StringComparison.OrdinalIgnoreCase)) + if (!AllVerbs.Contains(actionName, StringComparer.OrdinalIgnoreCase)) continue; - yield return mi; + list.Add(mi); } + + return list; } } internal static class ServiceExecGeneral { - public static Dictionary<string, ServiceMethod> execMap = new Dictionary<string, ServiceMethod>(); + private static Dictionary<string, ServiceMethod> execMap = new Dictionary<string, ServiceMethod>(); public static void CreateServiceRunnersFor(Type requestType, List<ServiceMethod> actions) { @@ -59,8 +75,7 @@ namespace Emby.Server.Implementations.Services var actionName = request.Verb ?? "POST"; ServiceMethod actionContext; - if (ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out actionContext) - || ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.AnyKey(serviceType, requestName), out actionContext)) + if (ServiceExecGeneral.execMap.TryGetValue(ServiceMethod.Key(serviceType, actionName, requestName), out actionContext)) { if (actionContext.RequestFilters != null) { diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index f9fcfdbab..d500595ce 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -162,7 +162,11 @@ namespace Emby.Server.Implementations.Services if (RequireqRequestStream(requestType)) { // Used by IRequiresRequestStream - return CreateRequiresRequestStreamRequest(host, httpReq, requestType); + var request = ServiceHandler.CreateRequest(httpReq, restPath, GetRequestParams(httpReq), host.CreateInstance(requestType)); + + var rawReq = (IRequiresRequestStream)request; + rawReq.RequestStream = httpReq.InputStream; + return rawReq; } var requestParams = GetFlattenedRequestParams(httpReq); @@ -176,16 +180,6 @@ namespace Emby.Server.Implementations.Services return requiresRequestStreamTypeInfo.IsAssignableFrom(requestType.GetTypeInfo()); } - private static IRequiresRequestStream CreateRequiresRequestStreamRequest(HttpListenerHost host, IRequest req, Type requestType) - { - var restPath = GetRoute(req); - var request = ServiceHandler.CreateRequest(req, restPath, GetRequestParams(req), host.CreateInstance(requestType)); - - var rawReq = (IRequiresRequestStream)request; - rawReq.RequestStream = req.InputStream; - return rawReq; - } - public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, Dictionary<string, string> requestParams) { var requestDto = CreateContentTypeRequest(host, httpReq, restPath.RequestType, httpReq.ContentType); @@ -228,22 +222,26 @@ namespace Emby.Server.Implementations.Services } } - if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null) + if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { - foreach (var name in request.FormData.Keys) + var formData = request.FormData; + if (formData != null) { - if (name == null) continue; //thank you ASP.NET - - var values = request.FormData.GetValues(name); - if (values.Count == 1) + foreach (var name in formData.Keys) { - map[name] = values[0]; - } - else - { - for (var i = 0; i < values.Count; i++) + if (name == null) continue; //thank you ASP.NET + + var values = formData.GetValues(name); + if (values.Count == 1) + { + map[name] = values[0]; + } + else { - map[name + (i == 0 ? "" : "#" + i)] = values[i]; + for (var i = 0; i < values.Count; i++) + { + map[name + (i == 0 ? "" : "#" + i)] = values[i]; + } } } } @@ -270,12 +268,16 @@ namespace Emby.Server.Implementations.Services map[name] = request.QueryString[name]; } - if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null) + if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT"))) { - foreach (var name in request.FormData.Keys) + var formData = request.FormData; + if (formData != null) { - if (name == null) continue; //thank you ASP.NET - map[name] = request.FormData[name]; + foreach (var name in formData.Keys) + { + if (name == null) continue; //thank you ASP.NET + map[name] = formData[name]; + } } } diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index bcbc6fb57..fa2dd43d0 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -4,8 +4,6 @@ namespace Emby.Server.Implementations.Services { public class ServiceMethod { - public const string AnyAction = "ANY"; - public string Id { get; set; } public ActionInvokerFn ServiceAction { get; set; } @@ -15,10 +13,5 @@ namespace Emby.Server.Implementations.Services { return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName; } - - public static string AnyKey(Type serviceType, string requestDtoName) - { - return Key(serviceType, AnyAction, requestDtoName); - } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index da5e74f1f..0ca36df19 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -21,8 +21,6 @@ namespace Emby.Server.Implementations.Services readonly bool[] componentsWithSeparators; private readonly string restPath; - private readonly string allowedVerbs; - private readonly bool allowsAllVerbs; public bool IsWildCardPath { get; private set; } private readonly string[] literalsToMatch; @@ -46,35 +44,21 @@ namespace Emby.Server.Implementations.Services /// </summary> public int TotalComponentsCount { get; set; } - public string[] Verbs - { - get - { - return allowsAllVerbs - ? new[] { "ANY" } - : AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); - } - } + public string[] Verbs { get; private set; } public Type RequestType { get; private set; } public string Path { get { return this.restPath; } } public string Summary { get; private set; } - - public string Notes { get; private set; } - - public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } } - - public string AllowedVerbs { get { return this.allowedVerbs; } } + public string Description { get; private set; } + public bool IsHidden { get; private set; } public int Priority { get; set; } //passed back to RouteAttribute public static string[] GetPathPartsForMatching(string pathInfo) { - var parts = pathInfo.ToLower().Split(PathSeperatorChar) - .Where(x => !String.IsNullOrEmpty(x)).ToArray(); - return parts; + return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); } public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching) @@ -109,18 +93,15 @@ namespace Emby.Server.Implementations.Services return list; } - public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, string summary = null, string notes = null) + public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, bool isHidden = false, string summary = null, string description = null) { this.RequestType = requestType; this.Summary = summary; - this.Notes = notes; + this.IsHidden = isHidden; + this.Description = description; this.restPath = path; - this.allowsAllVerbs = verbs == null || String.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase); - if (!this.allowsAllVerbs) - { - this.allowedVerbs = verbs.ToUpper(); - } + this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpper().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); var componentsList = new List<string>(); @@ -153,7 +134,6 @@ namespace Emby.Server.Implementations.Services this.PathComponentsCount = this.componentsWithSeparators.Length; string firstLiteralMatch = null; - var sbHashKey = new StringBuilder(); for (var i = 0; i < components.Length; i++) { var component = components[i]; @@ -172,7 +152,6 @@ namespace Emby.Server.Implementations.Services else { this.literalsToMatch[i] = component.ToLower(); - sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch); if (firstLiteralMatch == null) { @@ -198,9 +177,6 @@ namespace Emby.Server.Implementations.Services ? this.PathComponentsCount + PathSeperator + firstLiteralMatch : WildCardChar + PathSeperator + firstLiteralMatch; - this.IsValid = sbHashKey.Length > 0; - this.UniqueMatchHashKey = sbHashKey.ToString(); - this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType); RegisterCaseInsenstivePropertyNameMappings(); } @@ -220,26 +196,46 @@ namespace Emby.Server.Implementations.Services }; - private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) }; + private static Type excludeType = typeof(Stream); - internal static PropertyInfo[] GetSerializableProperties(Type type) + internal static List<PropertyInfo> GetSerializableProperties(Type type) { - var properties = GetPublicProperties(type); - var readableProperties = properties.Where(x => x.GetMethod != null); + var list = new List<PropertyInfo>(); + var props = GetPublicProperties(type); - // else return those properties that are not decorated with IgnoreDataMember - return readableProperties - .Where(prop => prop.GetCustomAttributes(true) - .All(attr => + foreach (var prop in props) + { + if (prop.GetMethod == null) + { + continue; + } + + if (excludeType == prop.PropertyType) + { + continue; + } + + var ignored = false; + foreach (var attr in prop.GetCustomAttributes(true)) + { + if (IgnoreAttributesNamed.Contains(attr.GetType().Name)) { - var name = attr.GetType().Name; - return !IgnoreAttributesNamed.Contains(name); - })) - .Where(prop => !_excludeTypes.Contains(prop.PropertyType)) - .ToArray(); + ignored = true; + break; + } + } + + if (!ignored) + { + list.Add(prop); + } + } + + // else return those properties that are not decorated with IgnoreDataMember + return list; } - private static PropertyInfo[] GetPublicProperties(Type type) + private static List<PropertyInfo> GetPublicProperties(Type type) { if (type.GetTypeInfo().IsInterface) { @@ -269,12 +265,19 @@ namespace Emby.Server.Implementations.Services propertyInfos.InsertRange(0, newPropertyInfos); } - return propertyInfos.ToArray(propertyInfos.Count); + return propertyInfos; } - return GetTypesPublicProperties(type) - .Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties - .ToArray(); + var list = new List<PropertyInfo>(); + + foreach (var t in GetTypesPublicProperties(type)) + { + if (t.GetIndexParameters().Length == 0) + { + list.Add(t); + } + } + return list; } private static PropertyInfo[] GetTypesPublicProperties(Type subType) @@ -289,16 +292,11 @@ namespace Emby.Server.Implementations.Services return pis.ToArray(pis.Count); } - - public bool IsValid { get; set; } - /// <summary> /// Provide for quick lookups based on hashes that can be determined from a request url /// </summary> public string FirstMatchHashKey { get; private set; } - public string UniqueMatchHashKey { get; private set; } - private readonly StringMapTypeDeserializer typeDeserializer; private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>(); @@ -321,8 +319,14 @@ namespace Emby.Server.Implementations.Services score += Math.Max((10 - VariableArgsCount), 1) * 100; //Exact verb match is better than ANY - var exactVerb = String.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase); - score += exactVerb ? 10 : 1; + if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) + { + score += 10; + } + else + { + score += 1; + } return score; } @@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Services return false; } - if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod)) + if (!Verbs.Contains(httpMethod, StringComparer.OrdinalIgnoreCase)) { //logger.Info("allowsAllVerbs mismatch for {0} for {1} allowedverbs {2}", httpMethod, string.Join("/", withPathInfoParts), this.allowedVerbs); return false; @@ -457,8 +461,7 @@ namespace Emby.Server.Implementations.Services public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance) { - var requestComponents = pathInfo.Split(PathSeperatorChar) - .Where(x => !String.IsNullOrEmpty(x)).ToArray(); + var requestComponents = pathInfo.Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); ExplodeComponents(ref requestComponents); @@ -555,10 +558,5 @@ namespace Emby.Server.Implementations.Services return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap); } - - public override int GetHashCode() - { - return UniqueMatchHashKey.GetHashCode(); - } } }
\ No newline at end of file diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index fc1cf4ed9..2233bf918 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; namespace Emby.Server.Implementations.Services @@ -64,11 +63,16 @@ namespace Emby.Server.Implementations.Services if (instance == null) instance = _CreateInstanceFn(type); - foreach (var pair in keyValuePairs.Where(x => !string.IsNullOrEmpty(x.Value))) + foreach (var pair in keyValuePairs) { propertyName = pair.Key; propertyTextValue = pair.Value; + if (string.IsNullOrEmpty(propertyTextValue)) + { + continue; + } + if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)) { if (propertyName == "v") @@ -115,7 +119,7 @@ namespace Emby.Server.Implementations.Services { public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) { - if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Any()) return null; + if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null; var setMethodInfo = propertyInfo.SetMethod; return (instance, value) => setMethodInfo.Invoke(instance, new[] { value }); diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs new file mode 100644 index 000000000..fc2bdbd55 --- /dev/null +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.Services +{ + [Route("/swagger", "GET", Summary = "Gets the swagger specifications")] + [Route("/swagger.json", "GET", Summary = "Gets the swagger specifications")] + public class GetSwaggerSpec : IReturn<SwaggerSpec> + { + } + + public class SwaggerSpec + { + public string swagger { get; set; } + public string[] schemes { get; set; } + public SwaggerInfo info { get; set; } + public string host { get; set; } + public string basePath { get; set; } + public SwaggerTag[] tags { get; set; } + public IDictionary<string, Dictionary<string, SwaggerMethod>> paths { get; set; } + public Dictionary<string, SwaggerDefinition> definitions { get; set; } + public SwaggerComponents components { get; set; } + } + + public class SwaggerComponents + { + public Dictionary<string, SwaggerSecurityScheme> securitySchemes { get; set; } + } + + public class SwaggerSecurityScheme + { + public string name { get; set; } + public string type { get; set; } + public string @in { get; set; } + } + + public class SwaggerInfo + { + public string description { get; set; } + public string version { get; set; } + public string title { get; set; } + public string termsOfService { get; set; } + + public SwaggerConcactInfo contact { get; set; } + } + + public class SwaggerConcactInfo + { + public string email { get; set; } + public string name { get; set; } + public string url { get; set; } + } + + public class SwaggerTag + { + public string description { get; set; } + public string name { get; set; } + } + + public class SwaggerMethod + { + public string summary { get; set; } + public string description { get; set; } + public string[] tags { get; set; } + public string operationId { get; set; } + public string[] consumes { get; set; } + public string[] produces { get; set; } + public SwaggerParam[] parameters { get; set; } + public Dictionary<string, SwaggerResponse> responses { get; set; } + public Dictionary<string, string[]>[] security { get; set; } + } + + public class SwaggerParam + { + public string @in { get; set; } + public string name { get; set; } + public string description { get; set; } + public bool required { get; set; } + public string type { get; set; } + public string collectionFormat { get; set; } + } + + public class SwaggerResponse + { + public string description { get; set; } + + // ex. "$ref":"#/definitions/Pet" + public Dictionary<string, string> schema { get; set; } + } + + public class SwaggerDefinition + { + public string type { get; set; } + public Dictionary<string, SwaggerProperty> properties { get; set; } + } + + public class SwaggerProperty + { + public string type { get; set; } + public string format { get; set; } + public string description { get; set; } + public string[] @enum { get; set; } + public string @default { get; set; } + } + + public class SwaggerService : IService, IRequiresRequest + { + private SwaggerSpec _spec; + + public IRequest Request { get; set; } + + public object Get(GetSwaggerSpec request) + { + return _spec ?? (_spec = GetSpec()); + } + + private SwaggerSpec GetSpec() + { + string host = null; + Uri uri; + if (Uri.TryCreate(Request.RawUrl, UriKind.Absolute, out uri)) + { + host = uri.Host; + } + + var securitySchemes = new Dictionary<string, SwaggerSecurityScheme>(); + + securitySchemes["api_key"] = new SwaggerSecurityScheme + { + name = "api_key", + type = "apiKey", + @in = "query" + }; + + var spec = new SwaggerSpec + { + schemes = new[] { "http" }, + tags = GetTags(), + swagger = "2.0", + info = new SwaggerInfo + { + title = "Emby Server API", + version = "1.0.0", + description = "Explore the Emby Server API", + contact = new SwaggerConcactInfo + { + name = "Emby Developer Community", + url = "https://emby.media/community/index.php?/forum/47-developer-api" + }, + termsOfService = "https://emby.media/terms" + }, + paths = GetPaths(), + definitions = GetDefinitions(), + basePath = "/emby", + host = host, + + components = new SwaggerComponents + { + securitySchemes = securitySchemes + } + }; + + return spec; + } + + + private SwaggerTag[] GetTags() + { + return new SwaggerTag[] { }; + } + + private Dictionary<string, SwaggerDefinition> GetDefinitions() + { + return new Dictionary<string, SwaggerDefinition>(); + } + + private IDictionary<string, Dictionary<string, SwaggerMethod>> GetPaths() + { + var paths = new SortedDictionary<string, Dictionary<string, SwaggerMethod>>(); + + var all = ServiceController.Instance.RestPathMap.OrderBy(i => i.Key, StringComparer.OrdinalIgnoreCase).ToList(); + + foreach (var current in all) + { + foreach (var info in current.Value) + { + if (info.IsHidden) + { + continue; + } + + if (info.Path.StartsWith("/mediabrowser", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + if (info.Path.StartsWith("/emby", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + paths[info.Path] = GetPathInfo(info); + } + } + + return paths; + } + + private Dictionary<string, SwaggerMethod> GetPathInfo(RestPath info) + { + var result = new Dictionary<string, SwaggerMethod>(); + + foreach (var verb in info.Verbs) + { + var responses = new Dictionary<string, SwaggerResponse> + { + }; + + responses["200"] = new SwaggerResponse + { + description = "OK" + }; + + var security = new List<Dictionary<string, string[]>>(); + + var apiKeySecurity = new Dictionary<string, string[]>(); + apiKeySecurity["api_key"] = new string[] { }; + + security.Add(apiKeySecurity); + + result[verb.ToLower()] = new SwaggerMethod + { + summary = info.Summary, + description = info.Description, + produces = new[] + { + "application/json" + }, + consumes = new[] + { + "application/json" + }, + operationId = info.RequestType.Name, + tags = new string[] { }, + + parameters = new SwaggerParam[] { }, + + responses = responses, + + security = security.ToArray() + }; + } + + return result; + } + } +} diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index dbac76bb4..bd53da1bd 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -14,7 +14,7 @@ using System.Threading.Tasks; namespace Emby.Server.Implementations.Session { - public class HttpSessionController : ISessionController, IDisposable + public class HttpSessionController : ISessionController { private readonly IHttpClient _httpClient; private readonly IJsonSerializer _json; @@ -195,9 +195,5 @@ namespace Emby.Server.Implementations.Session return "?" + args; } - - public void Dispose() - { - } } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 0692a0ba5..6f185bc81 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1416,7 +1416,7 @@ namespace Emby.Server.Implementations.Session if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false); + var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false); if (result == null) { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 2735bb237..a5af843db 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -102,6 +102,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _serverManager.WebSocketConnected -= _serverManager_WebSocketConnected; + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index f0ff0b5dd..ee9ee8969 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -283,6 +283,7 @@ namespace Emby.Server.Implementations.Session { socket.Closed -= connection_Closed; } + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs index 23b6a3cb5..764df8baf 100644 --- a/Emby.Server.Implementations/TV/SeriesPostScanTask.cs +++ b/Emby.Server.Implementations/TV/SeriesPostScanTask.cs @@ -217,6 +217,7 @@ namespace Emby.Server.Implementations.TV public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 018e452be..0b81f7e93 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -64,8 +64,7 @@ namespace Emby.Server.Implementations.TV var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.DatePlayed }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, ParentId = parentIdGuid, @@ -122,8 +121,7 @@ namespace Emby.Server.Implementations.TV var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.DatePlayed }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) }, SeriesPresentationUniqueKey = presentationUniqueKey, Limit = limit, DtoOptions = new MediaBrowser.Controller.Dto.DtoOptions @@ -200,8 +198,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.SortName }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, ParentIndexNumberNotEquals = 0, @@ -223,8 +220,7 @@ namespace Emby.Server.Implementations.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.SortName }, - SortOrder = SortOrder.Ascending, + OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) }, Limit = 1, IsPlayed = false, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/TextEncoding/TextEncoding.cs b/Emby.Server.Implementations/TextEncoding/TextEncoding.cs index 1496d6f0f..9eb9be7ea 100644 --- a/Emby.Server.Implementations/TextEncoding/TextEncoding.cs +++ b/Emby.Server.Implementations/TextEncoding/TextEncoding.cs @@ -27,18 +27,33 @@ namespace Emby.Server.Implementations.TextEncoding return Encoding.ASCII; } - private Encoding GetInitialEncoding(byte[] buffer) + private Encoding GetInitialEncoding(byte[] buffer, int count) { - if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) - return Encoding.UTF8; - if (buffer[0] == 0xfe && buffer[1] == 0xff) - return Encoding.Unicode; - if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) - return Encoding.UTF32; - if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) - return Encoding.UTF7; + if (count >= 3) + { + if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) + return Encoding.UTF8; + } + + if (count >= 2) + { + if (buffer[0] == 0xfe && buffer[1] == 0xff) + return Encoding.Unicode; + } + + if (count >= 4) + { + if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff) + return Encoding.UTF32; + } - var result = new TextEncodingDetect().DetectEncoding(buffer, buffer.Length); + if (count >= 3) + { + if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) + return Encoding.UTF7; + } + + var result = new TextEncodingDetect().DetectEncoding(buffer, count); switch (result) { @@ -64,9 +79,11 @@ namespace Emby.Server.Implementations.TextEncoding } private bool _langDetectInitialized; - public string GetDetectedEncodingName(byte[] bytes, string language, bool enableLanguageDetection) + public string GetDetectedEncodingName(byte[] bytes, int count, string language, bool enableLanguageDetection) { - var encoding = GetInitialEncoding(bytes); + var index = 0; + + var encoding = GetInitialEncoding(bytes, count); if (encoding != null && encoding.Equals(Encoding.UTF8)) { @@ -81,7 +98,7 @@ namespace Emby.Server.Implementations.TextEncoding LanguageDetector.Initialize(_json); } - language = DetectLanguage(bytes); + language = DetectLanguage(bytes, index, count); if (!string.IsNullOrWhiteSpace(language)) { @@ -89,7 +106,7 @@ namespace Emby.Server.Implementations.TextEncoding } } - var charset = DetectCharset(bytes, language); + var charset = DetectCharset(bytes, index, count, language); if (!string.IsNullOrWhiteSpace(charset)) { @@ -112,11 +129,11 @@ namespace Emby.Server.Implementations.TextEncoding return null; } - private string DetectLanguage(byte[] bytes) + private string DetectLanguage(byte[] bytes, int index, int count) { try { - return LanguageDetector.DetectLanguage(Encoding.UTF8.GetString(bytes)); + return LanguageDetector.DetectLanguage(Encoding.UTF8.GetString(bytes, index, count)); } catch (NLangDetectException ex) { @@ -124,7 +141,7 @@ namespace Emby.Server.Implementations.TextEncoding try { - return LanguageDetector.DetectLanguage(Encoding.ASCII.GetString(bytes)); + return LanguageDetector.DetectLanguage(Encoding.ASCII.GetString(bytes, index, count)); } catch (NLangDetectException ex) { @@ -132,7 +149,7 @@ namespace Emby.Server.Implementations.TextEncoding try { - return LanguageDetector.DetectLanguage(Encoding.Unicode.GetString(bytes)); + return LanguageDetector.DetectLanguage(Encoding.Unicode.GetString(bytes, index, count)); } catch (NLangDetectException ex) { @@ -163,9 +180,9 @@ namespace Emby.Server.Implementations.TextEncoding } } - public Encoding GetDetectedEncoding(byte[] bytes, string language, bool enableLanguageDetection) + public Encoding GetDetectedEncoding(byte[] bytes, int size, string language, bool enableLanguageDetection) { - var charset = GetDetectedEncodingName(bytes, language, enableLanguageDetection); + var charset = GetDetectedEncodingName(bytes, size, language, enableLanguageDetection); return GetEncodingFromCharset(charset); } @@ -225,10 +242,10 @@ namespace Emby.Server.Implementations.TextEncoding } } - private string DetectCharset(byte[] bytes, string language) + private string DetectCharset(byte[] bytes, int index, int count, string language) { var detector = new CharsetDetector(); - detector.Feed(bytes, 0, bytes.Length); + detector.Feed(bytes, index, count); detector.DataEnd(); var charset = detector.Charset; diff --git a/Emby.Server.Implementations/Threading/CommonTimer.cs b/Emby.Server.Implementations/Threading/CommonTimer.cs index 9451b07f3..bb67325d1 100644 --- a/Emby.Server.Implementations/Threading/CommonTimer.cs +++ b/Emby.Server.Implementations/Threading/CommonTimer.cs @@ -31,6 +31,7 @@ namespace Emby.Server.Implementations.Threading public void Dispose() { _timer.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 75328a39a..180463040 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -722,6 +722,7 @@ namespace Emby.Server.Implementations.Updates public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index 98e0c4080..fa1d5b74e 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.UserViews Recursive = recursive, IncludeItemTypes = new[] { typeof(BoxSet).Name }, Limit = 20, - SortBy = new[] { ItemSortBy.Random }, + OrderBy = new [] { new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending) }, DtoOptions = new DtoOptions(false) }); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 5b869221a..c27b8ac26 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="Emby.XmlTv" version="1.0.10" targetFramework="net46" /> - <package id="MediaBrowser.Naming" version="1.0.7" targetFramework="net46" /> <package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" /> <package id="SharpCompress" version="0.14.0" targetFramework="net46" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 21552617d..04cef60bf 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -81,18 +81,14 @@ namespace MediaBrowser.Api return value.Split(separator); } - /// <summary> - /// Runs this instance. - /// </summary> public void Run() { + } - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> public void Dispose() { + GC.SuppressFinalize(this); } } } diff --git a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs index 8004d7e9b..c7a9d97ba 100644 --- a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs @@ -314,6 +314,7 @@ namespace MediaBrowser.Api public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } } diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 35ac2b482..d64bf7ec7 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Api.UserLibrary; using MediaBrowser.Model.Services; namespace MediaBrowser.Api @@ -90,7 +91,7 @@ namespace MediaBrowser.Api public int? Limit { get; set; } [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public SortOrder? SortOrder { get; set; } + public string SortOrder { get; set; } [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Filters { get; set; } @@ -116,6 +117,15 @@ namespace MediaBrowser.Api return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)); } + + /// <summary> + /// Gets the order by. + /// </summary> + /// <returns>IEnumerable{ItemSortBy}.</returns> + public Tuple<string, SortOrder>[] GetOrderBy() + { + return BaseItemsRequest.GetOrderBy(SortBy, SortOrder); + } } [Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")] @@ -228,8 +238,7 @@ namespace MediaBrowser.Api UserId = request.UserId, ChannelId = request.Id, FolderId = request.FolderId, - SortOrder = request.SortOrder, - SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), + OrderBy = request.GetOrderBy(), Filters = request.GetFilters().ToArray(), Fields = request.GetItemFields() diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 389244aa9..cbef6e5b3 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs @@ -70,24 +70,27 @@ namespace MediaBrowser.Api.Dlna public Stream RequestStream { get; set; } } - [Route("/Dlna/{UuId}/mediareceiverregistrar/events", Summary = "Processes an event subscription request")] + [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] + [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] public class ProcessMediaReceiverRegistrarEventRequest { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,POST")] + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] public string UuId { get; set; } } - [Route("/Dlna/{UuId}/contentdirectory/events", Summary = "Processes an event subscription request")] + [Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] + [Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] public class ProcessContentDirectoryEventRequest { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,POST")] + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] public string UuId { get; set; } } - [Route("/Dlna/{UuId}/connectionmanager/events", Summary = "Processes an event subscription request")] + [Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] + [Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] public class ProcessConnectionManagerEventRequest { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,POST")] + [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] public string UuId { get; set; } } @@ -200,17 +203,32 @@ namespace MediaBrowser.Api.Dlna } } - public object Any(ProcessContentDirectoryEventRequest request) + public object Subscribe(ProcessContentDirectoryEventRequest request) { return ProcessEventRequest(_contentDirectory); } - public object Any(ProcessConnectionManagerEventRequest request) + public object Subscribe(ProcessConnectionManagerEventRequest request) { return ProcessEventRequest(_connectionManager); } - public object Any(ProcessMediaReceiverRegistrarEventRequest request) + public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) + { + return ProcessEventRequest(_mediaReceiverRegistrar); + } + + public object Unsubscribe(ProcessContentDirectoryEventRequest request) + { + return ProcessEventRequest(_contentDirectory); + } + + public object Unsubscribe(ProcessConnectionManagerEventRequest request) + { + return ProcessEventRequest(_connectionManager); + } + + public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) { return ProcessEventRequest(_mediaReceiverRegistrar); } diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index bc6101d6c..400169ac5 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -298,7 +298,7 @@ namespace MediaBrowser.Api /// <returns>IEnumerable{FileSystemEntryInfo}.</returns> private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request) { - var entries = _fileSystem.GetFileSystemEntries(request.Path).Where(i => + var entries = _fileSystem.GetFileSystemEntries(request.Path).OrderBy(i => i.FullName).Where(i => { if (!request.IncludeHidden && i.IsHidden) { diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index 8a16cbfa1..6c48b732f 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -128,7 +128,7 @@ namespace MediaBrowser.Api DisplayName = system.Name }; - var items = user == null ? + var items = user == null ? system.GetRecursiveChildren(i => i is Game) : system.GetRecursiveChildren(user, new InternalItemsQuery(user) { @@ -157,14 +157,14 @@ namespace MediaBrowser.Api /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public async Task<object> Get(GetSimilarGames request) + public object Get(GetSimilarGames request) { - var result = await GetSimilarItemsResult(request).ConfigureAwait(false); + var result = GetSimilarItemsResult(request); return ToOptimizedSerializedResultUsingCache(result); } - private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) + private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; @@ -186,7 +186,7 @@ namespace MediaBrowser.Api }); - var returnList = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)); + var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); var result = new QueryResult<BaseItemDto> { diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 309c7195b..f8481517d 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -567,7 +567,7 @@ namespace MediaBrowser.Api.Images cropwhitespace = request.CropWhitespace.Value; } - var outputFormats = GetOutputFormats(request, imageInfo, cropwhitespace, supportedImageEnhancers); + var outputFormats = GetOutputFormats(request); TimeSpan? cacheDuration = null; @@ -597,7 +597,7 @@ namespace MediaBrowser.Api.Images ImageRequest request, ItemImageInfo image, bool cropwhitespace, - List<ImageFormat> supportedFormats, + ImageFormat[] supportedFormats, List<IImageEnhancer> enhancers, TimeSpan? cacheDuration, IDictionary<string, string> headers, @@ -639,62 +639,23 @@ namespace MediaBrowser.Api.Images IsHeadRequest = isHeadRequest, Path = imageResult.Item1, - // Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it. - // I'd rather do this than add a delay after saving the file - FileShare = FileShareMode.ReadWrite + FileShare = FileShareMode.Read }).ConfigureAwait(false); } - private List<ImageFormat> GetOutputFormats(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List<IImageEnhancer> enhancers) + private ImageFormat[] GetOutputFormats(ImageRequest request) { if (!string.IsNullOrWhiteSpace(request.Format)) { ImageFormat format; if (Enum.TryParse(request.Format, true, out format)) { - return new List<ImageFormat> { format }; + return new ImageFormat[] { format }; } } - var extension = Path.GetExtension(image.Path); - ImageFormat? inputFormat = null; - - if (string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) || - string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase)) - { - inputFormat = ImageFormat.Jpg; - } - else if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase)) - { - inputFormat = ImageFormat.Png; - } - - var clientSupportedFormats = GetClientSupportedFormats(); - - var serverFormats = _imageProcessor.GetSupportedImageOutputFormats(); - var outputFormats = new List<ImageFormat>(); - - // Client doesn't care about format, so start with webp if supported - if (serverFormats.Contains(ImageFormat.Webp) && clientSupportedFormats.Contains(ImageFormat.Webp)) - { - outputFormats.Add(ImageFormat.Webp); - } - - if (enhancers.Count > 0) - { - outputFormats.Add(ImageFormat.Png); - } - - if (inputFormat.HasValue && inputFormat.Value == ImageFormat.Jpg) - { - outputFormats.Add(ImageFormat.Jpg); - } - - // We can't predict if there will be transparency or not, so play it safe - outputFormats.Add(ImageFormat.Png); - - return outputFormats; + return GetClientSupportedFormats(); } private ImageFormat[] GetClientSupportedFormats() diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 9e83cf680..53a67ff71 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -214,6 +214,8 @@ namespace MediaBrowser.Api UpdateItem(request, item); + item.OnMetadataChanged(); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); if (isLockedChanged && item.IsFolder) diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 44cc60b50..6152ea20b 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -518,9 +518,18 @@ namespace MediaBrowser.Api.Library LogDownload(item, user, auth); } + var path = item.Path; + + // Quotes are valid in linux. They'll possibly cause issues here + var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); + if (!string.IsNullOrWhiteSpace(filename)) + { + headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\""; + } + return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { - Path = item.Path, + Path = path, ResponseHeaders = headers }); } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index e866be9c6..36bcee913 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -15,6 +15,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Api.UserLibrary; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Configuration; @@ -373,7 +374,7 @@ namespace MediaBrowser.Api.LiveTv public string SortBy { get; set; } [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public SortOrder? SortOrder { get; set; } + public string SortOrder { get; set; } [ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string Genres { get; set; } @@ -922,8 +923,7 @@ namespace MediaBrowser.Api.LiveTv options.AddCurrentProgram = request.AddCurrentProgram; - var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user) - .ConfigureAwait(false)); + var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, options, user); var result = new QueryResult<BaseItemDto> { @@ -995,8 +995,7 @@ namespace MediaBrowser.Api.LiveTv query.StartIndex = request.StartIndex; query.Limit = request.Limit; - query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - query.SortOrder = request.SortOrder; + query.OrderBy = BaseItemsRequest.GetOrderBy(request.SortBy, request.SortOrder); query.IsNews = request.IsNews; query.IsMovie = request.IsMovie; query.IsSeries = request.IsSeries; @@ -1075,12 +1074,12 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(result); } - public async Task<object> Get(GetRecordingSeries request) + public object Get(GetRecordingSeries request) { var options = GetDtoOptions(_authContext, request); options.DeviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; - var result = await _liveTvManager.GetRecordingSeries(new RecordingQuery + var result = _liveTvManager.GetRecordingSeries(new RecordingQuery { ChannelId = request.ChannelId, UserId = request.UserId, @@ -1092,7 +1091,7 @@ namespace MediaBrowser.Api.LiveTv IsInProgress = request.IsInProgress, EnableTotalRecordCount = request.EnableTotalRecordCount - }, options, CancellationToken.None).ConfigureAwait(false); + }, options, CancellationToken.None); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 602a697bf..ddb187f3d 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -50,28 +50,6 @@ <Compile Include="IHasDtoOptions.cs" /> <Compile Include="LiveTv\ProgressiveFileCopier.cs" /> <Compile Include="PlaylistService.cs" /> - <Compile Include="Reports\Activities\ReportActivitiesBuilder.cs" /> - <Compile Include="Reports\Common\HeaderActivitiesMetadata.cs" /> - <Compile Include="Reports\Common\HeaderMetadata.cs" /> - <Compile Include="Reports\Common\ItemViewType.cs" /> - <Compile Include="Reports\Common\ReportBuilderBase.cs" /> - <Compile Include="Reports\Common\ReportDisplayType.cs" /> - <Compile Include="Reports\Common\ReportExportType.cs" /> - <Compile Include="Reports\Common\ReportFieldType.cs" /> - <Compile Include="Reports\Common\ReportHeaderIdType.cs" /> - <Compile Include="Reports\Common\ReportHelper.cs" /> - <Compile Include="Reports\Common\ReportIncludeItemTypes.cs" /> - <Compile Include="Reports\Common\ReportViewType.cs" /> - <Compile Include="Reports\Data\ReportBuilder.cs" /> - <Compile Include="Reports\Data\ReportExport.cs" /> - <Compile Include="Reports\Data\ReportOptions.cs" /> - <Compile Include="Reports\Model\ReportGroup.cs" /> - <Compile Include="Reports\Model\ReportHeader.cs" /> - <Compile Include="Reports\Model\ReportItem.cs" /> - <Compile Include="Reports\Model\ReportResult.cs" /> - <Compile Include="Reports\Model\ReportRow.cs" /> - <Compile Include="Reports\ReportRequests.cs" /> - <Compile Include="Reports\ReportsService.cs" /> <Compile Include="Social\SharingService.cs" /> <Compile Include="StartupWizardService.cs" /> <Compile Include="Subtitles\SubtitleService.cs" /> diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 3e42026b1..254c93b33 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -113,16 +113,16 @@ namespace MediaBrowser.Api.Movies /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public async Task<object> Get(GetSimilarMovies request) + public object Get(GetSimilarMovies request) { - var result = await GetSimilarItemsResult(request).ConfigureAwait(false); + var result = GetSimilarItemsResult(request); return ToOptimizedSerializedResultUsingCache(result); } - public async Task<object> Get(GetSimilarTrailers request) + public object Get(GetSimilarTrailers request) { - var result = await GetSimilarItemsResult(request).ConfigureAwait(false); + var result = GetSimilarItemsResult(request); return ToOptimizedSerializedResultUsingCache(result); } @@ -138,7 +138,7 @@ namespace MediaBrowser.Api.Movies return ToOptimizedResult(result); } - private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) + private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; @@ -166,7 +166,7 @@ namespace MediaBrowser.Api.Movies }); - var returnList = await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false); + var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -193,8 +193,7 @@ namespace MediaBrowser.Api.Movies //typeof(LiveTvProgram).Name }, // IsMovie = true - SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), Limit = 7, ParentId = parentIdGuid, Recursive = true, @@ -215,8 +214,7 @@ namespace MediaBrowser.Api.Movies { IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, - SortBy = new[] { ItemSortBy.Random }, - SortOrder = SortOrder.Descending, + OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), Limit = 10, IsFavoriteOrLiked = true, ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(recentlyPlayedMovies.Count), @@ -313,7 +311,7 @@ namespace MediaBrowser.Api.Movies if (items.Count > 0) { - var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result; + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); yield return new RecommendationDto { @@ -353,7 +351,7 @@ namespace MediaBrowser.Api.Movies if (items.Count > 0) { - var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result; + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); yield return new RecommendationDto { @@ -390,7 +388,7 @@ namespace MediaBrowser.Api.Movies if (similar.Count > 0) { - var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result; + var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user); yield return new RecommendationDto { diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index bc7ae2be2..d7986fa50 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Persistence; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Music @@ -52,43 +51,43 @@ namespace MediaBrowser.Api.Music _authContext = authContext; } - public async Task<object> Get(GetSimilarArtists request) + public object Get(GetSimilarArtists request) { var dtoOptions = GetDtoOptions(_authContext, request); - var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, Logger, request, new[] { typeof(MusicArtist) }, - SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false); + SimilarItemsHelper.GetSimiliarityScore); return ToOptimizedSerializedResultUsingCache(result); } - + /// <summary> /// Gets the specified request. /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public async Task<object> Get(GetSimilarAlbums request) + public object Get(GetSimilarAlbums request) { var dtoOptions = GetDtoOptions(_authContext, request); - var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, Logger, request, new[] { typeof(MusicAlbum) }, - GetAlbumSimilarityScore).ConfigureAwait(false); + GetAlbumSimilarityScore); return ToOptimizedSerializedResultUsingCache(result); } - + /// <summary> /// Gets the album similarity score. /// </summary> diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index bf6b75a3e..8a18298f1 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Api.Music _authContext = authContext; } - public Task<object> Get(GetInstantMixFromItem request) + public object Get(GetInstantMixFromItem request) { var item = _libraryManager.GetItemById(request.Id); @@ -94,7 +94,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromArtistId request) + public object Get(GetInstantMixFromArtistId request) { var item = _libraryManager.GetItemById(request.Id); @@ -107,7 +107,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromMusicGenreId request) + public object Get(GetInstantMixFromMusicGenreId request) { var item = _libraryManager.GetItemById(request.Id); @@ -120,7 +120,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromSong request) + public object Get(GetInstantMixFromSong request) { var item = _libraryManager.GetItemById(request.Id); @@ -133,7 +133,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromAlbum request) + public object Get(GetInstantMixFromAlbum request) { var album = _libraryManager.GetItemById(request.Id); @@ -146,7 +146,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromPlaylist request) + public object Get(GetInstantMixFromPlaylist request) { var playlist = (Playlist)_libraryManager.GetItemById(request.Id); @@ -159,7 +159,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromMusicGenre request) + public object Get(GetInstantMixFromMusicGenre request) { var user = _userManager.GetUserById(request.UserId); @@ -170,7 +170,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - public Task<object> Get(GetInstantMixFromArtist request) + public object Get(GetInstantMixFromArtist request) { var user = _userManager.GetUserById(request.UserId); var artist = _libraryManager.GetArtist(request.Name, new DtoOptions(false)); @@ -182,7 +182,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - private async Task<object> GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) + private object GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) { var list = items; @@ -196,8 +196,7 @@ namespace MediaBrowser.Api.Music list = list.Take(request.Limit.Value).ToList(); } - var returnList = (await _dtoService.GetBaseItemDtos(list, dtoOptions, user) - .ConfigureAwait(false)); + var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user); result.Items = returnList; diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 54c995d3d..226678021 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -172,7 +172,7 @@ namespace MediaBrowser.Api Task.WaitAll(task); } - public async Task<object> Get(GetPlaylistItems request) + public object Get(GetPlaylistItems request) { var playlist = (Playlist)_libraryManager.GetItemById(request.Id); var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; @@ -193,8 +193,7 @@ namespace MediaBrowser.Api var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user) - .ConfigureAwait(false)); + var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user); var index = 0; foreach (var item in dtos) diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index f6efe15e6..1eea89431 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Api /// <summary> /// Class GetPluginSecurityInfo /// </summary> - [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information")] + [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information", IsHidden = true)] [Authenticated] public class GetPluginSecurityInfo : IReturn<PluginSecurityInfo> { @@ -91,13 +91,13 @@ namespace MediaBrowser.Api /// <summary> /// Class UpdatePluginSecurityInfo /// </summary> - [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information")] + [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information", IsHidden = true)] [Authenticated(Roles = "Admin")] public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid { } - [Route("/Plugins/RegistrationRecords/{Name}", "GET", Summary = "Gets registration status for a feature")] + [Route("/Plugins/RegistrationRecords/{Name}", "GET", Summary = "Gets registration status for a feature", IsHidden = true)] [Authenticated] public class GetRegistrationStatus { @@ -108,7 +108,7 @@ namespace MediaBrowser.Api public string Mb2Equivalent { get; set; } } - [Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature")] + [Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature", IsHidden = true)] [Authenticated] public class GetRegistration : IReturn<RegistrationInfo> { @@ -116,7 +116,7 @@ namespace MediaBrowser.Api public string Name { get; set; } } - [Route("/Appstore/Register", "POST", Summary = "Registers an appstore sale")] + [Route("/Appstore/Register", "POST", Summary = "Registers an appstore sale", IsHidden = true)] [Authenticated] public class RegisterAppstoreSale { diff --git a/MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs b/MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs deleted file mode 100644 index 8fe5246f6..000000000 --- a/MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs +++ /dev/null @@ -1,256 +0,0 @@ -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Querying; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Library; -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report activities builder. </summary> - /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/> - public class ReportActivitiesBuilder : ReportBuilderBase - { - #region [Constructors] - - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportActivitiesBuilder class. </summary> - /// <param name="libraryManager"> Manager for library. </param> - /// <param name="userManager"> Manager for user. </param> - public ReportActivitiesBuilder(ILibraryManager libraryManager, IUserManager userManager) - : base(libraryManager) - { - _userManager = userManager; - } - - #endregion - - #region [Private Fields] - - private readonly IUserManager _userManager; ///< Manager for user - - #endregion - - #region [Public Methods] - - /// <summary> Gets a result. </summary> - /// <param name="queryResult"> The query result. </param> - /// <param name="request"> The request. </param> - /// <returns> The result. </returns> - public ReportResult GetResult(QueryResult<ActivityLogEntry> queryResult, IReportsQuery request) - { - ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType); - List<ReportOptions<ActivityLogEntry>> options = this.GetReportOptions<ActivityLogEntry>(request, - () => this.GetDefaultHeaderMetadata(), - (hm) => this.GetOption(hm)).Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)).ToList(); - - var headers = GetHeaders<ActivityLogEntry>(options); - var rows = GetReportRows(queryResult.Items, options); - - ReportResult result = new ReportResult { Headers = headers }; - HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy); - int i = headers.FindIndex(x => x.FieldName == groupBy); - if (groupBy != HeaderMetadata.None && i >= 0) - { - var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Group = g.Trim(), Rows = x }) - .GroupBy(x => x.Group) - .OrderBy(x => x.Key) - .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() }); - - result.Groups = rowsGroup.ToList(); - result.IsGrouped = true; - } - else - { - result.Rows = rows; - result.IsGrouped = false; - } - - return result; - } - - #endregion - - #region [Protected Internal Methods] - - /// <summary> Gets the headers. </summary> - /// <typeparam name="H"> Type of the header. </typeparam> - /// <param name="request"> The request. </param> - /// <returns> The headers. </returns> - /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/> - protected internal override List<ReportHeader> GetHeaders<H>(H request) - { - return this.GetHeaders<ActivityLogEntry>(request, () => this.GetDefaultHeaderMetadata(), (hm) => this.GetOption(hm)); - } - - #endregion - - #region [Private Methods] - - /// <summary> Gets default header metadata. </summary> - /// <returns> The default header metadata. </returns> - private List<HeaderMetadata> GetDefaultHeaderMetadata() - { - return new List<HeaderMetadata> - { - HeaderMetadata.UserPrimaryImage, - HeaderMetadata.Date, - HeaderMetadata.User, - HeaderMetadata.Type, - HeaderMetadata.Severity, - HeaderMetadata.Name, - HeaderMetadata.ShortOverview, - HeaderMetadata.Overview, - //HeaderMetadata.UserId - //HeaderMetadata.Item, - }; - } - - /// <summary> Gets an option. </summary> - /// <param name="header"> The header. </param> - /// <param name="sortField"> The sort field. </param> - /// <returns> The option. </returns> - private ReportOptions<ActivityLogEntry> GetOption(HeaderMetadata header, string sortField = "") - { - HeaderMetadata internalHeader = header; - - ReportOptions<ActivityLogEntry> option = new ReportOptions<ActivityLogEntry>() - { - Header = new ReportHeader - { - HeaderFieldType = ReportFieldType.String, - SortField = sortField, - Type = "", - ItemViewType = ItemViewType.None - } - }; - - switch (header) - { - case HeaderMetadata.Name: - option.Column = (i, r) => i.Name; - option.Header.SortField = ""; - break; - case HeaderMetadata.Overview: - option.Column = (i, r) => i.Overview; - option.Header.SortField = ""; - option.Header.CanGroup = false; - break; - - case HeaderMetadata.ShortOverview: - option.Column = (i, r) => i.ShortOverview; - option.Header.SortField = ""; - option.Header.CanGroup = false; - break; - - case HeaderMetadata.Type: - option.Column = (i, r) => i.Type; - option.Header.SortField = ""; - break; - - case HeaderMetadata.Date: - option.Column = (i, r) => i.Date; - option.Header.SortField = ""; - option.Header.HeaderFieldType = ReportFieldType.DateTime; - option.Header.Type = ""; - break; - - case HeaderMetadata.UserPrimaryImage: - //option.Column = (i, r) => i.UserPrimaryImageTag; - option.Header.DisplayType = ReportDisplayType.Screen; - option.Header.ItemViewType = ItemViewType.UserPrimaryImage; - option.Header.ShowHeaderLabel = false; - internalHeader = HeaderMetadata.User; - option.Header.CanGroup = false; - option.Column = (i, r) => - { - if (!string.IsNullOrEmpty(i.UserId)) - { - MediaBrowser.Controller.Entities.User user = _userManager.GetUserById(i.UserId); - if (user != null) - { - var dto = _userManager.GetUserDto(user); - return dto.PrimaryImageTag; - } - } - return string.Empty; - }; - option.Header.SortField = ""; - break; - case HeaderMetadata.Severity: - option.Column = (i, r) => i.Severity; - option.Header.SortField = ""; - break; - case HeaderMetadata.Item: - option.Column = (i, r) => i.ItemId; - option.Header.SortField = ""; - break; - case HeaderMetadata.User: - option.Column = (i, r) => - { - if (!string.IsNullOrEmpty(i.UserId)) - { - MediaBrowser.Controller.Entities.User user = _userManager.GetUserById(i.UserId); - if (user != null) - return user.Name; - } - return string.Empty; - }; - option.Header.SortField = ""; - break; - case HeaderMetadata.UserId: - option.Column = (i, r) => i.UserId; - option.Header.SortField = ""; - break; - } - - option.Header.Name = GetLocalizedHeader(internalHeader); - option.Header.FieldName = header; - - return option; - } - - /// <summary> Gets report rows. </summary> - /// <param name="items"> The items. </param> - /// <param name="options"> Options for controlling the operation. </param> - /// <returns> The report rows. </returns> - private List<ReportRow> GetReportRows(IEnumerable<ActivityLogEntry> items, List<ReportOptions<ActivityLogEntry>> options) - { - var rows = new List<ReportRow>(); - - foreach (ActivityLogEntry item in items) - { - ReportRow rRow = GetRow(item); - foreach (ReportOptions<ActivityLogEntry> option in options) - { - object itemColumn = option.Column != null ? option.Column(item, rRow) : ""; - object itemId = option.ItemID != null ? option.ItemID(item) : ""; - ReportItem rItem = new ReportItem - { - Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType), - Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object) - }; - rRow.Columns.Add(rItem); - } - - rows.Add(rRow); - } - - return rows; - } - - /// <summary> Gets a row. </summary> - /// <param name="item"> The item. </param> - /// <returns> The row. </returns> - private ReportRow GetRow(ActivityLogEntry item) - { - ReportRow rRow = new ReportRow - { - Id = item.Id, - UserId = item.UserId - }; - return rRow; - } - - #endregion - - } -} diff --git a/MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs b/MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs deleted file mode 100644 index 4a45f2646..000000000 --- a/MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum HeaderActivitiesMetadata - { - None, - Name, - Overview, - ShortOverview, - Type, - Date, - UserPrimaryImageTag, - Severity, - Item, - User - } -} diff --git a/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs b/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs deleted file mode 100644 index e25e78802..000000000 --- a/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum HeaderMetadata - { - None, - Name, - PremiereDate, - DateAdded, - ReleaseDate, - Runtime, - PlayCount, - Season, - SeasonNumber, - Series, - Network, - Year, - ParentalRating, - CommunityRating, - Trailers, - Specials, - GameSystem, - Players, - AlbumArtist, - Album, - Disc, - Track, - Audio, - EmbeddedImage, - Video, - Resolution, - Subtitles, - Genres, - Countries, - Status, - Tracks, - EpisodeSeries, - EpisodeSeason, - EpisodeNumber, - AudioAlbumArtist, - MusicArtist, - AudioAlbum, - Locked, - ImagePrimary, - ImageBackdrop, - ImageLogo, - Actor, - Studios, - Composer, - Director, - GuestStar, - Producer, - Writer, - Artist, - Years, - ParentalRatings, - CommunityRatings, - - //Activity logs - Overview, - ShortOverview, - Type, - Date, - UserPrimaryImage, - Severity, - Item, - User, - UserId - } -} diff --git a/MediaBrowser.Api/Reports/Common/ItemViewType.cs b/MediaBrowser.Api/Reports/Common/ItemViewType.cs deleted file mode 100644 index ede6705e0..000000000 --- a/MediaBrowser.Api/Reports/Common/ItemViewType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ItemViewType - { - None, - Detail, - Edit, - List, - ItemByNameDetails, - StatusImage, - EmbeddedImage, - SubtitleImage, - TrailersImage, - SpecialsImage, - LockDataImage, - TagsPrimaryImage, - TagsBackdropImage, - TagsLogoImage, - UserPrimaryImage - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs b/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs deleted file mode 100644 index 6d5a180fb..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs +++ /dev/null @@ -1,365 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report builder base. </summary> - public abstract class ReportBuilderBase - { - - #region [Constructors] - - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilderBase class. </summary> - /// <param name="libraryManager"> Manager for library. </param> - public ReportBuilderBase(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - #endregion - - #region [Protected Fields] - - /// <summary> Manager for library. </summary> - protected readonly ILibraryManager _libraryManager; ///< Manager for library - - protected Func<bool, string> GetBoolString = s => s == true ? "x" : ""; ///< . - - #endregion - - #region [Protected Internal Methods] - - /// <summary> Gets the headers. </summary> - /// <typeparam name="H"> Type of the header. </typeparam> - /// <param name="request"> The request. </param> - /// <returns> The headers. </returns> - protected internal abstract List<ReportHeader> GetHeaders<H>(H request) where H : IReportsHeader; - - #endregion - - #region [Protected Methods] - - /// <summary> Gets active headers. </summary> - /// <typeparam name="T"> Generic type parameter. </typeparam> - /// <param name="options"> Options for controlling the operation. </param> - /// <returns> The active headers. </returns> - protected List<ReportHeader> GetActiveHeaders<T>(List<ReportOptions<T>> options, ReportDisplayType displayType) - { - List<ReportHeader> headers = new List<ReportHeader>(); - foreach (ReportOptions<T> option in options.Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType))) - { - headers.Add(option.Header); - } - - return headers; - } - - /// <summary> Gets audio stream. </summary> - /// <param name="item"> The item. </param> - /// <returns> The audio stream. </returns> - protected string GetAudioStream(BaseItem item) - { - var stream = GetStream(item, MediaStreamType.Audio); - if (stream != null) - return stream.Codec.ToUpper() == "DCA" ? stream.Profile : stream.Codec. - ToUpper(); - - return string.Empty; - } - - /// <summary> Gets an episode. </summary> - /// <param name="item"> The item. </param> - /// <returns> The episode. </returns> - protected string GetEpisode(BaseItem item) - { - - if (item.GetClientTypeName() == ChannelMediaContentType.Episode.ToString() && item.ParentIndexNumber != null) - return "Season " + item.ParentIndexNumber; - else - return item.Name; - } - - /// <summary> Gets a genre. </summary> - /// <param name="name"> The name. </param> - /// <returns> The genre. </returns> - protected Genre GetGenre(string name) - { - if (string.IsNullOrEmpty(name)) - return null; - return _libraryManager.GetGenre(name); - } - - /// <summary> Gets genre identifier. </summary> - /// <param name="name"> The name. </param> - /// <returns> The genre identifier. </returns> - protected string GetGenreID(string name) - { - if (string.IsNullOrEmpty(name)) - return string.Empty; - return string.Format("{0:N}", - GetGenre(name).Id); - } - - /// <summary> Gets the headers. </summary> - /// <typeparam name="T"> Generic type parameter. </typeparam> - /// <param name="options"> Options for controlling the operation. </param> - /// <returns> The headers. </returns> - protected List<ReportHeader> GetHeaders<T>(List<ReportOptions<T>> options) - { - List<ReportHeader> headers = new List<ReportHeader>(); - foreach (ReportOptions<T> option in options) - { - headers.Add(option.Header); - } - - return headers; - } - - /// <summary> Gets the headers. </summary> - /// <typeparam name="T"> Generic type parameter. </typeparam> - /// <param name="request"> The request. </param> - /// <param name="getHeadersMetadata"> The get headers metadata. </param> - /// <param name="getOptions"> Options for controlling the get. </param> - /// <returns> The headers. </returns> - protected List<ReportHeader> GetHeaders<T>(IReportsHeader request, Func<List<HeaderMetadata>> getHeadersMetadata, Func<HeaderMetadata, ReportOptions<T>> getOptions) - { - List<ReportOptions<T>> options = this.GetReportOptions(request, getHeadersMetadata, getOptions); - return this.GetHeaders(options); - } - - /// <summary> Gets list as string. </summary> - /// <param name="items"> The items. </param> - /// <returns> The list as string. </returns> - protected string GetListAsString(List<string> items) - { - return String.Join("; ", items); - } - - /// <summary> Gets localized header. </summary> - /// <param name="internalHeader"> The internal header. </param> - /// <returns> The localized header. </returns> - protected static string GetLocalizedHeader(HeaderMetadata internalHeader) - { - if (internalHeader == HeaderMetadata.EpisodeNumber) - { - return "Episode"; - } - - string headerName = ""; - if (internalHeader != HeaderMetadata.None) - { - string localHeader = "Header" + internalHeader.ToString(); - headerName = ReportHelper.GetCoreLocalizedString(localHeader); - } - return headerName; - } - - /// <summary> Gets media source information. </summary> - /// <param name="item"> The item. </param> - /// <returns> The media source information. </returns> - protected MediaSourceInfo GetMediaSourceInfo(BaseItem item) - { - var mediaSource = item as IHasMediaSources; - if (mediaSource != null) - return mediaSource.GetMediaSources(false).FirstOrDefault(n => n.Type == MediaSourceType.Default); - - return null; - } - - /// <summary> Gets an object. </summary> - /// <typeparam name="T"> Generic type parameter. </typeparam> - /// <typeparam name="R"> Type of the r. </typeparam> - /// <param name="item"> The item. </param> - /// <param name="function"> The function. </param> - /// <param name="defaultValue"> The default value. </param> - /// <returns> The object. </returns> - protected R GetObject<T, R>(BaseItem item, Func<T, R> function, R defaultValue = default(R)) where T : class - { - var value = item as T; - if (value != null && function != null) - return function(value); - else - return defaultValue; - } - - /// <summary> Gets a person. </summary> - /// <param name="name"> The name. </param> - /// <returns> The person. </returns> - protected Person GetPerson(string name) - { - if (string.IsNullOrEmpty(name)) - return null; - return _libraryManager.GetPerson(name); - } - - /// <summary> Gets person identifier. </summary> - /// <param name="name"> The name. </param> - /// <returns> The person identifier. </returns> - protected string GetPersonID(string name) - { - if (string.IsNullOrEmpty(name)) - return string.Empty; - return string.Format("{0:N}", - GetPerson(name).Id); - } - - /// <summary> Gets report options. </summary> - /// <typeparam name="T"> Generic type parameter. </typeparam> - /// <param name="request"> The request. </param> - /// <param name="getHeadersMetadata"> The get headers metadata. </param> - /// <param name="getOptions"> Options for controlling the get. </param> - /// <returns> The report options. </returns> - protected List<ReportOptions<T>> GetReportOptions<T>(IReportsHeader request, Func<List<HeaderMetadata>> getHeadersMetadata, Func<HeaderMetadata, ReportOptions<T>> getOptions) - { - List<HeaderMetadata> headersMetadata = getHeadersMetadata(); - List<ReportOptions<T>> options = new List<ReportOptions<T>>(); - ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType); - foreach (HeaderMetadata header in headersMetadata) - { - ReportOptions<T> headerOptions = getOptions(header); - if (this.DisplayTypeVisible(headerOptions.Header.DisplayType, displayType)) - options.Add(headerOptions); - } - - if (request != null && !string.IsNullOrEmpty(request.ReportColumns)) - { - List<HeaderMetadata> headersMetadataFiltered = ReportHelper.GetFilteredReportHeaderMetadata(request.ReportColumns, () => headersMetadata); - foreach (ReportHeader header in options.Select(x => x.Header)) - { - - if (this.DisplayTypeVisible(header.DisplayType, displayType)) - { - - if (!headersMetadataFiltered.Contains(header.FieldName) && displayType != ReportDisplayType.Export) - { - header.DisplayType = ReportDisplayType.None; - } - } - else - header.DisplayType = ReportDisplayType.None; - } - } - - return options; - } - - /// <summary> Gets runtime date time. </summary> - /// <param name="runtime"> The runtime. </param> - /// <returns> The runtime date time. </returns> - protected double? GetRuntimeDateTime(long? runtime) - { - if (runtime.HasValue) - return Math.Ceiling(new TimeSpan(runtime.Value).TotalMinutes); - return null; - } - - /// <summary> Gets series production year. </summary> - /// <param name="item"> The item. </param> - /// <returns> The series production year. </returns> - protected string GetSeriesProductionYear(BaseItem item) - { - - string productionYear = item.ProductionYear.ToString(); - var series = item as Series; - if (series == null) - { - if (item.ProductionYear == null || item.ProductionYear == 0) - return string.Empty; - return productionYear; - } - - if (series.Status == SeriesStatus.Continuing) - return productionYear += "-Present"; - - if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear) - return productionYear += "-" + series.EndDate.Value.Year; - - return productionYear; - } - - /// <summary> Gets a stream. </summary> - /// <param name="item"> The item. </param> - /// <param name="streamType"> Type of the stream. </param> - /// <returns> The stream. </returns> - protected MediaStream GetStream(BaseItem item, MediaStreamType streamType) - { - var itemInfo = GetMediaSourceInfo(item); - if (itemInfo != null) - return itemInfo.MediaStreams.FirstOrDefault(n => n.Type == streamType); - - return null; - } - - /// <summary> Gets a studio. </summary> - /// <param name="name"> The name. </param> - /// <returns> The studio. </returns> - protected Studio GetStudio(string name) - { - if (string.IsNullOrEmpty(name)) - return null; - return _libraryManager.GetStudio(name); - } - - /// <summary> Gets studio identifier. </summary> - /// <param name="name"> The name. </param> - /// <returns> The studio identifier. </returns> - protected string GetStudioID(string name) - { - if (string.IsNullOrEmpty(name)) - return string.Empty; - return string.Format("{0:N}", - GetStudio(name).Id); - } - - /// <summary> Gets video resolution. </summary> - /// <param name="item"> The item. </param> - /// <returns> The video resolution. </returns> - protected string GetVideoResolution(BaseItem item) - { - var stream = GetStream(item, - MediaStreamType.Video); - if (stream != null && stream.Width != null) - return string.Format("{0} * {1}", - stream.Width, - stream.Height != null ? stream.Height.ToString() : "-"); - - return string.Empty; - } - - /// <summary> Gets video stream. </summary> - /// <param name="item"> The item. </param> - /// <returns> The video stream. </returns> - protected string GetVideoStream(BaseItem item) - { - var stream = GetStream(item, MediaStreamType.Video); - if (stream != null) - return stream.Codec.ToUpper(); - - return string.Empty; - } - - /// <summary> Displays a type visible. </summary> - /// <param name="headerDisplayType"> Type of the header display. </param> - /// <param name="displayType"> Type of the display. </param> - /// <returns> true if it succeeds, false if it fails. </returns> - protected bool DisplayTypeVisible(ReportDisplayType headerDisplayType, ReportDisplayType displayType) - { - if (headerDisplayType == ReportDisplayType.None) - return false; - - bool rval = headerDisplayType == displayType || headerDisplayType == ReportDisplayType.ScreenExport && (displayType == ReportDisplayType.Screen || displayType == ReportDisplayType.Export); - return rval; - } - - #endregion - - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportDisplayType.cs b/MediaBrowser.Api/Reports/Common/ReportDisplayType.cs deleted file mode 100644 index 681bb3928..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportDisplayType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ReportDisplayType - { - None, - Screen, - Export, - ScreenExport - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportExportType.cs b/MediaBrowser.Api/Reports/Common/ReportExportType.cs deleted file mode 100644 index 4ac4cad21..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportExportType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ReportExportType - { - CSV, - Excel - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportFieldType.cs b/MediaBrowser.Api/Reports/Common/ReportFieldType.cs deleted file mode 100644 index 63c48dff0..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportFieldType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ReportFieldType - { - String, - Boolean, - Date, - Time, - DateTime, - Int, - Image, - Object, - Minutes - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportHeaderIdType.cs b/MediaBrowser.Api/Reports/Common/ReportHeaderIdType.cs deleted file mode 100644 index 32b2bc04d..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportHeaderIdType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ReportHeaderIdType - { - Row, - Item - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportHelper.cs b/MediaBrowser.Api/Reports/Common/ReportHelper.cs deleted file mode 100644 index 9dc4fbd51..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportHelper.cs +++ /dev/null @@ -1,138 +0,0 @@ -using MediaBrowser.Controller.Entities; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report helper. </summary> - public class ReportHelper - { - #region [Public Methods] - - /// <summary> Convert field to string. </summary> - /// <typeparam name="T"> Generic type parameter. </typeparam> - /// <param name="value"> The value. </param> - /// <param name="fieldType"> Type of the field. </param> - /// <returns> The field converted to string. </returns> - public static string ConvertToString<T>(T value, ReportFieldType fieldType) - { - if (value == null) - return ""; - switch (fieldType) - { - case ReportFieldType.String: - return value.ToString(); - case ReportFieldType.Boolean: - return value.ToString(); - case ReportFieldType.Date: - return string.Format("{0:d}", value); - case ReportFieldType.Time: - return string.Format("{0:t}", value); - case ReportFieldType.DateTime: - return string.Format("{0:d}", value); - case ReportFieldType.Minutes: - return string.Format("{0}mn", value); - case ReportFieldType.Int: - return string.Format("", value); - default: - if (value is Guid) - return string.Format("{0:N}", value); - return value.ToString(); - } - } - - /// <summary> Gets filtered report header metadata. </summary> - /// <param name="reportColumns"> The report columns. </param> - /// <param name="defaultReturnValue"> The default return value. </param> - /// <returns> The filtered report header metadata. </returns> - public static List<HeaderMetadata> GetFilteredReportHeaderMetadata(string reportColumns, Func<List<HeaderMetadata>> defaultReturnValue = null) - { - if (!string.IsNullOrEmpty(reportColumns)) - { - var s = reportColumns.Split('|').Select(x => ReportHelper.GetHeaderMetadataType(x)).Where(x => x != HeaderMetadata.None); - return s.ToList(); - } - else - if (defaultReturnValue != null) - return defaultReturnValue(); - else - return new List<HeaderMetadata>(); - } - - /// <summary> Gets header metadata type. </summary> - /// <param name="header"> The header. </param> - /// <returns> The header metadata type. </returns> - public static HeaderMetadata GetHeaderMetadataType(string header) - { - if (string.IsNullOrEmpty(header)) - return HeaderMetadata.None; - - HeaderMetadata rType; - - if (!Enum.TryParse<HeaderMetadata>(header, out rType)) - return HeaderMetadata.None; - - return rType; - } - - /// <summary> Gets report view type. </summary> - /// <param name="rowType"> The type. </param> - /// <returns> The report view type. </returns> - public static ReportViewType GetReportViewType(string rowType) - { - if (string.IsNullOrEmpty(rowType)) - return ReportViewType.ReportData; - - ReportViewType rType; - - if (!Enum.TryParse<ReportViewType>(rowType, out rType)) - return ReportViewType.ReportData; - - return rType; - } - - /// <summary> Gets row type. </summary> - /// <param name="rowType"> The type. </param> - /// <returns> The row type. </returns> - public static ReportIncludeItemTypes GetRowType(string rowType) - { - if (string.IsNullOrEmpty(rowType)) - return ReportIncludeItemTypes.BaseItem; - - ReportIncludeItemTypes rType; - - if (!Enum.TryParse<ReportIncludeItemTypes>(rowType, out rType)) - return ReportIncludeItemTypes.BaseItem; - - return rType; - } - - /// <summary> Gets report display type. </summary> - /// <param name="displayType"> Type of the display. </param> - /// <returns> The report display type. </returns> - public static ReportDisplayType GetReportDisplayType(string displayType) - { - if (string.IsNullOrEmpty(displayType)) - return ReportDisplayType.ScreenExport; - - ReportDisplayType rType; - - if (!Enum.TryParse<ReportDisplayType>(displayType, out rType)) - return ReportDisplayType.ScreenExport; - - return rType; - } - - /// <summary> Gets core localized string. </summary> - /// <param name="phrase"> The phrase. </param> - /// <returns> The core localized string. </returns> - public static string GetCoreLocalizedString(string phrase) - { - return BaseItem.LocalizationManager.GetLocalizedString(phrase); - } - - #endregion - - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs b/MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs deleted file mode 100644 index 65cc2b686..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ReportIncludeItemTypes - { - MusicArtist, - MusicAlbum, - Book, - BoxSet, - Episode, - Game, - Video, - Movie, - MusicVideo, - Trailer, - Season, - Series, - Audio, - BaseItem, - Artist - } -} diff --git a/MediaBrowser.Api/Reports/Common/ReportViewType.cs b/MediaBrowser.Api/Reports/Common/ReportViewType.cs deleted file mode 100644 index 8593c8e84..000000000 --- a/MediaBrowser.Api/Reports/Common/ReportViewType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - public enum ReportViewType - { - ReportData, - ReportActivities - - } -} diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs deleted file mode 100644 index 6a1502c7e..000000000 --- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs +++ /dev/null @@ -1,626 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report builder. </summary> - /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/> - public class ReportBuilder : ReportBuilderBase - { - - #region [Constructors] - - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilder class. </summary> - /// <param name="libraryManager"> Manager for library. </param> - public ReportBuilder(ILibraryManager libraryManager) - : base(libraryManager) - { - } - - #endregion - - #region [Public Methods] - - /// <summary> Gets report result. </summary> - /// <param name="items"> The items. </param> - /// <param name="request"> The request. </param> - /// <returns> The report result. </returns> - public ReportResult GetResult(BaseItem[] items, IReportsQuery request) - { - ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType); - - List<ReportOptions<BaseItem>> options = this.GetReportOptions<BaseItem>(request, - () => this.GetDefaultHeaderMetadata(reportRowType), - (hm) => this.GetOption(hm)).Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)).ToList(); - - var headers = GetHeaders<BaseItem>(options); - var rows = GetReportRows(items, options); - - ReportResult result = new ReportResult { Headers = headers }; - HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy); - int i = headers.FindIndex(x => x.FieldName == groupBy); - if (groupBy != HeaderMetadata.None && i >= 0) - { - var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Group = g.Trim(), Rows = x }) - .GroupBy(x => x.Group) - .OrderBy(x => x.Key) - .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() }); - - result.Groups = rowsGroup.ToList(); - result.IsGrouped = true; - } - else - { - result.Rows = rows; - result.IsGrouped = false; - } - - return result; - } - - #endregion - - #region [Protected Internal Methods] - - /// <summary> Gets the headers. </summary> - /// <typeparam name="H"> Type of the header. </typeparam> - /// <param name="request"> The request. </param> - /// <returns> The headers. </returns> - /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/> - protected internal override List<ReportHeader> GetHeaders<H>(H request) - { - ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - return this.GetHeaders<BaseItem>(request, () => this.GetDefaultHeaderMetadata(reportRowType), (hm) => this.GetOption(hm)); - } - - #endregion - - #region [Private Methods] - - /// <summary> Gets default report header metadata. </summary> - /// <param name="reportIncludeItemTypes"> Type of the report row. </param> - /// <returns> The default report header metadata. </returns> - private List<HeaderMetadata> GetDefaultHeaderMetadata(ReportIncludeItemTypes reportIncludeItemTypes) - { - switch (reportIncludeItemTypes) - { - case ReportIncludeItemTypes.Season: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Series, - HeaderMetadata.Season, - HeaderMetadata.SeasonNumber, - HeaderMetadata.DateAdded, - HeaderMetadata.Year, - HeaderMetadata.Genres - }; - - case ReportIncludeItemTypes.Series: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.Network, - HeaderMetadata.DateAdded, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Runtime, - HeaderMetadata.Trailers, - HeaderMetadata.Specials - }; - - case ReportIncludeItemTypes.MusicAlbum: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.AlbumArtist, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Tracks, - HeaderMetadata.Year, - HeaderMetadata.Genres - }; - - case ReportIncludeItemTypes.MusicArtist: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.MusicArtist, - HeaderMetadata.Countries, - HeaderMetadata.DateAdded, - HeaderMetadata.Year, - HeaderMetadata.Genres - }; - - case ReportIncludeItemTypes.Game: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.GameSystem, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Players, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.Trailers - }; - - case ReportIncludeItemTypes.Movie: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Runtime, - HeaderMetadata.Video, - HeaderMetadata.Resolution, - HeaderMetadata.Audio, - HeaderMetadata.Subtitles, - HeaderMetadata.Trailers, - HeaderMetadata.Specials - }; - - case ReportIncludeItemTypes.Book: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating - }; - - case ReportIncludeItemTypes.BoxSet: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Trailers - }; - - case ReportIncludeItemTypes.Audio: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.AudioAlbumArtist, - HeaderMetadata.AudioAlbum, - HeaderMetadata.Disc, - HeaderMetadata.Track, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Runtime, - HeaderMetadata.Audio - }; - - case ReportIncludeItemTypes.Episode: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.EpisodeSeries, - HeaderMetadata.Season, - HeaderMetadata.EpisodeNumber, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Runtime, - HeaderMetadata.Video, - HeaderMetadata.Resolution, - HeaderMetadata.Audio, - HeaderMetadata.Subtitles, - HeaderMetadata.Trailers, - HeaderMetadata.Specials - }; - - case ReportIncludeItemTypes.Video: - case ReportIncludeItemTypes.MusicVideo: - case ReportIncludeItemTypes.Trailer: - case ReportIncludeItemTypes.BaseItem: - default: - return new List<HeaderMetadata> - { - HeaderMetadata.Status, - HeaderMetadata.Locked, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.ImagePrimary, - HeaderMetadata.ImageBackdrop, - HeaderMetadata.ImageLogo, - HeaderMetadata.Name, - HeaderMetadata.DateAdded, - HeaderMetadata.ReleaseDate, - HeaderMetadata.Year, - HeaderMetadata.Genres, - HeaderMetadata.ParentalRating, - HeaderMetadata.CommunityRating, - HeaderMetadata.Runtime, - HeaderMetadata.Video, - HeaderMetadata.Resolution, - HeaderMetadata.Audio, - HeaderMetadata.Subtitles, - HeaderMetadata.Trailers, - HeaderMetadata.Specials - }; - - } - - } - - /// <summary> Gets report option. </summary> - /// <param name="header"> The header. </param> - /// <param name="sortField"> The sort field. </param> - /// <returns> The report option. </returns> - private ReportOptions<BaseItem> GetOption(HeaderMetadata header, string sortField = "") - { - HeaderMetadata internalHeader = header; - - ReportOptions<BaseItem> option = new ReportOptions<BaseItem>() - { - Header = new ReportHeader - { - HeaderFieldType = ReportFieldType.String, - SortField = sortField, - Type = "", - ItemViewType = ItemViewType.None - } - }; - - switch (header) - { - case HeaderMetadata.Status: - option.Header.ItemViewType = ItemViewType.StatusImage; - internalHeader = HeaderMetadata.Status; - option.Header.CanGroup = false; - option.Header.DisplayType = ReportDisplayType.Screen; - break; - case HeaderMetadata.Locked: - option.Column = (i, r) => this.GetBoolString(r.HasLockData); - option.Header.ItemViewType = ItemViewType.LockDataImage; - option.Header.CanGroup = false; - option.Header.DisplayType = ReportDisplayType.Export; - break; - case HeaderMetadata.ImagePrimary: - option.Column = (i, r) => this.GetBoolString(r.HasImageTagsPrimary); - option.Header.ItemViewType = ItemViewType.TagsPrimaryImage; - option.Header.CanGroup = false; - option.Header.DisplayType = ReportDisplayType.Export; - break; - case HeaderMetadata.ImageBackdrop: - option.Column = (i, r) => this.GetBoolString(r.HasImageTagsBackdrop); - option.Header.ItemViewType = ItemViewType.TagsBackdropImage; - option.Header.CanGroup = false; - option.Header.DisplayType = ReportDisplayType.Export; - break; - case HeaderMetadata.ImageLogo: - option.Column = (i, r) => this.GetBoolString(r.HasImageTagsLogo); - option.Header.ItemViewType = ItemViewType.TagsLogoImage; - option.Header.CanGroup = false; - option.Header.DisplayType = ReportDisplayType.Export; - break; - - case HeaderMetadata.Name: - option.Column = (i, r) => i.Name; - option.Header.ItemViewType = ItemViewType.Detail; - option.Header.SortField = "SortName"; - break; - - case HeaderMetadata.DateAdded: - option.Column = (i, r) => i.DateCreated; - option.Header.SortField = "DateCreated,SortName"; - option.Header.HeaderFieldType = ReportFieldType.DateTime; - option.Header.Type = ""; - break; - - case HeaderMetadata.PremiereDate: - case HeaderMetadata.ReleaseDate: - option.Column = (i, r) => i.PremiereDate; - option.Header.HeaderFieldType = ReportFieldType.DateTime; - option.Header.SortField = "ProductionYear,PremiereDate,SortName"; - break; - - case HeaderMetadata.Runtime: - option.Column = (i, r) => this.GetRuntimeDateTime(i.RunTimeTicks); - option.Header.HeaderFieldType = ReportFieldType.Minutes; - option.Header.SortField = "Runtime,SortName"; - break; - - case HeaderMetadata.PlayCount: - option.Header.HeaderFieldType = ReportFieldType.Int; - break; - - case HeaderMetadata.Season: - option.Column = (i, r) => this.GetEpisode(i); - option.Header.ItemViewType = ItemViewType.Detail; - option.Header.SortField = "SortName"; - break; - - case HeaderMetadata.SeasonNumber: - option.Column = (i, r) => this.GetObject<Season, string>(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString()); - option.Header.SortField = "IndexNumber"; - option.Header.HeaderFieldType = ReportFieldType.Int; - break; - - case HeaderMetadata.Series: - option.Column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName); - option.Header.ItemViewType = ItemViewType.Detail; - option.Header.SortField = "SeriesSortName,SortName"; - break; - - case HeaderMetadata.EpisodeSeries: - option.Column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName); - option.Header.ItemViewType = ItemViewType.Detail; - option.ItemID = (i) => - { - Series series = this.GetObject<Episode, Series>(i, (x) => x.Series); - if (series == null) - return string.Empty; - return series.Id; - }; - option.Header.SortField = "SeriesSortName,SortName"; - internalHeader = HeaderMetadata.Series; - break; - - case HeaderMetadata.EpisodeSeason: - option.Column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName); - option.Header.ItemViewType = ItemViewType.Detail; - option.ItemID = (i) => - { - Season season = this.GetObject<Episode, Season>(i, (x) => x.Season); - if (season == null) - return string.Empty; - return season.Id; - }; - option.Header.SortField = "SortName"; - internalHeader = HeaderMetadata.Season; - break; - - case HeaderMetadata.EpisodeNumber: - option.Column = (i, r) => this.GetObject<BaseItem, string>(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString()); - //option.Header.SortField = "IndexNumber"; - //option.Header.HeaderFieldType = ReportFieldType.Int; - break; - - case HeaderMetadata.Network: - option.Column = (i, r) => this.GetListAsString(i.Studios.ToList()); - option.ItemID = (i) => this.GetStudioID(i.Studios.FirstOrDefault()); - option.Header.ItemViewType = ItemViewType.ItemByNameDetails; - option.Header.SortField = "Studio,SortName"; - break; - - case HeaderMetadata.Year: - option.Column = (i, r) => this.GetSeriesProductionYear(i); - option.Header.SortField = "ProductionYear,PremiereDate,SortName"; - break; - - case HeaderMetadata.ParentalRating: - option.Column = (i, r) => i.OfficialRating; - option.Header.SortField = "OfficialRating,SortName"; - break; - - case HeaderMetadata.CommunityRating: - option.Column = (i, r) => i.CommunityRating; - option.Header.SortField = "CommunityRating,SortName"; - break; - - case HeaderMetadata.Trailers: - option.Column = (i, r) => this.GetBoolString(r.HasLocalTrailer); - option.Header.ItemViewType = ItemViewType.TrailersImage; - break; - - case HeaderMetadata.Specials: - option.Column = (i, r) => this.GetBoolString(r.HasSpecials); - option.Header.ItemViewType = ItemViewType.SpecialsImage; - break; - - case HeaderMetadata.GameSystem: - option.Column = (i, r) => this.GetObject<Game, string>(i, (x) => x.GameSystem); - option.Header.SortField = "GameSystem,SortName"; - break; - - case HeaderMetadata.Players: - option.Column = (i, r) => this.GetObject<Game, int?>(i, (x) => x.PlayersSupported); - option.Header.SortField = "Players,GameSystem,SortName"; - break; - - case HeaderMetadata.AlbumArtist: - option.Column = (i, r) => this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist); - option.ItemID = (i) => this.GetPersonID(this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist)); - option.Header.ItemViewType = ItemViewType.Detail; - option.Header.SortField = "AlbumArtist,Album,SortName"; - - break; - case HeaderMetadata.MusicArtist: - option.Column = (i, r) => this.GetObject<MusicArtist, string>(i, (x) => x.GetLookupInfo().Name); - option.Header.ItemViewType = ItemViewType.Detail; - option.Header.SortField = "AlbumArtist,Album,SortName"; - internalHeader = HeaderMetadata.AlbumArtist; - break; - case HeaderMetadata.AudioAlbumArtist: - option.Column = (i, r) => this.GetListAsString(this.GetObject<Audio, List<string>>(i, (x) => x.AlbumArtists.ToList())); - option.Header.SortField = "AlbumArtist,Album,SortName"; - internalHeader = HeaderMetadata.AlbumArtist; - break; - - case HeaderMetadata.AudioAlbum: - option.Column = (i, r) => this.GetObject<Audio, string>(i, (x) => x.Album); - option.Header.SortField = "Album,SortName"; - internalHeader = HeaderMetadata.Album; - break; - - case HeaderMetadata.Disc: - option.Column = (i, r) => i.ParentIndexNumber; - break; - - case HeaderMetadata.Track: - option.Column = (i, r) => i.IndexNumber; - break; - - case HeaderMetadata.Tracks: - option.Column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.Cast<Audio>().ToList(), new List<Audio>()).Count(); - break; - - case HeaderMetadata.Audio: - option.Column = (i, r) => this.GetAudioStream(i); - break; - - case HeaderMetadata.EmbeddedImage: - break; - - case HeaderMetadata.Video: - option.Column = (i, r) => this.GetVideoStream(i); - break; - - case HeaderMetadata.Resolution: - option.Column = (i, r) => this.GetVideoResolution(i); - break; - - case HeaderMetadata.Subtitles: - option.Column = (i, r) => this.GetBoolString(r.HasSubtitles); - option.Header.ItemViewType = ItemViewType.SubtitleImage; - break; - - case HeaderMetadata.Genres: - option.Column = (i, r) => this.GetListAsString(i.Genres); - break; - - } - - option.Header.Name = GetLocalizedHeader(internalHeader); - option.Header.FieldName = header; - - return option; - } - - /// <summary> Gets report rows. </summary> - /// <param name="items"> The items. </param> - /// <param name="options"> Options for controlling the operation. </param> - /// <returns> The report rows. </returns> - private List<ReportRow> GetReportRows(IEnumerable<BaseItem> items, List<ReportOptions<BaseItem>> options) - { - var rows = new List<ReportRow>(); - - foreach (BaseItem item in items) - { - ReportRow rRow = GetRow(item); - foreach (ReportOptions<BaseItem> option in options) - { - object itemColumn = option.Column != null ? option.Column(item, rRow) : ""; - object itemId = option.ItemID != null ? option.ItemID(item) : ""; - ReportItem rItem = new ReportItem - { - Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType), - Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object) - }; - rRow.Columns.Add(rItem); - } - - rows.Add(rRow); - } - - return rows; - } - - /// <summary> Gets a row. </summary> - /// <param name="item"> The item. </param> - /// <returns> The row. </returns> - private ReportRow GetRow(BaseItem item) - { - var hasTrailers = item as IHasTrailers; - var hasSpecialFeatures = item as IHasSpecialFeatures; - var video = item as Video; - ReportRow rRow = new ReportRow - { - Id = item.Id.ToString("N"), - HasLockData = item.IsLocked, - HasLocalTrailer = hasTrailers != null ? hasTrailers.GetTrailerIds().Count() > 0 : false, - HasImageTagsPrimary = item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0, - HasImageTagsBackdrop = item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0, - HasImageTagsLogo = item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Logo) > 0, - HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Length > 0 : false, - HasSubtitles = video != null ? video.HasSubtitles : false, - RowType = ReportHelper.GetRowType(item.GetClientTypeName()) - }; - return rRow; - } - - #endregion - - } -} diff --git a/MediaBrowser.Api/Reports/Data/ReportExport.cs b/MediaBrowser.Api/Reports/Data/ReportExport.cs deleted file mode 100644 index 6d751e030..000000000 --- a/MediaBrowser.Api/Reports/Data/ReportExport.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System.Linq; -using System.Text; - -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report export. </summary> - public class ReportExport - { - /// <summary> Export to CSV. </summary> - /// <param name="reportResult"> The report result. </param> - /// <returns> A string. </returns> - public string ExportToCsv(ReportResult reportResult) - { - StringBuilder returnValue = new StringBuilder(); - - returnValue.AppendLine(string.Join(";", reportResult.Headers.Select(s => s.Name.Replace(',', ' ')).ToArray())); - - if (reportResult.IsGrouped) - foreach (ReportGroup group in reportResult.Groups) - { - foreach (ReportRow row in reportResult.Rows) - { - returnValue.AppendLine(string.Join(";", row.Columns.Select(s => s.Name.Replace(',', ' ')).ToArray())); - } - } - else - foreach (ReportRow row in reportResult.Rows) - { - returnValue.AppendLine(string.Join(";", row.Columns.Select(s => s.Name.Replace(',', ' ')).ToArray())); - } - - return returnValue.ToString(); - } - - - /// <summary> Export to excel. </summary> - /// <param name="reportResult"> The report result. </param> - /// <returns> A string. </returns> - public string ExportToExcel(ReportResult reportResult) - { - - string style = @"<style type='text/css'> - BODY { - font-family: Arial; - font-size: 12px; - } - - TABLE { - font-family: Arial; - font-size: 12px; - } - - A { - font-family: Arial; - color: #144A86; - font-size: 12px; - cursor: pointer; - text-decoration: none; - font-weight: bold; - } - DIV { - font-family: Arial; - font-size: 12px; - margin-bottom: 0px; - } - P, LI, DIV { - font-size: 12px; - margin-bottom: 0px; - } - - P, UL { - font-size: 12px; - margin-bottom: 6px; - margin-top: 0px; - } - - H1 { - font-size: 18pt; - } - - H2 { - font-weight: bold; - font-size: 14pt; - COLOR: #C0C0C0; - } - - H3 { - font-weight: normal; - font-size: 14pt; - text-indent: +1em; - } - - H4 { - font-size: 10pt; - font-weight: normal; - } - - H5 { - font-size: 10pt; - font-weight: normal; - background: #A9A9A9; - COLOR: white; - display: inline; - } - - H6 { - padding: 2 1 2 5; - font-size: 11px; - font-weight: bold; - text-decoration: none; - margin-bottom: 1px; - } - - UL { - line-height: 1.5em; - list-style-type: disc; - } - - OL { - line-height: 1.5em; - } - - LI { - line-height: 1.5em; - } - - A IMG { - border: 0; - } - - table.gridtable { - color: #333333; - border-width: 0.1pt; - border-color: #666666; - border-collapse: collapse; - } - - table.gridtable th { - border-width: 0.1pt; - padding: 8px; - border-style: solid; - border-color: #666666; - background-color: #dedede; - } - table.gridtable tr { - background-color: #ffffff; - } - table.gridtable td { - border-width: 0.1pt; - padding: 8px; - border-style: solid; - border-color: #666666; - background-color: #ffffff; - } - </style>"; - - string Html = @"<!DOCTYPE html> - <html xmlns='http://www.w3.org/1999/xhtml'> - <head> - <meta http-equiv='X-UA-Compatible' content='IE=8, IE=9, IE=10' /> - <meta charset='utf-8'> - <title>Emby Reports Export</title>"; - Html += "\n" + style + "\n"; - Html += "</head>\n"; - Html += "<body>\n"; - - StringBuilder returnValue = new StringBuilder(); - returnValue.AppendLine("<table class='gridtable'>"); - returnValue.AppendLine("<tr>"); - returnValue.AppendLine(string.Join("", reportResult.Headers.Select(s => string.Format("<th>{0}</th>", s.Name)).ToArray())); - returnValue.AppendLine("</tr>"); - if (reportResult.IsGrouped) - foreach (ReportGroup group in reportResult.Groups) - { - returnValue.AppendLine("<tr>"); - returnValue.AppendLine("<th scope='rowgroup' colspan='" + reportResult.Headers.Count + "'>" + (string.IsNullOrEmpty(group.Name) ? " " : group.Name) + "</th>"); - returnValue.AppendLine("</tr>"); - foreach (ReportRow row in group.Rows) - { - ExportToExcelRow(reportResult, returnValue, row); - } - returnValue.AppendLine("<tr>"); - returnValue.AppendLine("<th style='background-color: #ffffff;' scope='rowgroup' colspan='" + reportResult.Headers.Count + "'>" + " " + "</th>"); - returnValue.AppendLine("</tr>"); - } - - else - foreach (ReportRow row in reportResult.Rows) - { - ExportToExcelRow(reportResult, returnValue, row); - } - returnValue.AppendLine("</table>"); - - Html += returnValue.ToString(); - Html += "</body>"; - Html += "</html>"; - return Html; - } - private static void ExportToExcelRow(ReportResult reportResult, - StringBuilder returnValue, - ReportRow row) - { - returnValue.AppendLine("<tr>"); - returnValue.AppendLine(string.Join("", row.Columns.Select(s => string.Format("<td>{0}</td>", s.Name)).ToArray())); - returnValue.AppendLine("</tr>"); - } - } - -} diff --git a/MediaBrowser.Api/Reports/Data/ReportOptions.cs b/MediaBrowser.Api/Reports/Data/ReportOptions.cs deleted file mode 100644 index fae91fca3..000000000 --- a/MediaBrowser.Api/Reports/Data/ReportOptions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace MediaBrowser.Api.Reports -{ - - /// <summary> A report options. </summary> - public class ReportOptions<I> - { - /// <summary> Initializes a new instance of the ReportOptions class. </summary> - public ReportOptions() - { - } - - /// <summary> Initializes a new instance of the ReportOptions class. </summary> - /// <param name="header"> . </param> - /// <param name="row"> . </param> - public ReportOptions(ReportHeader header, Func<I, ReportRow, object> column) - { - Header = header; - Column = column; - } - - /// <summary> - /// Initializes a new instance of the ReportOptions class. - /// </summary> - /// <param name="header"></param> - /// <param name="column"></param> - /// <param name="itemID"></param> - public ReportOptions(ReportHeader header, Func<I, ReportRow, object> column, Func<I, object> itemID) - { - Header = header; - Column = column; - ItemID = itemID; - } - - /// <summary> Gets or sets the header. </summary> - /// <value> The header. </value> - public ReportHeader Header { get; set; } - - /// <summary> Gets or sets the column. </summary> - /// <value> The column. </value> - public Func<I, ReportRow, object> Column { get; set; } - - /// <summary> Gets or sets the identifier of the item. </summary> - /// <value> The identifier of the item. </value> - public Func<I, object> ItemID { get; set; } - } -} diff --git a/MediaBrowser.Api/Reports/Model/ReportGroup.cs b/MediaBrowser.Api/Reports/Model/ReportGroup.cs deleted file mode 100644 index 06ffa9082..000000000 --- a/MediaBrowser.Api/Reports/Model/ReportGroup.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Api.Reports -{ - - /// <summary> A report group. </summary> - public class ReportGroup - { - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportGroup class. </summary> - public ReportGroup() - { - Rows = new List<ReportRow>(); - } - - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportGroup class. </summary> - /// <param name="rows"> The rows. </param> - public ReportGroup(List<ReportRow> rows) - { - Rows = rows; - } - - /// <summary> Gets or sets the name. </summary> - /// <value> The name. </value> - public string Name { get; set; } - - /// <summary> Gets or sets the rows. </summary> - /// <value> The rows. </value> - public List<ReportRow> Rows { get; set; } - - /// <summary> Returns a string that represents the current object. </summary> - /// <returns> A string that represents the current object. </returns> - /// <seealso cref="M:System.Object.ToString()"/> - public override string ToString() - { - return Name; - } - } -} diff --git a/MediaBrowser.Api/Reports/Model/ReportHeader.cs b/MediaBrowser.Api/Reports/Model/ReportHeader.cs deleted file mode 100644 index f08339037..000000000 --- a/MediaBrowser.Api/Reports/Model/ReportHeader.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report header. </summary> - public class ReportHeader - { - /// <summary> Initializes a new instance of the ReportHeader class. </summary> - public ReportHeader() - { - ItemViewType = ItemViewType.None; - Visible = true; - CanGroup = true; - ShowHeaderLabel = true; - DisplayType = ReportDisplayType.ScreenExport; - } - - /// <summary> Gets or sets the type of the header field. </summary> - /// <value> The type of the header field. </value> - public ReportFieldType HeaderFieldType { get; set; } - - /// <summary> Gets or sets the name of the header. </summary> - /// <value> The name of the header. </value> - public string Name { get; set; } - - /// <summary> Gets or sets the name of the field. </summary> - /// <value> The name of the field. </value> - public HeaderMetadata FieldName { get; set; } - - /// <summary> Gets or sets the sort field. </summary> - /// <value> The sort field. </value> - public string SortField { get; set; } - - /// <summary> Gets or sets the type. </summary> - /// <value> The type. </value> - public string Type { get; set; } - - /// <summary> Gets or sets the type of the item view. </summary> - /// <value> The type of the item view. </value> - public ItemViewType ItemViewType { get; set; } - - /// <summary> Gets or sets a value indicating whether this object is visible. </summary> - /// <value> true if visible, false if not. </value> - public bool Visible { get; set; } - - /// <summary> Gets or sets the type of the display. </summary> - /// <value> The type of the display. </value> - public ReportDisplayType DisplayType { get; set; } - - /// <summary> Gets or sets a value indicating whether the header label is shown. </summary> - /// <value> true if show header label, false if not. </value> - public bool ShowHeaderLabel { get; set; } - - /// <summary> Gets or sets a value indicating whether we can group. </summary> - /// <value> true if we can group, false if not. </value> - public bool CanGroup { get; set; } - - } -} diff --git a/MediaBrowser.Api/Reports/Model/ReportItem.cs b/MediaBrowser.Api/Reports/Model/ReportItem.cs deleted file mode 100644 index 8d53dd9a1..000000000 --- a/MediaBrowser.Api/Reports/Model/ReportItem.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - /// <summary> A report item. </summary> - public class ReportItem - { - /// <summary> Gets or sets the identifier. </summary> - /// <value> The identifier. </value> - public string Id { get; set; } - - /// <summary> Gets or sets the name. </summary> - /// <value> The name. </value> - public string Name { get; set; } - - public string Image { get; set; } - - /// <summary> Gets or sets the custom tag. </summary> - /// <value> The custom tag. </value> - public string CustomTag { get; set; } - - /// <summary> Returns a string that represents the current object. </summary> - /// <returns> A string that represents the current object. </returns> - /// <seealso cref="M:System.Object.ToString()"/> - public override string ToString() - { - return Name; - } - } -} diff --git a/MediaBrowser.Api/Reports/Model/ReportResult.cs b/MediaBrowser.Api/Reports/Model/ReportResult.cs deleted file mode 100644 index a4bc95aa1..000000000 --- a/MediaBrowser.Api/Reports/Model/ReportResult.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Api.Reports -{ - - /// <summary> Encapsulates the result of a report. </summary> - public class ReportResult - { - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportResult class. </summary> - public ReportResult() - { - Rows = new List<ReportRow>(); - Headers = new List<ReportHeader>(); - Groups = new List<ReportGroup>(); - TotalRecordCount = 0; - IsGrouped = false; - } - - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportResult class. </summary> - /// <param name="headers"> The headers. </param> - /// <param name="rows"> The rows. </param> - public ReportResult(List<ReportHeader> headers, List<ReportRow> rows) - { - Rows = rows; - Headers = headers; - TotalRecordCount = 0; - } - - /// <summary> Gets or sets the rows. </summary> - /// <value> The rows. </value> - public List<ReportRow> Rows { get; set; } - - /// <summary> Gets or sets the headers. </summary> - /// <value> The headers. </value> - public List<ReportHeader> Headers { get; set; } - - /// <summary> Gets or sets the groups. </summary> - /// <value> The groups. </value> - public List<ReportGroup> Groups { get; set; } - - - /// <summary> Gets or sets the number of total records. </summary> - /// <value> The total number of record count. </value> - public int TotalRecordCount { get; set; } - - /// <summary> Gets or sets the is grouped. </summary> - /// <value> The is grouped. </value> - public bool IsGrouped { get; set; } - - } -} diff --git a/MediaBrowser.Api/Reports/Model/ReportRow.cs b/MediaBrowser.Api/Reports/Model/ReportRow.cs deleted file mode 100644 index e8e26a053..000000000 --- a/MediaBrowser.Api/Reports/Model/ReportRow.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Api.Reports -{ - public class ReportRow - { - /// <summary> - /// Initializes a new instance of the ReportRow class. - /// </summary> - public ReportRow() - { - Columns = new List<ReportItem>(); - } - - /// <summary> Gets or sets the identifier. </summary> - /// <value> The identifier. </value> - public string Id { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this object has backdrop image. </summary> - /// <value> true if this object has backdrop image, false if not. </value> - public bool HasImageTagsBackdrop { get; set; } - - /// <summary> Gets or sets a value indicating whether this object has image tags. </summary> - /// <value> true if this object has image tags, false if not. </value> - public bool HasImageTagsPrimary { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this object has image tags logo. </summary> - /// <value> true if this object has image tags logo, false if not. </value> - public bool HasImageTagsLogo { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this object has local trailer. </summary> - /// <value> true if this object has local trailer, false if not. </value> - public bool HasLocalTrailer { get; set; } - - /// <summary> Gets or sets a value indicating whether this object has lock data. </summary> - /// <value> true if this object has lock data, false if not. </value> - public bool HasLockData { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this object has embedded image. </summary> - /// <value> true if this object has embedded image, false if not. </value> - public bool HasEmbeddedImage { get; set; } - - /// <summary> Gets or sets a value indicating whether this object has subtitles. </summary> - /// <value> true if this object has subtitles, false if not. </value> - public bool HasSubtitles { get; set; } - - /// <summary> Gets or sets a value indicating whether this object has specials. </summary> - /// <value> true if this object has specials, false if not. </value> - public bool HasSpecials { get; set; } - - /// <summary> Gets or sets the columns. </summary> - /// <value> The columns. </value> - public List<ReportItem> Columns { get; set; } - - /// <summary> Gets or sets the type. </summary> - /// <value> The type. </value> - public ReportIncludeItemTypes RowType { get; set; } - - /// <summary> Gets or sets the identifier of the user. </summary> - /// <value> The identifier of the user. </value> - public string UserId { get; set; } - } -} diff --git a/MediaBrowser.Api/Reports/ReportRequests.cs b/MediaBrowser.Api/Reports/ReportRequests.cs deleted file mode 100644 index 362795658..000000000 --- a/MediaBrowser.Api/Reports/ReportRequests.cs +++ /dev/null @@ -1,194 +0,0 @@ -using MediaBrowser.Api.UserLibrary; -using System.Collections.Generic; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Reports -{ - public interface IReportsDownload : IReportsQuery - { - /// <summary> Gets or sets the minimum date. </summary> - /// <value> The minimum date. </value> - string MinDate { get; set; } - } - - /// <summary> Interface for reports query. </summary> - public interface IReportsQuery : IReportsHeader - { - /// <summary> - /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.GetActivityLogs has - /// query limit. </summary> - /// <value> - /// true if this MediaBrowser.Api.Reports.GetActivityLogs has query limit, false if not. </value> - bool HasQueryLimit { get; set; } - /// <summary> Gets or sets who group this MediaBrowser.Api.Reports.GetActivityLogs. </summary> - /// <value> Describes who group this MediaBrowser.Api.Reports.GetActivityLogs. </value> - string GroupBy { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - int? StartIndex { get; set; } - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - int? Limit { get; set; } - - } - public interface IReportsHeader - { - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - string ReportView { get; set; } - - /// <summary> Gets or sets the report columns. </summary> - /// <value> The report columns. </value> - string ReportColumns { get; set; } - - /// <summary> Gets or sets a list of types of the include items. </summary> - /// <value> A list of types of the include items. </value> - string IncludeItemTypes { get; set; } - - /// <summary> Gets or sets a list of types of the displays. </summary> - /// <value> A list of types of the displays. </value> - string DisplayType { get; set; } - - } - - public class BaseReportRequest : BaseItemsRequest, IReportsQuery - { - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ReportView { get; set; } - - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DisplayType { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.BaseReportRequest has - /// query limit. </summary> - /// <value> - /// true if this MediaBrowser.Api.Reports.BaseReportRequest has query limit, false if not. </value> - [ApiMember(Name = "HasQueryLimit", Description = "Optional. If specified, results will include all records.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool HasQueryLimit { get; set; } - - /// <summary> - /// Gets or sets who group this MediaBrowser.Api.Reports.BaseReportRequest. </summary> - /// <value> Describes who group this MediaBrowser.Api.Reports.BaseReportRequest. </value> - [ApiMember(Name = "GroupBy", Description = "Optional. If specified, results will include grouped records.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string GroupBy { get; set; } - - /// <summary> Gets or sets the report columns. </summary> - /// <value> The report columns. </value> - [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ReportColumns { get; set; } - - - } - - [Route("/Reports/Items", "GET", Summary = "Gets reports based on library items")] - public class GetItemReport : BaseReportRequest, IReturn<ReportResult> - { - - } - - [Route("/Reports/Headers", "GET", Summary = "Gets reports headers based on library items")] - public class GetReportHeaders : IReturn<List<ReportHeader>>, IReportsHeader - { - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ReportView { get; set; } - - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DisplayType { get; set; } - - /// <summary> Gets or sets a list of types of the include items. </summary> - /// <value> A list of types of the include items. </value> - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - - /// <summary> Gets or sets the report columns. </summary> - /// <value> The report columns. </value> - [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ReportColumns { get; set; } - } - - [Route("/Reports/Items/Download", "GET", Summary = "Downloads report")] - public class GetReportDownload : BaseReportRequest, IReportsDownload - { - public GetReportDownload() - { - ExportType = ReportExportType.CSV; - } - - public ReportExportType ExportType { get; set; } - - /// <summary> Gets or sets the minimum date. </summary> - /// <value> The minimum date. </value> - [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MinDate { get; set; } - - } - - - [Route("/Reports/Activities", "GET", Summary = "Gets activities entries")] - public class GetActivityLogs : IReturn<ReportResult>, IReportsQuery, IReportsDownload - { - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ReportView { get; set; } - - /// <summary> Gets or sets the report view. </summary> - /// <value> The report view. </value> - [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DisplayType { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.GetActivityLogs has - /// query limit. </summary> - /// <value> - /// true if this MediaBrowser.Api.Reports.GetActivityLogs has query limit, false if not. </value> - [ApiMember(Name = "HasQueryLimit", Description = "Optional. If specified, results will include all records.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool HasQueryLimit { get; set; } - - /// <summary> Gets or sets who group this MediaBrowser.Api.Reports.GetActivityLogs. </summary> - /// <value> Describes who group this MediaBrowser.Api.Reports.GetActivityLogs. </value> - [ApiMember(Name = "GroupBy", Description = "Optional. If specified, results will include grouped records.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string GroupBy { get; set; } - - /// <summary> Gets or sets the report columns. </summary> - /// <value> The report columns. </value> - [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ReportColumns { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> Gets or sets the minimum date. </summary> - /// <value> The minimum date. </value> - [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MinDate { get; set; } - - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - } -} diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs deleted file mode 100644 index 76d282990..000000000 --- a/MediaBrowser.Api/Reports/ReportsService.cs +++ /dev/null @@ -1,421 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Querying; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Globalization; -using System.Linq; -using System; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Globalization; - -namespace MediaBrowser.Api.Reports -{ - /// <summary> The reports service. </summary> - /// <seealso cref="T:MediaBrowser.Api.BaseApiService"/> - public class ReportsService : BaseApiService - { - #region [Constructors] - - /// <summary> - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. </summary> - /// <param name="userManager"> Manager for user. </param> - /// <param name="libraryManager"> Manager for library. </param> - /// <param name="localization"> The localization. </param> - /// <param name="activityManager"> Manager for activity. </param> - public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IActivityManager activityManager, IActivityRepository repo) - { - _userManager = userManager; - _libraryManager = libraryManager; - _localization = localization; - _activityManager = activityManager; - _repo = repo; - } - - #endregion - - #region [Private Fields] - - private readonly IActivityManager _activityManager; ///< Manager for activity - - /// <summary> Manager for library. </summary> - private readonly ILibraryManager _libraryManager; ///< Manager for library - /// <summary> The localization. </summary> - - private readonly ILocalizationManager _localization; ///< The localization - - private readonly IActivityRepository _repo; - - /// <summary> Manager for user. </summary> - private readonly IUserManager _userManager; ///< Manager for user - - #endregion - - #region [Public Methods] - - /// <summary> Gets the given request. </summary> - /// <param name="request"> The request. </param> - /// <returns> A Task<object> </returns> - public object Get(GetActivityLogs request) - { - request.DisplayType = "Screen"; - ReportResult result = GetReportActivities(request); - return ToOptimizedResult(result); - } - - /// <summary> Gets the given request. </summary> - /// <param name="request"> The request. </param> - /// <returns> A Task<object> </returns> - public object Get(GetReportHeaders request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - - request.DisplayType = "Screen"; - ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView); - - List<ReportHeader> result = new List<ReportHeader>(); - switch (reportViewType) - { - case ReportViewType.ReportData: - ReportBuilder dataBuilder = new ReportBuilder(_libraryManager); - result = dataBuilder.GetHeaders(request); - break; - case ReportViewType.ReportActivities: - ReportActivitiesBuilder activityBuilder = new ReportActivitiesBuilder(_libraryManager, _userManager); - result = activityBuilder.GetHeaders(request); - break; - } - - return ToOptimizedResult(result); - - } - - /// <summary> Gets the given request. </summary> - /// <param name="request"> The request. </param> - /// <returns> A Task<object> </returns> - public object Get(GetItemReport request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - - request.DisplayType = "Screen"; - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; - var reportResult = GetReportResult(request, user); - - return ToOptimizedResult(reportResult); - } - - /// <summary> Gets the given request. </summary> - /// <param name="request"> The request. </param> - /// <returns> A Task<object> </returns> - public object Get(GetReportDownload request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - - request.DisplayType = "Export"; - ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView); - var headers = new Dictionary<string, string>(); - string fileExtension = "csv"; - string contentType = "text/plain;charset='utf-8'"; - - switch (request.ExportType) - { - case ReportExportType.CSV: - break; - case ReportExportType.Excel: - contentType = "application/vnd.ms-excel"; - fileExtension = "xls"; - break; - } - - var filename = "ReportExport." + fileExtension; - 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) - { - case ReportViewType.ReportData: - ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - ReportBuilder dataBuilder = new ReportBuilder(_libraryManager); - QueryResult<BaseItem> queryResult = GetQueryResult(request, user); - result = dataBuilder.GetResult(queryResult.Items, request); - result.TotalRecordCount = queryResult.TotalRecordCount; - break; - case ReportViewType.ReportActivities: - result = GetReportActivities(request); - break; - } - - string returnResult = string.Empty; - switch (request.ExportType) - { - case ReportExportType.CSV: - returnResult = new ReportExport().ExportToCsv(result); - break; - case ReportExportType.Excel: - returnResult = new ReportExport().ExportToExcel(result); - break; - } - - return ResultFactory.GetResult(returnResult, contentType, headers); - } - - #endregion - - private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user) - { - var query = new InternalItemsQuery(user) - { - IsPlayed = request.IsPlayed, - MediaTypes = request.GetMediaTypes(), - IncludeItemTypes = request.GetIncludeItemTypes(), - ExcludeItemTypes = request.GetExcludeItemTypes(), - Recursive = request.Recursive, - SortBy = request.GetOrderBy(), - SortOrder = request.SortOrder ?? SortOrder.Ascending, - - IsFavorite = request.IsFavorite, - Limit = request.Limit, - StartIndex = request.StartIndex, - IsMissing = request.IsMissing, - IsUnaired = request.IsUnaired, - CollapseBoxSetItems = request.CollapseBoxSetItems, - NameLessThan = request.NameLessThan, - NameStartsWith = request.NameStartsWith, - NameStartsWithOrGreater = request.NameStartsWithOrGreater, - HasImdbId = request.HasImdbId, - IsPlaceHolder = request.IsPlaceHolder, - IsLocked = request.IsLocked, - IsInBoxSet = request.IsInBoxSet, - IsHD = request.IsHD, - Is3D = request.Is3D, - HasTvdbId = request.HasTvdbId, - HasTmdbId = request.HasTmdbId, - HasOverview = request.HasOverview, - HasOfficialRating = request.HasOfficialRating, - HasParentalRating = request.HasParentalRating, - HasSpecialFeature = request.HasSpecialFeature, - HasSubtitles = request.HasSubtitles, - HasThemeSong = request.HasThemeSong, - HasThemeVideo = request.HasThemeVideo, - HasTrailer = request.HasTrailer, - Tags = request.GetTags(), - OfficialRatings = request.GetOfficialRatings(), - Genres = request.GetGenres(), - GenreIds = request.GetGenreIds(), - StudioIds = request.GetStudioIds(), - Person = request.Person, - PersonIds = request.GetPersonIds(), - PersonTypes = request.GetPersonTypes(), - Years = request.GetYears(), - ImageTypes = request.GetImageTypes().ToArray(), - VideoTypes = request.GetVideoTypes().ToArray(), - AdjacentTo = request.AdjacentTo, - ItemIds = request.GetItemIds(), - MinPlayers = request.MinPlayers, - 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, - EnableTotalRecordCount = request.EnableTotalRecordCount - }; - - if (!string.IsNullOrWhiteSpace(request.Ids)) - { - query.CollapseBoxSetItems = false; - } - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - if (!string.IsNullOrEmpty(request.MinPremiereDate)) - { - query.MinPremiereDate = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MaxPremiereDate)) - { - query.MaxPremiereDate = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - // Filter by Series Status - if (!string.IsNullOrEmpty(request.SeriesStatus)) - { - query.SeriesStatuses = request.SeriesStatus.Split(',').Select(d => (SeriesStatus)Enum.Parse(typeof(SeriesStatus), d, true)).ToArray(); - } - - // ExcludeLocationTypes - if (!string.IsNullOrEmpty(request.ExcludeLocationTypes)) - { - var excludeLocationTypes = request.ExcludeLocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray(); - if (excludeLocationTypes.Contains(LocationType.Virtual)) - { - query.IsVirtualItem = false; - } - } - - // Min official rating - if (!string.IsNullOrWhiteSpace(request.MinOfficialRating)) - { - query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating); - } - - // Max official rating - if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating)) - { - query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating); - } - - return query; - } - - private QueryResult<BaseItem> GetQueryResult(BaseReportRequest request, User user) - { - // all report queries currently need this because it's not being specified - request.Recursive = true; - - var item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : user.RootFolder : - _libraryManager.GetItemById(request.ParentId); - - if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)) - { - //item = user == null ? _libraryManager.RootFolder : user.RootFolder; - } - else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) - { - item = user == null ? _libraryManager.RootFolder : user.RootFolder; - } - - // Default list type = children - - var folder = item as Folder; - if (folder == null) - { - folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder(); - } - - if (!string.IsNullOrEmpty(request.Ids)) - { - request.Recursive = true; - var query = GetItemsQuery(request, user); - var result = folder.GetItems(query); - - if (string.IsNullOrWhiteSpace(request.SortBy)) - { - var ids = query.ItemIds.ToList(); - - // Try to preserve order - result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); - } - - return result; - } - - if (request.Recursive) - { - return folder.GetItems(GetItemsQuery(request, user)); - } - - if (user == null) - { - return folder.GetItems(GetItemsQuery(request, null)); - } - - var userRoot = item as UserRootFolder; - - if (userRoot == null) - { - return folder.GetItems(GetItemsQuery(request, user)); - } - - 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 ReportResult GetReportActivities(IReportsDownload request) - { - 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 ReportResult GetReportResult(GetItemReport request, User user) - { - ReportBuilder reportBuilder = new ReportBuilder(_libraryManager); - QueryResult<BaseItem> queryResult = GetQueryResult(request, user); - ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request); - reportResult.TotalRecordCount = queryResult.TotalRecordCount; - - return reportResult; - } - - #endregion - - } -} diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 48765e698..0b5eaa476 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -71,7 +71,7 @@ namespace MediaBrowser.Api /// </summary> public static class SimilarItemsHelper { - internal static async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore) + internal static QueryResult<BaseItemDto> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null; @@ -104,7 +104,7 @@ namespace MediaBrowser.Api returnItems = returnItems.Take(request.Limit.Value).ToList(); } - var dtos = await dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ConfigureAwait(false); + var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); return new QueryResult<BaseItemDto> { diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 7a75aeb4b..22f1f3b62 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -67,13 +67,10 @@ namespace MediaBrowser.Api _config.SaveConfiguration(); } - public async Task<object> Get(GetStartupInfo request) + public object Get(GetStartupInfo request) { - var info = await _appHost.GetSystemInfo().ConfigureAwait(false); - return new StartupInfo { - SupportsRunningAsService = info.SupportsRunningAsService, HasMediaEncoder = !string.IsNullOrWhiteSpace(_mediaEncoder.EncoderPath) }; } @@ -92,7 +89,6 @@ namespace MediaBrowser.Api private void SetWizardFinishValues(ServerConfiguration config) { - config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; config.SkipDeserializationForBasicTypes = true; config.EnableLocalizedGuids = true; @@ -152,7 +148,6 @@ namespace MediaBrowser.Api public class StartupInfo { - public bool SupportsRunningAsService { get; set; } public bool HasMediaEncoder { get; set; } } diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 931772e6a..3b918d8a2 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -5,8 +5,10 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using System; +using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api @@ -47,21 +49,21 @@ namespace MediaBrowser.Api _libraryManager = libraryManager; } - public async Task<object> Get(GetSuggestedItems request) + public object Get(GetSuggestedItems request) { - var result = await GetResultItems(request).ConfigureAwait(false); + var result = GetResultItems(request); return ToOptimizedResult(result); } - private async Task<QueryResult<BaseItemDto>> GetResultItems(GetSuggestedItems request) + private QueryResult<BaseItemDto> GetResultItems(GetSuggestedItems request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var dtoOptions = GetDtoOptions(_authContext, request); var result = GetItems(request, user, dtoOptions); - var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false); + var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user); if (dtoList == null) { @@ -79,7 +81,7 @@ namespace MediaBrowser.Api { return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { - SortBy = new string[] { ItemSortBy.Random }, + OrderBy = new[] { ItemSortBy.Random }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), MediaTypes = request.GetMediaTypes(), IncludeItemTypes = request.GetIncludeItemTypes(), IsVirtualItem = false, diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index edb9f063d..d850b2ce7 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Api.System /// Class RestartApplication /// </summary> [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] - [Authenticated(Roles = "Admin")] + [Authenticated(Roles = "Admin", AllowLocal = true)] public class RestartApplication { } @@ -52,10 +52,9 @@ namespace MediaBrowser.Api.System /// This is currently not authenticated because the uninstaller needs to be able to shutdown the server. /// </summary> [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] + [Authenticated(Roles = "Admin", AllowLocal = true)] public class ShutdownApplication { - // TODO: This is not currently authenticated due to uninstaller - // Improve later } [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] @@ -126,7 +125,7 @@ namespace MediaBrowser.Api.System } catch (IOException) { - files = new FileSystemMetadata[]{}; + files = new FileSystemMetadata[] { }; } var result = files.Select(i => new LogFile @@ -149,6 +148,12 @@ namespace MediaBrowser.Api.System var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath) .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase)); + // For older files, assume fully static + if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1)) + { + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.Read); + } + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.ReadWrite); } @@ -187,11 +192,7 @@ namespace MediaBrowser.Api.System /// <param name="request">The request.</param> public void Post(RestartApplication request) { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Restart().ConfigureAwait(false); - }); + _appHost.Restart(); } /// <summary> diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 4b1e69c5d..fd81a9a3e 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -293,14 +293,14 @@ namespace MediaBrowser.Api /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public async Task<object> Get(GetSimilarShows request) + public object Get(GetSimilarShows request) { - var result = await GetSimilarItemsResult(request).ConfigureAwait(false); + var result = GetSimilarItemsResult(request); return ToOptimizedSerializedResultUsingCache(result); } - private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) + private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; @@ -322,7 +322,7 @@ namespace MediaBrowser.Api }); - var returnList = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)); + var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); var result = new QueryResult<BaseItemDto> { @@ -334,7 +334,7 @@ namespace MediaBrowser.Api return result; } - public async Task<object> Get(GetUpcomingEpisodes request) + public object Get(GetUpcomingEpisodes request) { var user = _userManager.GetUserById(request.UserId); @@ -347,8 +347,7 @@ namespace MediaBrowser.Api var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { "PremiereDate", "AirTime", "SortName" }, - SortOrder = SortOrder.Ascending, + OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), MinPremiereDate = minPremiereDate, StartIndex = request.StartIndex, Limit = request.Limit, @@ -358,7 +357,7 @@ namespace MediaBrowser.Api }); - var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user); var result = new QueryResult<BaseItemDto> { @@ -374,7 +373,7 @@ namespace MediaBrowser.Api /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public async Task<object> Get(GetNextUpEpisodes request) + public object Get(GetNextUpEpisodes request) { var options = GetDtoOptions(_authContext, request); @@ -390,7 +389,7 @@ namespace MediaBrowser.Api var user = _userManager.GetUserById(request.UserId); - var returnItems = (await _dtoService.GetBaseItemDtos(result.Items, options, user).ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user); return ToOptimizedSerializedResultUsingCache(new QueryResult<BaseItemDto> { @@ -423,7 +422,7 @@ namespace MediaBrowser.Api return items; } - public async Task<object> Get(GetSeasons request) + public object Get(GetSeasons request) { var user = _userManager.GetUserById(request.UserId); @@ -444,7 +443,7 @@ namespace MediaBrowser.Api var dtoOptions = GetDtoOptions(_authContext, request); - var returnItems = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false)); + var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user); return new QueryResult<BaseItemDto> { @@ -463,7 +462,7 @@ namespace MediaBrowser.Api return null; } - public async Task<object> Get(GetEpisodes request) + public object Get(GetEpisodes request) { var user = _userManager.GetUserById(request.UserId); @@ -544,7 +543,7 @@ namespace MediaBrowser.Api returnItems = ApplyPaging(episodes, request.StartIndex, request.Limit).ToList(); } - var dtos = (await _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ConfigureAwait(false)); + var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); return new QueryResult<BaseItemDto> { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index fca842289..ed0c4069b 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -299,7 +299,7 @@ namespace MediaBrowser.Api.UserLibrary var filteredItems = FilterItems(request, extractedItems, user); - filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending); + filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy()); var ibnItemsArray = filteredItems.ToList(); diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 66aa35de9..88d080db5 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -136,7 +136,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <value>The sort order.</value> [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public SortOrder? SortOrder { get; set; } + public string SortOrder { get; set; } /// <summary> /// Specify this to localize the search to a specific item or folder. Omit to use the root. @@ -467,16 +467,41 @@ namespace MediaBrowser.Api.UserLibrary /// Gets the order by. /// </summary> /// <returns>IEnumerable{ItemSortBy}.</returns> - public string[] GetOrderBy() + public Tuple<string, SortOrder>[] GetOrderBy() { - var val = SortBy; + return GetOrderBy(SortBy, SortOrder); + } + + public static Tuple<string, SortOrder>[] GetOrderBy(string sortBy, string requestedSortOrder) + { + var val = sortBy; if (string.IsNullOrEmpty(val)) { - return new string[] { }; + return new Tuple<string, Model.Entities.SortOrder>[] { }; + } + + var vals = val.Split(','); + if (string.IsNullOrWhiteSpace(requestedSortOrder)) + { + requestedSortOrder = "Ascending"; + } + + var sortOrders = requestedSortOrder.Split(','); + + var result = new Tuple<string, Model.Entities.SortOrder>[vals.Length]; + + for (var i = 0; i < vals.Length; i++) + { + var sortOrderIndex = sortOrders.Length > i ? i : 0; + + var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; + var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending; + + result[i] = new Tuple<string, Model.Entities.SortOrder>(vals[i], sortOrder); } - return val.Split(','); + return result; } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 9dd5aa565..5919c50d4 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -84,14 +84,14 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public async Task<object> Get(GetItems request) + public object Get(GetItems request) { if (request == null) { throw new ArgumentNullException("request"); } - var result = await GetItems(request).ConfigureAwait(false); + var result = GetItems(request); return ToOptimizedSerializedResultUsingCache(result); } @@ -100,8 +100,7 @@ namespace MediaBrowser.Api.UserLibrary /// Gets the items. /// </summary> /// <param name="request">The request.</param> - /// <returns>Task{ItemsResult}.</returns> - private async Task<QueryResult<BaseItemDto>> GetItems(GetItems request) + private QueryResult<BaseItemDto> GetItems(GetItems request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; @@ -119,7 +118,7 @@ namespace MediaBrowser.Api.UserLibrary throw new InvalidOperationException("GetItemsToSerialize result.Items returned null"); } - var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false); + var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user); if (dtoList == null) { @@ -199,8 +198,7 @@ namespace MediaBrowser.Api.UserLibrary IncludeItemTypes = request.GetIncludeItemTypes(), ExcludeItemTypes = request.GetExcludeItemTypes(), Recursive = request.Recursive, - SortBy = request.GetOrderBy(), - SortOrder = request.SortOrder ?? SortOrder.Ascending, + OrderBy = request.GetOrderBy(), IsFavorite = request.IsFavorite, Limit = request.Limit, @@ -410,6 +408,20 @@ namespace MediaBrowser.Api.UserLibrary }).Where(i => i != null).Select(i => i.Id.ToString("N")).ToArray(); } + // Apply default sorting if none requested + if (query.OrderBy.Length == 0) + { + // Albums by artist + if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase)) + { + query.OrderBy = new Tuple<string, SortOrder>[] + { + new Tuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending), + new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) + }; + } + } + return query; } } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 512356b43..ddefb08df 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -97,6 +97,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } + [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Pw { get; set; } + /// <summary> /// Gets or sets the password. /// </summary> @@ -125,6 +128,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "Password", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string Password { get; set; } + [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Pw { get; set; } + [ApiMember(Name = "PasswordMd5", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] public string PasswordMd5 { get; set; } } @@ -148,12 +154,16 @@ namespace MediaBrowser.Api /// <value>The password.</value> public string CurrentPassword { get; set; } + public string CurrentPw { get; set; } + /// <summary> /// Gets or sets the new password. /// </summary> /// <value>The new password.</value> public string NewPassword { get; set; } + public string NewPw { get; set; } + /// <summary> /// Gets or sets a value indicating whether [reset password]. /// </summary> @@ -180,6 +190,8 @@ namespace MediaBrowser.Api /// <value>The new password.</value> public string NewPassword { get; set; } + public string NewPw { get; set; } + /// <summary> /// Gets or sets a value indicating whether [reset password]. /// </summary> @@ -408,7 +420,8 @@ namespace MediaBrowser.Api return Post(new AuthenticateUserByName { Username = user.Name, - Password = request.Password + Password = request.Password, + Pw = request.Pw }); } @@ -422,6 +435,7 @@ namespace MediaBrowser.Api AppVersion = auth.Version, DeviceId = auth.DeviceId, DeviceName = auth.Device, + Password = request.Pw, PasswordSha1 = request.Password, PasswordMd5 = request.PasswordMd5, RemoteEndPoint = Request.RemoteIp, @@ -459,14 +473,14 @@ namespace MediaBrowser.Api } else { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPassword, Request.RemoteIp).ConfigureAwait(false); + var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, null, Request.RemoteIp).ConfigureAwait(false); if (success == null) { throw new ArgumentException("Invalid user or password entered."); } - _userManager.ChangePassword(user, request.NewPassword); + _userManager.ChangePassword(user, request.NewPw, request.NewPassword); var currentToken = _authContext.GetAuthorizationInfo(Request).Token; @@ -491,7 +505,7 @@ namespace MediaBrowser.Api } else { - _userManager.ChangeEasyPassword(user, request.NewPassword); + _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword); } } @@ -501,8 +515,6 @@ namespace MediaBrowser.Api /// <param name="request">The request.</param> public void Post(UpdateUser request) { - // We need to parse this manually because we told service stack not to with IRequiresRequestStream - // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs var id = GetPathValue(1); AssertCanUpdateUser(_authContext, _userManager, id, false); diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 2b5ae6799..dc0c9ac9b 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -42,6 +42,8 @@ namespace MediaBrowser.Common /// <value><c>true</c> if this instance has pending kernel reload; otherwise, <c>false</c>.</value> bool HasPendingRestart { get; } + bool IsShuttingDown { get; } + /// <summary> /// Gets a value indicating whether this instance can self restart. /// </summary> @@ -57,11 +59,11 @@ namespace MediaBrowser.Common /// Notifies the pending restart. /// </summary> void NotifyPendingRestart(); - + /// <summary> /// Restarts this instance. /// </summary> - Task Restart(); + void Restart(); /// <summary> /// Gets the application version. diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index ed941a447..0d7fb69cb 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -70,6 +70,7 @@ namespace MediaBrowser.Common.Net { _disposable.Dispose(); } + GC.SuppressFinalize(this); } } } diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index 4eabbc803..15257715f 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Common.Net /// <summary> /// Interface IHttpClient /// </summary> - public interface IHttpClient : IDisposable + public interface IHttpClient { /// <summary> /// Gets the response. diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 95e076096..73be04ac8 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -55,11 +55,15 @@ namespace MediaBrowser.Common.Plugins get { return typeof(TConfigurationType); } } - public void SetAttributes(string assemblyFilePath, string assemblyFileName, Version assemblyVersion, Guid assemblyId) + public void SetAttributes(string assemblyFilePath, string assemblyFileName, Version assemblyVersion) { AssemblyFilePath = assemblyFilePath; AssemblyFileName = assemblyFileName; Version = assemblyVersion; + } + + public void SetId(Guid assemblyId) + { Id = assemblyId; } @@ -78,7 +82,7 @@ namespace MediaBrowser.Common.Plugins /// Gets the unique id. /// </summary> /// <value>The unique id.</value> - public Guid Id { get; private set; } + public virtual Guid Id { get; private set; } /// <summary> /// Gets the plugin version @@ -284,6 +288,7 @@ namespace MediaBrowser.Common.Plugins public interface IPluginAssembly { - void SetAttributes(string assemblyFilePath, string assemblyFileName, Version assemblyVersion, Guid assemblyId); + void SetAttributes(string assemblyFilePath, string assemblyFileName, Version assemblyVersion); + void SetId(Guid assemblyId); } } diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs index 503f3f407..5b318c6a7 100644 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ b/MediaBrowser.Common/Progress/ActionableProgress.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Common.Progress public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index c6e750a0c..54faa1443 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -58,8 +58,7 @@ namespace MediaBrowser.Controller.Channels Limit = query.Limit, StartIndex = query.StartIndex, UserId = query.User.Id.ToString("N"), - SortBy = query.SortBy, - SortOrder = query.SortOrder + OrderBy = query.OrderBy }, new SimpleProgress<double>(), CancellationToken.None).Result; } diff --git a/MediaBrowser.Controller/Connect/IConnectManager.cs b/MediaBrowser.Controller/Connect/IConnectManager.cs index f899c7262..70bdc52e6 100644 --- a/MediaBrowser.Controller/Connect/IConnectManager.cs +++ b/MediaBrowser.Controller/Connect/IConnectManager.cs @@ -58,10 +58,7 @@ namespace MediaBrowser.Controller.Connect /// <summary> /// Authenticates the specified username. /// </summary> - /// <param name="username">The username.</param> - /// <param name="passwordMd5">The password MD5.</param> - /// <returns>Task.</returns> - Task<ConnectAuthenticationResult> Authenticate(string username, string passwordMd5); + Task<ConnectAuthenticationResult> Authenticate(string username, string password, string passwordMd5); /// <summary> /// Gets the local user. diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 131d0bd9e..757448eb2 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -3,7 +3,7 @@ using MediaBrowser.Model.Drawing; namespace MediaBrowser.Controller.Drawing { - public interface IImageEncoder : IDisposable + public interface IImageEncoder { /// <summary> /// Gets the supported input formats. diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 113f823f5..d7b68d1e7 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -118,5 +118,7 @@ namespace MediaBrowser.Controller.Drawing IImageEncoder ImageEncoder { get; set; } void SaveImageSize(string path, DateTime imageDateModified, ImageSize size); + + bool SupportsTransparency(string path); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index fac21c744..26283b5ea 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -10,6 +10,11 @@ namespace MediaBrowser.Controller.Drawing { public class ImageProcessingOptions { + public ImageProcessingOptions() + { + RequiresAutoOrientation = true; + } + public string ItemId { get; set; } public string ItemType { get; set; } public IHasMetadata Item { get; set; } @@ -32,7 +37,7 @@ namespace MediaBrowser.Controller.Drawing public List<IImageEnhancer> Enhancers { get; set; } - public List<ImageFormat> SupportedOutputFormats { get; set; } + public ImageFormat[] SupportedOutputFormats { get; set; } public bool AddPlayedIndicator { get; set; } @@ -43,6 +48,7 @@ namespace MediaBrowser.Controller.Drawing public string BackgroundColor { get; set; } public string ForegroundLayer { get; set; } + public bool RequiresAutoOrientation { get; set; } public bool HasDefaultOptions(string originalImagePath) { diff --git a/MediaBrowser.Controller/Drawing/ImageStream.cs b/MediaBrowser.Controller/Drawing/ImageStream.cs index 353abaca3..b5e14eb6c 100644 --- a/MediaBrowser.Controller/Drawing/ImageStream.cs +++ b/MediaBrowser.Controller/Drawing/ImageStream.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Controller.Drawing { Stream.Dispose(); } + GC.SuppressFinalize(this); } } } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 76ecd8180..c0217330d 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -2,7 +2,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using System.Collections.Generic; -using System.Threading.Tasks; using MediaBrowser.Controller.Sync; namespace MediaBrowser.Controller.Dto @@ -40,7 +39,6 @@ namespace MediaBrowser.Controller.Dto /// <param name="fields">The fields.</param> /// <param name="user">The user.</param> /// <param name="owner">The owner.</param> - /// <returns>Task{BaseItemDto}.</returns> BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null); /// <summary> @@ -60,10 +58,9 @@ namespace MediaBrowser.Controller.Dto /// <param name="options">The options.</param> /// <param name="user">The user.</param> /// <param name="owner">The owner.</param> - /// <returns>IEnumerable<BaseItemDto>.</returns> - Task<BaseItemDto[]> GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null); - Task<BaseItemDto[]> GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); /// <summary> /// Gets the chapter information dto. diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 2105ef907..00fac1eab 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities { FileInfo = FileSystem.GetDirectoryInfo(path), Path = path, - Parent = Parent + Parent = GetParent() as Folder }; // Gather child folder and files diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 3ebf4da00..02a9f15a9 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -154,46 +154,28 @@ namespace MediaBrowser.Controller.Entities.Audio { var list = base.GetUserDataKeys(); - if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys) - { - var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty; - - - if (ParentIndexNumber.HasValue) - { - songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey; - } - songKey += Name; + var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty; - if (!string.IsNullOrWhiteSpace(Album)) - { - songKey = Album + "-" + songKey; - } - - var albumArtist = AlbumArtists.Length == 0 ? null : AlbumArtists[0]; - if (!string.IsNullOrWhiteSpace(albumArtist)) - { - songKey = albumArtist + "-" + songKey; - } - list.Insert(0, songKey); - } - else + if (ParentIndexNumber.HasValue) { - var parent = AlbumEntity; + songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey; + } + songKey += Name; - if (parent != null && IndexNumber.HasValue) - { - list.InsertRange(0, parent.GetUserDataKeys().Select(i => - { - var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "") - + IndexNumber.Value.ToString("0000 - "); + if (!string.IsNullOrWhiteSpace(Album)) + { + songKey = Album + "-" + songKey; + } - return i + songKey; - })); - } + var albumArtist = AlbumArtists.Length == 0 ? null : AlbumArtists[0]; + if (!string.IsNullOrWhiteSpace(albumArtist)) + { + songKey = albumArtist + "-" + songKey; } + list.Insert(0, songKey); + return list; } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 7af8161ca..acda9ae02 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -145,13 +145,10 @@ namespace MediaBrowser.Controller.Entities.Audio { var list = base.GetUserDataKeys(); - if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys) + var albumArtist = AlbumArtist; + if (!string.IsNullOrWhiteSpace(albumArtist)) { - var albumArtist = AlbumArtist; - if (!string.IsNullOrWhiteSpace(albumArtist)) - { - list.Insert(0, albumArtist + "-" + Name); - } + list.Insert(0, albumArtist + "-" + Name); } var id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 513b85d8b..502ba6c60 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -97,7 +97,7 @@ namespace MediaBrowser.Controller.Entities public string Tagline { get; set; } [IgnoreDataMember] - public ItemImageInfo[] ImageInfos { get; set; } + public virtual ItemImageInfo[] ImageInfos { get; set; } [IgnoreDataMember] public bool IsVirtualItem { get; set; } @@ -187,21 +187,6 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public string SlugName - { - get - { - var name = Name; - if (string.IsNullOrWhiteSpace(name)) - { - return string.Empty; - } - - return SlugReplaceChars.Aggregate(name, (current, c) => current.Replace(c, SlugChar)); - } - } - - [IgnoreDataMember] public bool IsUnaired { get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; } @@ -231,6 +216,9 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public Guid Id { get; set; } + [IgnoreDataMember] + public Guid OwnerId { get; set; } + /// <summary> /// Gets or sets a value indicating whether this instance is hd. /// </summary> @@ -336,12 +324,31 @@ namespace MediaBrowser.Controller.Entities { get { + if (OwnerId != Guid.Empty) + { + return true; + } + + // legacy + // Local trailer, special feature, theme video, etc. // An item that belongs to another item but is not part of the Parent-Child tree - return !IsFolder && ParentId == Guid.Empty && LocationType == LocationType.FileSystem; + // This is a hack for now relying on ExtraType. Eventually we may need to persist this + if (ParentId == Guid.Empty && !IsFolder && LocationType == LocationType.FileSystem) + { + return true; + } + + return false; } } + public BaseItem GetOwner() + { + var ownerId = OwnerId; + return ownerId == Guid.Empty ? null : LibraryManager.GetItemById(ownerId); + } + /// <summary> /// Gets or sets the type of the location. /// </summary> @@ -664,27 +671,34 @@ namespace MediaBrowser.Controller.Entities } var sortable = Name.Trim().ToLower(); - sortable = ConfigurationManager.Configuration.SortRemoveCharacters.Aggregate(sortable, (current, search) => current.Replace(search.ToLower(), string.Empty)); - sortable = ConfigurationManager.Configuration.SortReplaceCharacters.Aggregate(sortable, (current, search) => current.Replace(search.ToLower(), " ")); + foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) + { + sortable = sortable.Replace(removeChar, string.Empty); + } + + foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters) + { + sortable = sortable.Replace(replaceChar, " "); + } foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) { - var searchLower = search.ToLower(); // Remove from beginning if a space follows - if (sortable.StartsWith(searchLower + " ")) + if (sortable.StartsWith(search + " ")) { - sortable = sortable.Remove(0, searchLower.Length + 1); + sortable = sortable.Remove(0, search.Length + 1); } // Remove from middle if surrounded by spaces - sortable = sortable.Replace(" " + searchLower + " ", " "); + sortable = sortable.Replace(" " + search + " ", " "); // Remove from end if followed by a space - if (sortable.EndsWith(" " + searchLower)) + if (sortable.EndsWith(" " + search)) { - sortable = sortable.Remove(sortable.Length - (searchLower.Length + 1)); + sortable = sortable.Remove(sortable.Length - (search.Length + 1)); } } + return ModifySortChunks(sortable); } @@ -735,17 +749,12 @@ namespace MediaBrowser.Controller.Entities ParentId = parent == null ? Guid.Empty : parent.Id; } - [IgnoreDataMember] - public IEnumerable<Folder> Parents - { - get { return GetParents().OfType<Folder>(); } - } - public BaseItem GetParent() { - if (ParentId != Guid.Empty) + var parentId = ParentId; + if (parentId != Guid.Empty) { - return LibraryManager.GetItemById(ParentId); + return LibraryManager.GetItemById(parentId); } return null; @@ -771,7 +780,15 @@ namespace MediaBrowser.Controller.Entities public T FindParent<T>() where T : Folder { - return GetParents().OfType<T>().FirstOrDefault(); + foreach (var parent in GetParents()) + { + var item = parent as T; + if (item != null) + { + return item; + } + } + return null; } [IgnoreDataMember] @@ -779,11 +796,13 @@ namespace MediaBrowser.Controller.Entities { get { - if (ParentId == Guid.Empty) + var parentId = ParentId; + + if (parentId == Guid.Empty) { return null; } - return ParentId; + return parentId; } } @@ -918,9 +937,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (!string.IsNullOrWhiteSpace(OfficialRating)) + var officialRating = OfficialRating; + if (!string.IsNullOrWhiteSpace(officialRating)) { - return OfficialRating; + return officialRating; } var parent = DisplayParent; @@ -938,9 +958,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (!string.IsNullOrWhiteSpace(CustomRating)) + var customRating = CustomRating; + if (!string.IsNullOrWhiteSpace(customRating)) { - return CustomRating; + return customRating; } var parent = DisplayParent; @@ -1000,8 +1021,11 @@ namespace MediaBrowser.Controller.Entities { audio = dbItem; } - - audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong; + else + { + // item is new + audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong; + } return audio; @@ -1030,8 +1054,11 @@ namespace MediaBrowser.Controller.Entities { item = dbItem; } - - item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; + else + { + // item is new + item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; + } return item; @@ -1176,8 +1203,25 @@ namespace MediaBrowser.Controller.Entities var newItemIds = newItems.Select(i => i.Id).ToArray(); var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); + var ownerId = item.Id; + + var tasks = newItems.Select(i => + { + var subOptions = new MetadataRefreshOptions(options); + + if (!i.ExtraType.HasValue || + i.ExtraType.Value != Model.Entities.ExtraType.Trailer || + i.OwnerId != ownerId || + i.ParentId != Guid.Empty) + { + i.ExtraType = Model.Entities.ExtraType.Trailer; + i.OwnerId = ownerId; + i.ParentId = Guid.Empty; + subOptions.ForceSave = true; + } - var tasks = newItems.Select(i => RefreshMetadataForOwnedItem(i, true, options, cancellationToken)); + return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken); + }); await Task.WhenAll(tasks).ConfigureAwait(false); @@ -1194,13 +1238,20 @@ namespace MediaBrowser.Controller.Entities var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); + var ownerId = item.Id; + var tasks = newThemeVideos.Select(i => { var subOptions = new MetadataRefreshOptions(options); - if (!i.IsThemeMedia) + if (!i.ExtraType.HasValue || + i.ExtraType.Value != Model.Entities.ExtraType.ThemeVideo || + i.OwnerId != ownerId || + i.ParentId != Guid.Empty) { - i.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; + i.ExtraType = Model.Entities.ExtraType.ThemeVideo; + i.OwnerId = ownerId; + i.ParentId = Guid.Empty; subOptions.ForceSave = true; } @@ -1224,13 +1275,20 @@ namespace MediaBrowser.Controller.Entities var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); + var ownerId = item.Id; + var tasks = newThemeSongs.Select(i => { var subOptions = new MetadataRefreshOptions(options); - if (!i.IsThemeMedia) + if (!i.ExtraType.HasValue || + i.ExtraType.Value != Model.Entities.ExtraType.ThemeSong || + i.OwnerId != ownerId || + i.ParentId != Guid.Empty) { - i.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong; + i.ExtraType = Model.Entities.ExtraType.ThemeSong; + i.OwnerId = ownerId; + i.ParentId = Guid.Empty; subOptions.ForceSave = true; } @@ -1866,7 +1924,6 @@ namespace MediaBrowser.Controller.Entities { existingImage.Path = image.Path; existingImage.DateModified = image.DateModified; - existingImage.IsPlaceholder = image.IsPlaceholder; } else @@ -1900,7 +1957,6 @@ namespace MediaBrowser.Controller.Entities image.Path = file.FullName; image.DateModified = imageInfo.DateModified; - image.IsPlaceholder = false; } } @@ -2140,8 +2196,8 @@ namespace MediaBrowser.Controller.Entities } var filename = System.IO.Path.GetFileNameWithoutExtension(Path); - var extensions = new[] { ".nfo", ".xml", ".srt" }.ToList(); - extensions.AddRange(SupportedImageExtensionsList); + var extensions = new List<string> { ".nfo", ".xml", ".srt" }; + extensions.AddRange(SupportedImageExtensions); return FileSystem.GetFiles(FileSystem.GetDirectoryName(Path), extensions.ToArray(extensions.Count), false, false) .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase)) @@ -2357,6 +2413,14 @@ namespace MediaBrowser.Controller.Entities newOptions.ForceSave = true; } + //var parentId = Id; + //if (!video.IsOwnedItem || video.ParentId != parentId) + //{ + // video.IsOwnedItem = true; + // video.ParentId = parentId; + // newOptions.ForceSave = true; + //} + if (video == null) { return Task.FromResult(true); @@ -2392,7 +2456,14 @@ namespace MediaBrowser.Controller.Entities return this; } - return GetParents().FirstOrDefault(i => i.IsTopParent); + foreach (var parent in GetParents()) + { + if (parent.IsTopParent) + { + return parent; + } + } + return null; } [IgnoreDataMember] @@ -2473,5 +2544,21 @@ namespace MediaBrowser.Controller.Entities { return null; } + + public virtual ItemUpdateType OnMetadataChanged() + { + var updateType = ItemUpdateType.None; + + var item = this; + + var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? 0; + if (inheritedParentalRatingValue != item.InheritedParentalRatingValue) + { + item.InheritedParentalRatingValue = inheritedParentalRatingValue; + updateType |= ItemUpdateType.MetadataImport; + } + + return updateType; + } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 537beb26b..a83e084db 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -280,7 +280,7 @@ namespace MediaBrowser.Controller.Entities { FileInfo = FileSystem.GetDirectoryInfo(path), Path = path, - Parent = Parent, + Parent = GetParent() as Folder, CollectionType = CollectionType }; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8a87f3c6a..6d88f7015 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -378,6 +378,7 @@ namespace MediaBrowser.Controller.Entities cancellationToken.ThrowIfCancellationRequested(); var validChildren = new List<BaseItem>(); + var validChildrenNeedGeneration = false; var allLibraryPaths = LibraryManager .GetVirtualFolders() @@ -474,11 +475,7 @@ namespace MediaBrowser.Controller.Entities } else { - if (recursive || refreshChildMetadata) - { - // used below - validChildren = Children.ToList(); - } + validChildrenNeedGeneration = true; } progress.Report(10); @@ -502,6 +499,12 @@ namespace MediaBrowser.Controller.Entities ProviderManager.OnRefreshProgress(folder, newPct); }); + if (validChildrenNeedGeneration) + { + validChildren = Children.ToList(); + validChildrenNeedGeneration = false; + } + await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), directoryService, innerProgress, cancellationToken).ConfigureAwait(false); } } @@ -536,6 +539,12 @@ namespace MediaBrowser.Controller.Entities } else { + if (validChildrenNeedGeneration) + { + validChildren = Children.ToList(); + validChildrenNeedGeneration = false; + } + await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken); } } @@ -565,7 +574,7 @@ namespace MediaBrowser.Controller.Entities }); await RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); } numComplete++; @@ -588,7 +597,10 @@ namespace MediaBrowser.Controller.Entities } else { - await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + if (refreshOptions.RefreshItem(child)) + { + await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + } if (recursive) { @@ -952,7 +964,7 @@ namespace MediaBrowser.Controller.Entities { var result = LibraryManager.GetItemsResult(query); - if (query.SortBy.Length == 0) + if (query.OrderBy.Length == 0) { var ids = query.ItemIds.ToList(); @@ -973,7 +985,7 @@ namespace MediaBrowser.Controller.Entities { var result = LibraryManager.GetItemList(query); - if (query.SortBy.Length == 0) + if (query.OrderBy.Length == 0) { var ids = query.ItemIds.ToList(); @@ -1000,8 +1012,7 @@ namespace MediaBrowser.Controller.Entities Limit = query.Limit, StartIndex = query.StartIndex, UserId = query.User.Id.ToString("N"), - SortBy = query.SortBy, - SortOrder = query.SortOrder + OrderBy = query.OrderBy }, new SimpleProgress<double>(), CancellationToken.None).Result; } @@ -1197,11 +1208,21 @@ namespace MediaBrowser.Controller.Entities /// Gets the linked children. /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - public IEnumerable<BaseItem> GetLinkedChildren() + public List<BaseItem> GetLinkedChildren() { - return LinkedChildren - .Select(GetLinkedChild) - .Where(i => i != null); + var linkedChildren = LinkedChildren; + var list = new List<BaseItem>(linkedChildren.Length); + + foreach (var i in linkedChildren) + { + var child = GetLinkedChild(i); + + if (child != null) + { + list.Add(child); + } + } + return list; } protected virtual bool FilterLinkedChildrenPerUser @@ -1212,16 +1233,19 @@ namespace MediaBrowser.Controller.Entities } } - public IEnumerable<BaseItem> GetLinkedChildren(User user) + public List<BaseItem> GetLinkedChildren(User user) { if (!FilterLinkedChildrenPerUser || user == null) { return GetLinkedChildren(); } - if (LinkedChildren.Length == 0) + var linkedChildren = LinkedChildren; + var list = new List<BaseItem>(linkedChildren.Length); + + if (linkedChildren.Length == 0) { - return new List<BaseItem>(); + return list; } var allUserRootChildren = user.RootFolder.Children.OfType<Folder>().ToList(); @@ -1232,37 +1256,43 @@ namespace MediaBrowser.Controller.Entities .Select(i => i.Id) .ToList(); - return LinkedChildren - .Select(i => + foreach (var i in linkedChildren) + { + var child = GetLinkedChild(i); + + if (child == null) { - var child = GetLinkedChild(i); + continue; + } + + var childOwner = child.IsOwnedItem ? (child.GetOwner() ?? child) : child; - if (child != null) + if (childOwner != null) + { + var childLocationType = childOwner.LocationType; + if (childLocationType == LocationType.Remote || childLocationType == LocationType.Virtual) { - var childLocationType = child.LocationType; - if (childLocationType == LocationType.Remote || childLocationType == LocationType.Virtual) + if (!childOwner.IsVisibleStandalone(user)) { - if (!child.IsVisibleStandalone(user)) - { - return null; - } + continue; } - else if (childLocationType == LocationType.FileSystem) - { - var itemCollectionFolderIds = - LibraryManager.GetCollectionFolders(child, allUserRootChildren) - .Select(f => f.Id).ToList(); + } + else if (childLocationType == LocationType.FileSystem) + { + var itemCollectionFolderIds = + LibraryManager.GetCollectionFolders(childOwner, allUserRootChildren).Select(f => f.Id); - if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains)) - { - return null; - } + if (!itemCollectionFolderIds.Any(collectionFolderIds.Contains)) + { + continue; } } + } + + list.Add(child); + } - return child; - }) - .Where(i => i != null); + return list; } /// <summary> diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index 59d9bd9f9..4146686b2 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -266,6 +266,8 @@ namespace MediaBrowser.Controller.Entities int? ProductionYear { get; set; } string[] Tags { get; set; } + + ItemUpdateType OnMetadataChanged(); } public static class HasMetadataExtensions diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs index 106b8bce4..90786d44d 100644 --- a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs +++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs @@ -15,5 +15,6 @@ namespace MediaBrowser.Controller.Entities bool IsPremiere { get; set; } ProgramAudio? Audio { get; set; } string EpisodeTitle { get; set; } + string ServiceName { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index 8686c802a..07dde3789 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -5,7 +5,7 @@ using System.Linq; namespace MediaBrowser.Controller.Entities { - public interface IHasTrailers : IHasProviderIds + public interface IHasTrailers : IHasMetadata { /// <summary> /// Gets or sets the remote trailers. diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 04833d049..a7f6c6014 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -16,10 +16,6 @@ namespace MediaBrowser.Controller.Entities public int? Limit { get; set; } - public string[] SortBy { get; set; } - - public SortOrder SortOrder { get; set; } - public User User { get; set; } public BaseItem SimilarTo { get; set; } @@ -164,8 +160,9 @@ namespace MediaBrowser.Controller.Entities public bool ForceDirect { get; set; } public Dictionary<string, string> ExcludeProviderIds { get; set; } public bool EnableGroupByMetadataKey { get; set; } + public bool? HasChapterImages { get; set; } - public List<Tuple<string, SortOrder>> OrderBy { get; set; } + public Tuple<string, SortOrder>[] OrderBy { get; set; } public DateTime? MinDateCreated { get; set; } public DateTime? MinDateLastSaved { get; set; } @@ -173,6 +170,10 @@ namespace MediaBrowser.Controller.Entities public DtoOptions DtoOptions { get; set; } public int MinSimilarityScore { get; set; } + public string HasNoAudioTrackWithLanguage { get; set; } + public string HasNoInternalSubtitleTrackWithLanguage { get; set; } + public string HasNoExternalSubtitleTrackWithLanguage { get; set; } + public string HasNoSubtitleTrackWithLanguage { get; set; } public InternalItemsQuery() { @@ -190,7 +191,6 @@ namespace MediaBrowser.Controller.Entities BlockUnratedItems = new UnratedItem[] { }; Tags = new string[] { }; OfficialRatings = new string[] { }; - SortBy = new string[] { }; MediaTypes = new string[] { }; IncludeItemTypes = new string[] { }; ExcludeItemTypes = new string[] { }; @@ -213,7 +213,7 @@ namespace MediaBrowser.Controller.Entities TrailerTypes = new TrailerType[] { }; SourceTypes = new SourceType[] { }; SeriesStatuses = new SeriesStatus[] { }; - OrderBy = new List<Tuple<string, SortOrder>>(); + OrderBy = new Tuple<string, SortOrder>[] { }; } public InternalItemsQuery(User user) diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index 672595db8..6b2d2392d 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -24,12 +24,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The date modified.</value> public DateTime DateModified { get; set; } - /// <summary> - /// Gets or sets a value indicating whether this instance is placeholder. - /// </summary> - /// <value><c>true</c> if this instance is placeholder; otherwise, <c>false</c>.</value> - public bool IsPlaceholder { get; set; } - [IgnoreDataMember] public bool IsLocalFile { diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 900e8a664..bd8d9024d 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -80,16 +80,44 @@ namespace MediaBrowser.Controller.Entities.Movies protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService) { + if (IsLegacyBoxSet) + { + return base.GetNonCachedChildren(directoryService); + } return new List<BaseItem>(); } protected override List<BaseItem> LoadChildren() { + if (IsLegacyBoxSet) + { + return base.LoadChildren(); + } + // Save a trip to the database return new List<BaseItem>(); } [IgnoreDataMember] + private bool IsLegacyBoxSet + { + get + { + if (string.IsNullOrWhiteSpace(Path)) + { + return false; + } + + if (LinkedChildren.Length > 0) + { + return false; + } + + return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path); + } + } + + [IgnoreDataMember] public override bool IsPreSorted { get diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 3a41709fe..2e0e01944 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -81,7 +81,20 @@ namespace MediaBrowser.Controller.Entities.Movies var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds); - var tasks = newItems.Select(i => RefreshMetadataForOwnedItem(i, false, options, cancellationToken)); + var ownerId = Id; + + var tasks = newItems.Select(i => + { + var subOptions = new MetadataRefreshOptions(options); + + if (i.OwnerId != ownerId) + { + i.OwnerId = ownerId; + subOptions.ForceSave = true; + } + + return RefreshMetadataForOwnedItem(i, false, subOptions, cancellationToken); + }); await Task.WhenAll(tasks).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 6514d31d2..5931c32e1 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -252,7 +252,7 @@ namespace MediaBrowser.Controller.Entities.TV query.AncestorWithPresentationUniqueKey = null; query.SeriesPresentationUniqueKey = seriesKey; query.IncludeItemTypes = new[] { typeof(Season).Name }; - query.SortBy = new[] {ItemSortBy.SortName}; + query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(); if (!config.DisplayMissingEpisodes) { @@ -275,9 +275,9 @@ namespace MediaBrowser.Controller.Entities.TV query.AncestorWithPresentationUniqueKey = null; query.SeriesPresentationUniqueKey = seriesKey; - if (query.SortBy.Length == 0) + if (query.OrderBy.Length == 0) { - query.SortBy = new[] { ItemSortBy.SortName }; + query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(); } if (query.IncludeItemTypes.Length == 0) { @@ -301,7 +301,7 @@ namespace MediaBrowser.Controller.Entities.TV AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }, - SortBy = new[] { ItemSortBy.SortName }, + OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), DtoOptions = options }; var config = user.Configuration; @@ -347,7 +347,10 @@ namespace MediaBrowser.Controller.Entities.TV cancellationToken.ThrowIfCancellationRequested(); - await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + if (refreshOptions.RefreshItem(item)) + { + await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + } numComplete++; double percent = numComplete; @@ -382,7 +385,10 @@ namespace MediaBrowser.Controller.Entities.TV if (!skipItem) { - await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + if (refreshOptions.RefreshItem(item)) + { + await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); + } } numComplete++; @@ -410,7 +416,7 @@ namespace MediaBrowser.Controller.Entities.TV AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey, SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null, IncludeItemTypes = new[] { typeof(Episode).Name }, - SortBy = new[] { ItemSortBy.SortName }, + OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), DtoOptions = options }; if (user != null) @@ -453,7 +459,7 @@ namespace MediaBrowser.Controller.Entities.TV return episodes.Where(episode => { - var episodeItem = (Episode) episode; + var episodeItem = (Episode)episode; var currentSeasonNumber = supportSpecialsInSeason ? episodeItem.AiredSeasonNumber : episode.ParentIndexNumber; if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value) diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 3c89037cc..e9a794e79 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -30,12 +30,26 @@ namespace MediaBrowser.Controller.Entities /// <value>The password.</value> public string Password { get; set; } public string EasyPassword { get; set; } + public string Salt { get; set; } public string ConnectUserName { get; set; } public string ConnectUserId { get; set; } public UserLinkType? ConnectLinkType { get; set; } public string ConnectAccessKey { get; set; } + // Strictly to remove IgnoreDataMember + public override ItemImageInfo[] ImageInfos + { + get + { + return base.ImageInfos; + } + set + { + base.ImageInfos = value; + } + } + /// <summary> /// Gets or sets the path. /// </summary> diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index acfa239d3..3ab82a103 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -397,7 +397,7 @@ namespace MediaBrowser.Controller.Entities }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null); - query.SortBy = new string[] { }; + query.OrderBy = new Tuple<string, SortOrder>[] { }; return PostFilterAndSort(items, parent, null, query, false, true); } @@ -507,8 +507,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, InternalItemsQuery query) { - query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; - query.SortOrder = SortOrder.Descending; + query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(); query.Recursive = true; query.Parent = parent; @@ -521,8 +520,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, InternalItemsQuery query) { - query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }; - query.SortOrder = SortOrder.Descending; + query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(); query.IsResumable = true; query.Recursive = true; query.Parent = parent; @@ -633,8 +631,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, InternalItemsQuery query) { - query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; - query.SortOrder = SortOrder.Descending; + query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(); query.Recursive = true; query.Parent = parent; @@ -663,8 +660,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetTvResume(Folder parent, User user, InternalItemsQuery query) { - query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }; - query.SortOrder = SortOrder.Descending; + query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(); query.IsResumable = true; query.Recursive = true; query.Parent = parent; @@ -1104,9 +1100,9 @@ namespace MediaBrowser.Controller.Entities { items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase); - if (query.SortBy.Length > 0) + if (query.OrderBy.Length > 0) { - items = libraryManager.Sort(items, query.User, query.SortBy, query.SortOrder); + items = libraryManager.Sort(items, query.User, query.OrderBy); } var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 86b52bd77..e9f7d5932 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -22,12 +22,6 @@ namespace MediaBrowser.Controller Task<SystemInfo> GetSystemInfo(); /// <summary> - /// Gets a value indicating whether this instance is running as service. - /// </summary> - /// <value><c>true</c> if this instance is running as service; otherwise, <c>false</c>.</value> - bool IsRunningAsService { get; } - - /// <summary> /// Gets a value indicating whether [supports automatic run at startup]. /// </summary> /// <value><c>true</c> if [supports automatic run at startup]; otherwise, <c>false</c>.</value> @@ -90,5 +84,7 @@ namespace MediaBrowser.Controller void LaunchUrl(string url); void EnableLoopback(string appName); + + string PackageRuntime { get; } } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 265d4d786..3001e3366 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -181,8 +181,8 @@ namespace MediaBrowser.Controller.Library /// <param name="sortBy">The sort by.</param> /// <param name="sortOrder">The sort order.</param> /// <returns>IEnumerable{BaseItem}.</returns> - IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, - SortOrder sortOrder); + IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder); + IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<Tuple<string, SortOrder>> orderBy); /// <summary> /// Gets the user root folder. @@ -563,8 +563,6 @@ namespace MediaBrowser.Controller.Library QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query); QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query); - void RegisterIgnoredPath(string path); - void UnRegisterIgnoredPath(string path); int GetCount(InternalItemsQuery query); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6da3e53aa..d4232c77e 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -59,16 +59,6 @@ namespace MediaBrowser.Controller.Library User GetUserByName(string name); /// <summary> - /// Authenticates a User and returns a result indicating whether or not it succeeded - /// </summary> - /// <param name="username">The username.</param> - /// <param name="passwordSha1">The password sha1.</param> - /// <param name="remoteEndPoint">The remote end point.</param> - /// <returns>Task{System.Boolean}.</returns> - /// <exception cref="System.ArgumentNullException">user</exception> - Task<User> AuthenticateUser(string username, string passwordSha1, string remoteEndPoint); - - /// <summary> /// Refreshes metadata for each user /// </summary> /// <param name="cancellationToken">The cancellation token.</param> @@ -135,18 +125,12 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Changes the password. /// </summary> - /// <param name="user">The user.</param> - /// <param name="newPasswordSha1">The new password sha1.</param> - /// <returns>Task.</returns> - void ChangePassword(User user, string newPasswordSha1); + void ChangePassword(User user, string newPassword, string newPasswordSha1); /// <summary> /// Changes the easy password. /// </summary> - /// <param name="user">The user.</param> - /// <param name="newPasswordSha1">The new password sha1.</param> - /// <returns>Task.</returns> - void ChangeEasyPassword(User user, string newPasswordSha1); + void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1); /// <summary> /// Gets the user dto. @@ -159,12 +143,7 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Authenticates the user. /// </summary> - /// <param name="username">The username.</param> - /// <param name="passwordSha1">The password sha1.</param> - /// <param name="passwordMd5">The password MD5.</param> - /// <param name="remoteEndPoint">The remote end point.</param> - /// <returns>Task<System.Boolean>.</returns> - Task<User> AuthenticateUser(string username, string passwordSha1, string passwordMd5, string remoteEndPoint); + Task<User> AuthenticateUser(string username, string password, string passwordSha1, string passwordMd5, string remoteEndPoint); /// <summary> /// Starts the forgot password process. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 6ff630590..42c31c629 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="cancellationToken">The cancellation token.</param> /// <returns>QueryResult{RecordingInfoDto}.</returns> Task<QueryResult<BaseItemDto>> GetRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken); - Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken); + QueryResult<BaseItemDto> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken); /// <summary> /// Gets the timers. @@ -360,7 +360,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="items">The items.</param> /// <param name="options">The options.</param> /// <param name="user">The user.</param> - Task AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> items, DtoOptions options, User user); + void AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> items, DtoOptions options, User user); /// <summary> /// Called when [recording file deleted]. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index 4b757f0b9..ed3b74bf9 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -42,7 +42,6 @@ namespace MediaBrowser.Controller.LiveTv public string Id { get; set; } public string Path { get; set; } public TimerInfo Timer { get; set; } - public ProgramInfo Program { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs index b90d0e3d2..20947462e 100644 --- a/MediaBrowser.Controller/LiveTv/LiveStream.cs +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Controller.LiveTv return Task.FromResult(true); } - protected Stream GetInputStream(string path, long startPosition, bool allowAsyncFileRead) + protected Stream GetInputStream(string path, bool allowAsyncFileRead) { var fileOpenOptions = FileOpenOptions.SequentialScan; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs index c29d73253..8fa96076b 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -91,20 +91,7 @@ namespace MediaBrowser.Controller.LiveTv public override double? GetDefaultPrimaryImageAspectRatio() { - if (IsMovie) - { - double value = 2; - value /= 3; - - return value; - } - else - { - double value = 2; - value /= 3; - - return value; - } + return LiveTvProgram.GetDefaultPrimaryImageAspectRatio(this); } public override string GetClientTypeName() diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 1607dbcba..1c1637330 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -47,19 +47,31 @@ namespace MediaBrowser.Controller.LiveTv return list; } - private static string EmbyServiceName = "Emby"; - public override double? GetDefaultPrimaryImageAspectRatio() + public static double? GetDefaultPrimaryImageAspectRatio(IHasProgramAttributes item) { - var serviceName = ServiceName; - if (!IsMovie && !string.Equals(serviceName, EmbyServiceName, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(serviceName)) + var serviceName = item.ServiceName; + if (!item.IsMovie + && !string.Equals(serviceName, EmbyServiceName, StringComparison.OrdinalIgnoreCase) + && !string.Equals(serviceName, "Next Pvr", StringComparison.OrdinalIgnoreCase)) { - return null; + double value = 16; + value /= 9; + + return value; } + else + { + double value = 2; + value /= 3; - double value = 2; - value /= 3; + return value; + } + } - return value; + private static string EmbyServiceName = "Emby"; + public override double? GetDefaultPrimaryImageAspectRatio() + { + return GetDefaultPrimaryImageAspectRatio(this); } [IgnoreDataMember] @@ -88,6 +100,9 @@ namespace MediaBrowser.Controller.LiveTv [IgnoreDataMember] public string EpisodeTitle { get; set; } + [IgnoreDataMember] + public string ShowId { get; set; } + /// <summary> /// Gets or sets a value indicating whether this instance is movie. /// </summary> diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs index 950949f37..c5fe7b1b3 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs @@ -100,20 +100,7 @@ namespace MediaBrowser.Controller.LiveTv public override double? GetDefaultPrimaryImageAspectRatio() { - if (IsMovie) - { - double value = 2; - value /= 3; - - return value; - } - else - { - double value = 2; - value /= 3; - - return value; - } + return LiveTvProgram.GetDefaultPrimaryImageAspectRatio(this); } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 26766f51a..5ef763b62 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -183,14 +183,12 @@ <Compile Include="Net\IHttpResultFactory.cs" /> <Compile Include="Net\IHttpServer.cs" /> <Compile Include="Net\IServerManager.cs" /> - <Compile Include="Net\IServiceRequest.cs" /> <Compile Include="Net\ISessionContext.cs" /> <Compile Include="Net\IWebSocket.cs" /> <Compile Include="Net\IWebSocketConnection.cs" /> <Compile Include="Net\IWebSocketListener.cs" /> <Compile Include="Net\LoggedAttribute.cs" /> <Compile Include="Net\SecurityException.cs" /> - <Compile Include="Net\ServiceRequest.cs" /> <Compile Include="Net\StaticResultOptions.cs" /> <Compile Include="Net\WebSocketConnectEventArgs.cs" /> <Compile Include="Net\WebSocketMessageInfo.cs" /> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 736f1b32f..657b9c959 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -56,10 +56,14 @@ namespace MediaBrowser.Controller.MediaEncoding { return GetAvailableEncoder("h264_nvenc", defaultEncoder); } - if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(hwType, "omx", StringComparison.OrdinalIgnoreCase)) { return GetAvailableEncoder("h264_omx", defaultEncoder); } + if (string.Equals(hwType, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + { + return GetAvailableEncoder("h264_v4l2m2m", defaultEncoder); + } if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice)) { if (IsVaapiSupported(state)) @@ -692,7 +696,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(request.Profile)) { if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { // not supported by h264_omx param += " -profile:v " + request.Profile; @@ -761,11 +766,17 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } + if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + { + param = "-pix_fmt nv21 " + param; + } + return param; } @@ -1021,11 +1032,11 @@ namespace MediaBrowser.Controller.MediaEncoding { if (sourceBitrate <= 2000000) { - sourceBitrate *= 2; + sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5); } else if (sourceBitrate <= 3000000) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 1.5); + sourceBitrate = Convert.ToInt32(sourceBitrate * 2); } var bitrate = Math.Min(sourceBitrate, requestedBitrate); @@ -1277,10 +1288,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the internal graphical subtitle param. /// </summary> - /// <param name="state">The state.</param> - /// <param name="outputVideoCodec">The output video codec.</param> - /// <returns>System.String.</returns> - public string GetGraphicalSubtitleParam(EncodingJobInfo state, string outputVideoCodec) + public string GetGraphicalSubtitleParam(EncodingJobInfo state, EncodingOptions options, string outputVideoCodec) { var outputSizeParam = string.Empty; @@ -1289,7 +1297,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Add resolution params, if specified if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) { - outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"'); + outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { @@ -1332,11 +1340,8 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// If we're going to put a fixed size on the command line, this will calculate it /// </summary> - /// <param name="state">The state.</param> - /// <param name="outputVideoCodec">The output video codec.</param> - /// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param> - /// <returns>System.String.</returns> public string GetOutputSizeParam(EncodingJobInfo state, + EncodingOptions options, string outputVideoCodec, bool allowTimeStampCopy = true) { @@ -1354,7 +1359,14 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { - filters.Add("yadif=0:-1:0"); + if (string.Equals(options.DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("yadif=1:-1:0"); + } + else + { + filters.Add("yadif=0:-1:0"); + } } if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) @@ -1389,13 +1401,22 @@ namespace MediaBrowser.Controller.MediaEncoding } else { + var isExynosV4L2 = string.Equals(outputVideoCodec, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); + // If fixed dimensions were supplied if (request.Width.HasValue && request.Height.HasValue) { var widthParam = request.Width.Value.ToString(_usCulture); var heightParam = request.Height.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam)); + if (isExynosV4L2) + { + filters.Add(string.Format("scale=trunc({0}/64)*64:trunc({1}/2)*2", widthParam, heightParam)); + } + else + { + filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam)); + } } // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size @@ -1404,7 +1425,14 @@ namespace MediaBrowser.Controller.MediaEncoding var maxWidthParam = request.MaxWidth.Value.ToString(_usCulture); var maxHeightParam = request.MaxHeight.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + if (isExynosV4L2) + { + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + } + else + { + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + } } // If a fixed width was requested @@ -1420,7 +1448,14 @@ namespace MediaBrowser.Controller.MediaEncoding { var heightParam = request.Height.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); + if (isExynosV4L2) + { + filters.Add(string.Format("scale=trunc(oh*a/64)*64:{0}", heightParam)); + } + else + { + filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); + } } // If a max width was requested @@ -1428,7 +1463,14 @@ namespace MediaBrowser.Controller.MediaEncoding { var maxWidthParam = request.MaxWidth.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); + if (isExynosV4L2) + { + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2", maxWidthParam)); + } + else + { + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); + } } // If a max height was requested @@ -1436,7 +1478,14 @@ namespace MediaBrowser.Controller.MediaEncoding { var maxHeightParam = request.MaxHeight.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + if (isExynosV4L2) + { + filters.Add(string.Format("scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + } + else + { + filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + } } } @@ -1568,6 +1617,11 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " " + GetFastSeekCommandLineParameter(state.BaseRequest); inputModifier = inputModifier.Trim(); + if (state.InputProtocol == MediaProtocol.Rtsp) + { + inputModifier += " -rtsp_transport tcp"; + } + if (!string.IsNullOrEmpty(state.InputAudioSync)) { inputModifier += " -async " + state.InputAudioSync; @@ -1578,7 +1632,7 @@ namespace MediaBrowser.Controller.MediaEncoding inputModifier += " -vsync " + state.InputVideoSync; } - if (state.ReadInputAtNativeFramerate) + if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp) { inputModifier += " -re"; } @@ -1686,7 +1740,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - state.PlayableStreamFileNames = new string[]{}; + state.PlayableStreamFileNames = new string[] { }; } } else @@ -1880,6 +1934,20 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } + + else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) + { + switch (videoStream.Codec.ToLower()) + { + case "avc": + case "h264": + if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v h264_mmal"; + } + break; + } + } } // leave blank so ffmpeg will decide @@ -2025,7 +2093,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Add resolution params, if specified if (!hasGraphicalSubs) { - var outputSizeParam = GetOutputSizeParam(state, videoCodec); + var outputSizeParam = GetOutputSizeParam(state, encodingOptions, videoCodec); args += outputSizeParam; hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; } @@ -2049,7 +2117,7 @@ namespace MediaBrowser.Controller.MediaEncoding // This is for internal graphical subs if (hasGraphicalSubs) { - args += GetGraphicalSubtitleParam(state, videoCodec); + args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec); } if (!state.RunTimeTicks.HasValue) diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index 81e294069..ecbfaecea 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using MediaBrowser.Model.Services; namespace MediaBrowser.Controller.Net @@ -26,6 +25,8 @@ namespace MediaBrowser.Controller.Net /// <value><c>true</c> if [allow before startup wizard]; otherwise, <c>false</c>.</value> public bool AllowBeforeStartupWizard { get; set; } + public bool AllowLocal { get; set; } + /// <summary> /// The request filter is executed before the service. /// </summary> @@ -34,9 +35,7 @@ namespace MediaBrowser.Controller.Net /// <param name="requestDto">The request DTO</param> public void RequestFilter(IRequest request, IResponse response, object requestDto) { - var serviceRequest = new ServiceRequest(request); - - AuthService.Authenticate(serviceRequest, this); + AuthService.Authenticate(request, this); } /// <summary> @@ -50,7 +49,7 @@ namespace MediaBrowser.Controller.Net get { return 0; } } - public IEnumerable<string> GetRoles() + public string[] GetRoles() { return (Roles ?? string.Empty).Split(new []{ ',' }, StringSplitOptions.RemoveEmptyEntries); } @@ -60,7 +59,8 @@ namespace MediaBrowser.Controller.Net { bool EscapeParentalControl { get; } bool AllowBeforeStartupWizard { get; } + bool AllowLocal { get; } - IEnumerable<string> GetRoles(); + string[] GetRoles(); } } diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index dc298c8d9..361320250 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,9 +1,9 @@ - +using MediaBrowser.Model.Services; + namespace MediaBrowser.Controller.Net { public interface IAuthService { - void Authenticate(IServiceRequest request, - IAuthenticationAttributes authAttribtues); + void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues); } } diff --git a/MediaBrowser.Controller/Net/IAuthorizationContext.cs b/MediaBrowser.Controller/Net/IAuthorizationContext.cs index bdaed6046..5a9d0aa30 100644 --- a/MediaBrowser.Controller/Net/IAuthorizationContext.cs +++ b/MediaBrowser.Controller/Net/IAuthorizationContext.cs @@ -1,4 +1,5 @@ - +using MediaBrowser.Model.Services; + namespace MediaBrowser.Controller.Net { public interface IAuthorizationContext @@ -15,6 +16,6 @@ namespace MediaBrowser.Controller.Net /// </summary> /// <param name="requestContext">The request context.</param> /// <returns>AuthorizationInfo.</returns> - AuthorizationInfo GetAuthorizationInfo(IServiceRequest requestContext); + AuthorizationInfo GetAuthorizationInfo(IRequest requestContext); } } diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index f319244da..f41572b45 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -13,13 +13,13 @@ namespace MediaBrowser.Controller.Net /// Gets the URL prefix. /// </summary> /// <value>The URL prefix.</value> - IEnumerable<string> UrlPrefixes { get; } + string[] UrlPrefixes { get; } /// <summary> /// Starts the specified server name. /// </summary> /// <param name="urlPrefixes">The URL prefixes.</param> - void StartServer(IEnumerable<string> urlPrefixes); + void StartServer(string[] urlPrefixes); /// <summary> /// Stops this instance. diff --git a/MediaBrowser.Controller/Net/IServerManager.cs b/MediaBrowser.Controller/Net/IServerManager.cs index 202df2982..a84c48c5b 100644 --- a/MediaBrowser.Controller/Net/IServerManager.cs +++ b/MediaBrowser.Controller/Net/IServerManager.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Net /// Starts this instance. /// </summary> /// <param name="urlPrefixes">The URL prefixes.</param> - void Start(IEnumerable<string> urlPrefixes); + void Start(string[] urlPrefixes); /// <summary> /// Sends a message to all clients currently connected via a web socket diff --git a/MediaBrowser.Controller/Net/IServiceRequest.cs b/MediaBrowser.Controller/Net/IServiceRequest.cs deleted file mode 100644 index ebc7e8d65..000000000 --- a/MediaBrowser.Controller/Net/IServiceRequest.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Controller.Net -{ - public interface IServiceRequest - { - string RemoteIp { get; } - QueryParamCollection Headers { get; } - QueryParamCollection QueryString { get; } - IDictionary<string,object> Items { get; } - void AddResponseHeader(string name, string value); - } -} diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index 167e17867..213a66dac 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; using System.Threading.Tasks; +using MediaBrowser.Model.Services; namespace MediaBrowser.Controller.Net { @@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Net Task<SessionInfo> GetSession(object requestContext); Task<User> GetUser(object requestContext); - Task<SessionInfo> GetSession(IServiceRequest requestContext); - Task<User> GetUser(IServiceRequest requestContext); + Task<SessionInfo> GetSession(IRequest requestContext); + Task<User> GetUser(IRequest requestContext); } } diff --git a/MediaBrowser.Controller/Net/LoggedAttribute.cs b/MediaBrowser.Controller/Net/LoggedAttribute.cs index 6a2a5e2e3..eb57392e2 100644 --- a/MediaBrowser.Controller/Net/LoggedAttribute.cs +++ b/MediaBrowser.Controller/Net/LoggedAttribute.cs @@ -30,10 +30,8 @@ namespace MediaBrowser.Controller.Net /// <param name="requestDto">The request DTO</param> public void Filter(IRequest request, IResponse response, object requestDto) { - var serviceRequest = new ServiceRequest(request); - //This code is executed before the service - var auth = AuthorizationContext.GetAuthorizationInfo(serviceRequest); + var auth = AuthorizationContext.GetAuthorizationInfo(request); if (auth != null) { diff --git a/MediaBrowser.Controller/Net/ServiceRequest.cs b/MediaBrowser.Controller/Net/ServiceRequest.cs deleted file mode 100644 index 1f72d0eb2..000000000 --- a/MediaBrowser.Controller/Net/ServiceRequest.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Controller.Net -{ - public class ServiceRequest : IServiceRequest - { - private readonly IRequest _request; - - public ServiceRequest(IRequest request) - { - _request = request; - } - - public string RemoteIp - { - get { return _request.RemoteIp; } - } - - public QueryParamCollection Headers - { - get { return _request.Headers; } - } - - public QueryParamCollection QueryString - { - get { return _request.QueryString; } - } - - public IDictionary<string, object> Items - { - get { return _request.Items; } - } - - public void AddResponseHeader(string name, string value) - { - _request.Response.AddHeader(name, value); - } - } -} diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e36e6ad5d..ee96a8c3b 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -149,8 +149,7 @@ namespace MediaBrowser.Controller.Playlists Recursive = true, IncludeItemTypes = new[] { typeof(Audio).Name }, GenreIds = new[] { musicGenre.Id.ToString("N") }, - SortBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, - SortOrder = SortOrder.Ascending, + OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), DtoOptions = options }); } @@ -163,8 +162,7 @@ namespace MediaBrowser.Controller.Playlists Recursive = true, IncludeItemTypes = new[] { typeof(Audio).Name }, ArtistIds = new[] { musicArtist.Id.ToString("N") }, - SortBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, - SortOrder = SortOrder.Ascending, + OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), DtoOptions = options }); } @@ -176,7 +174,7 @@ namespace MediaBrowser.Controller.Playlists { Recursive = true, IsFolder = false, - SortBy = new[] { ItemSortBy.SortName }, + OrderBy = new[] { ItemSortBy.SortName }.Select(i => new Tuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), MediaTypes = new[] { mediaType }, EnableTotalRecordCount = false, DtoOptions = options diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs index a993f536d..90f7296c6 100644 --- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs +++ b/MediaBrowser.Controller/Providers/IImageEnhancer.cs @@ -39,6 +39,8 @@ namespace MediaBrowser.Controller.Providers /// <returns>ImageSize.</returns> ImageSize GetEnhancedImageSize(IHasMetadata item, ImageType imageType, int imageIndex, ImageSize originalImageSize); + EnhancedImageInfo GetEnhancedImageInfo(IHasMetadata item, string inputFile, ImageType imageType, int imageIndex); + /// <summary> /// Enhances the image async. /// </summary> @@ -51,4 +53,9 @@ namespace MediaBrowser.Controller.Providers /// <exception cref="System.ArgumentNullException"></exception> Task EnhanceImageAsync(IHasMetadata item, string inputFile, string outputFile, ImageType imageType, int imageIndex); } + + public class EnhancedImageInfo + { + public bool RequiresTransparency { get; set; } + } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 86cef628e..0df2370bd 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -1,5 +1,7 @@ -using System.Linq; - +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -20,6 +22,8 @@ namespace MediaBrowser.Controller.Providers public MetadataRefreshMode MetadataRefreshMode { get; set; } public RemoteSearchResult SearchResult { get; set; } + public List<string> RefreshPaths { get; set; } + public bool ForceSave { get; set; } public MetadataRefreshOptions(IFileSystem fileSystem) @@ -44,6 +48,26 @@ namespace MediaBrowser.Controller.Providers ReplaceAllImages = copy.ReplaceAllImages; ReplaceImages = copy.ReplaceImages.ToList(); SearchResult = copy.SearchResult; + + if (copy.RefreshPaths != null && copy.RefreshPaths.Count > 0) + { + if (RefreshPaths == null) + { + RefreshPaths = new List<string>(); + } + + RefreshPaths.AddRange(copy.RefreshPaths); + } + } + + public bool RefreshItem(BaseItem item) + { + if (RefreshPaths != null && RefreshPaths.Count > 0) + { + return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparer.OrdinalIgnoreCase); + } + + return true; } } } diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index 362f5b2b9..1b684fa8f 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Controller.Session { public string Username { get; set; } public string UserId { get; set; } + public string Password { get; set; } public string PasswordSha1 { get; set; } public string PasswordMd5 { get; set; } public string App { get; set; } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 90c1de2f2..367a7a467 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -303,6 +303,7 @@ namespace MediaBrowser.Controller.Session StopAutomaticProgress(); _sessionManager = null; + GC.SuppressFinalize(this); } } } diff --git a/MediaBrowser.Model/Channels/ChannelItemQuery.cs b/MediaBrowser.Model/Channels/ChannelItemQuery.cs index 4aacc1619..909d35b38 100644 --- a/MediaBrowser.Model/Channels/ChannelItemQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelItemQuery.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; namespace MediaBrowser.Model.Channels @@ -35,16 +37,15 @@ namespace MediaBrowser.Model.Channels /// <value>The limit.</value> public int? Limit { get; set; } - public SortOrder? SortOrder { get; set; } - public string[] SortBy { get; set; } public ItemFilter[] Filters { get; set; } public ItemFields[] Fields { get; set; } + public Tuple<string, SortOrder>[] OrderBy { get; set; } public ChannelItemQuery() { Filters = new ItemFilter[] { }; - SortBy = new string[] { }; Fields = new ItemFields[] { }; + OrderBy = new Tuple<string, SortOrder>[] { }; } } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index b093d82e3..a143bb9e3 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Model.Configuration public string VaapiDevice { get; set; } public int H264Crf { get; set; } public string H264Preset { get; set; } + public string DeinterlaceMethod { get; set; } public bool EnableHardwareEncoding { get; set; } public bool EnableSubtitleExtraction { get; set; } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 5177a757a..ae04bbaab 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -179,7 +179,6 @@ namespace MediaBrowser.Model.Configuration public int SchemaVersion { get; set; } public bool EnableAnonymousUsageReporting { get; set; } - public bool EnableStandaloneMusicKeys { get; set; } public bool EnableFolderView { get; set; } public bool EnableGroupingIntoCollections { get; set; } public bool DisplaySpecialsWithinSeasons { get; set; } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index d6f0eafc7..fc976b605 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -17,9 +17,6 @@ namespace MediaBrowser.Model.Dlna [XmlIgnore] public string Id { get; set; } - [XmlIgnore] - public MediaBrowser.Model.Dlna.DeviceProfileType ProfileType { get; set; } - /// <summary> /// Gets or sets the identification. /// </summary> diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index ae74e255b..de832314c 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -58,12 +58,16 @@ namespace MediaBrowser.Model.Dlna private static ResolutionConfiguration GetResolutionConfiguration(int outputBitrate) { + ResolutionConfiguration previousOption = null; + foreach (var config in Configurations) { if (outputBitrate <= config.MaxBitrate) { - return config; + return previousOption ?? config; } + + previousOption = config; } return null; diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 10c6a05c0..a5ec0f26c 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -849,8 +849,6 @@ namespace MediaBrowser.Model.Dlna } } } - ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); - // Honor requested max channels if (options.MaxAudioChannels.HasValue) { @@ -878,6 +876,9 @@ namespace MediaBrowser.Model.Dlna var longBitrate = Math.Max(Math.Min(videoBitrate, currentValue), 64000); playlistItem.VideoBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); } + + // Do this after initial values are set to account for greater than/less than conditions + ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); } playlistItem.TranscodeReasons = transcodeReasons; @@ -1430,7 +1431,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.AudioBitrate = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.AudioBitrate = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.AudioBitrate = Math.Min(num, item.AudioBitrate ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.AudioBitrate = Math.Max(num, item.AudioBitrate ?? num); + } } break; } @@ -1439,7 +1451,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.MaxAudioChannels = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.MaxAudioChannels = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.MaxAudioChannels = Math.Min(num, item.MaxAudioChannels ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.MaxAudioChannels = Math.Max(num, item.MaxAudioChannels ?? num); + } } break; } @@ -1507,7 +1530,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.MaxRefFrames = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.MaxRefFrames = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.MaxRefFrames = Math.Min(num, item.MaxRefFrames ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.MaxRefFrames = Math.Max(num, item.MaxRefFrames ?? num); + } } break; } @@ -1516,7 +1550,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.MaxVideoBitDepth = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.MaxVideoBitDepth = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.MaxVideoBitDepth = Math.Min(num, item.MaxVideoBitDepth ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.MaxVideoBitDepth = Math.Max(num, item.MaxVideoBitDepth ?? num); + } } break; } @@ -1530,7 +1575,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.MaxHeight = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.MaxHeight = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.MaxHeight = Math.Min(num, item.MaxHeight ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.MaxHeight = Math.Max(num, item.MaxHeight ?? num); + } } break; } @@ -1539,7 +1595,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.VideoBitrate = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.VideoBitrate = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.VideoBitrate = Math.Min(num, item.VideoBitrate ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.VideoBitrate = Math.Max(num, item.VideoBitrate ?? num); + } } break; } @@ -1548,7 +1615,18 @@ namespace MediaBrowser.Model.Dlna float num; if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.MaxFramerate = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.MaxFramerate = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.MaxFramerate = Math.Min(num, item.MaxFramerate ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.MaxFramerate = Math.Max(num, item.MaxFramerate ?? num); + } } break; } @@ -1557,7 +1635,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.VideoLevel = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.VideoLevel = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.VideoLevel = Math.Min(num, item.VideoLevel ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.VideoLevel = Math.Max(num, item.VideoLevel ?? num); + } } break; } @@ -1566,7 +1655,18 @@ namespace MediaBrowser.Model.Dlna int num; if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out num)) { - item.MaxWidth = num; + if (condition.Condition == ProfileConditionType.Equals) + { + item.MaxWidth = num; + } + else if (condition.Condition == ProfileConditionType.LessThanEqual) + { + item.MaxWidth = Math.Min(num, item.MaxWidth ?? num); + } + else if (condition.Condition == ProfileConditionType.GreaterThanEqual) + { + item.MaxWidth = Math.Max(num, item.MaxWidth ?? num); + } } break; } diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index 99f69e203..2b5672896 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -55,18 +55,6 @@ namespace MediaBrowser.Model.Dto public string Id { get; set; } /// <summary> - /// Gets or sets the offline password. - /// </summary> - /// <value>The offline password.</value> - public string OfflinePassword { get; set; } - - /// <summary> - /// Gets or sets the offline password salt. - /// </summary> - /// <value>The offline password salt.</value> - public string OfflinePasswordSalt { get; set; } - - /// <summary> /// Gets or sets the primary image tag. /// </summary> /// <value>The primary image tag.</value> diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 2c2f22e86..a70a1066d 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -33,7 +33,6 @@ namespace MediaBrowser.Model.LiveTv MediaLocationsCreated = new string[] { }; RecordingEncodingFormat = "mkv"; RecordingPostProcessorArguments = "\"{path}\""; - EnableRecordingEncoding = true; } } diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index 1fd995760..c0959635f 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv public ProgramQuery() { ChannelIds = new string[] { }; - SortBy = new string[] { }; + OrderBy = new Tuple<string, SortOrder>[] { }; Genres = new string[] { }; EnableTotalRecordCount = true; EnableUserData = true; @@ -104,17 +104,7 @@ namespace MediaBrowser.Model.LiveTv /// </summary> public int? Limit { get; set; } - /// <summary> - /// What to sort the results by - /// </summary> - /// <value>The sort by.</value> - public string[] SortBy { get; set; } - - /// <summary> - /// The sort order to return results with - /// </summary> - /// <value>The sort order.</value> - public SortOrder? SortOrder { get; set; } + public Tuple<string, SortOrder>[] OrderBy { get; set; } /// <summary> /// Limit results to items containing specific genres diff --git a/MediaBrowser.Model/Net/HttpResponse.cs b/MediaBrowser.Model/Net/HttpResponse.cs index f4bd8e681..7c3d1d73d 100644 --- a/MediaBrowser.Model/Net/HttpResponse.cs +++ b/MediaBrowser.Model/Net/HttpResponse.cs @@ -59,6 +59,7 @@ namespace MediaBrowser.Model.Net { _disposable.Dispose(); } + GC.SuppressFinalize(this); } } } diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs index 2b5d33dce..343e12ab6 100644 --- a/MediaBrowser.Model/Net/IAcceptSocket.cs +++ b/MediaBrowser.Model/Net/IAcceptSocket.cs @@ -12,9 +12,6 @@ namespace MediaBrowser.Model.Net void Listen(int backlog); void Bind(IpEndPointInfo endpoint); void Connect(IpEndPointInfo endPoint); - void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed); - IAsyncResult BeginSendFile(string path, byte[] preBuffer, byte[] postBuffer, AsyncCallback callback, object state); - void EndSendFile(IAsyncResult result); } public class SocketCreateException : Exception diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 42550340b..6a6781026 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -24,8 +24,5 @@ namespace MediaBrowser.Model.Net /// Sends a UDP message to a particular end point (uni or multicast). /// </summary> Task SendToAsync(byte[] buffer, int offset, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken); - - IAsyncResult BeginSendTo(byte[] buffer, int offset, int size, IpEndPointInfo endPoint, AsyncCallback callback, object state); - int EndSendTo(IAsyncResult result); } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Plugins/PluginPageInfo.cs b/MediaBrowser.Model/Plugins/PluginPageInfo.cs index 4b91e0791..64cdb31f8 100644 --- a/MediaBrowser.Model/Plugins/PluginPageInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginPageInfo.cs @@ -4,6 +4,10 @@ { public string Name { get; set; } + public string DisplayName { get; set; } + public string EmbeddedResourcePath { get; set; } + + public bool EnableInMainMenu { get; set; } } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index f056c7410..5a895815e 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -49,11 +49,6 @@ namespace MediaBrowser.Model.Services string ResponseContentType { get; set; } /// <summary> - /// Whether the ResponseContentType has been explicitly overrided or whether it was just the default - /// </summary> - bool HasExplicitResponseContentType { get; } - - /// <summary> /// Attach any data to this request that all filters and services can access. /// </summary> Dictionary<string, object> Items { get; } diff --git a/MediaBrowser.Model/Services/RouteAttribute.cs b/MediaBrowser.Model/Services/RouteAttribute.cs index 5a39688da..264500e60 100644 --- a/MediaBrowser.Model/Services/RouteAttribute.cs +++ b/MediaBrowser.Model/Services/RouteAttribute.cs @@ -88,6 +88,10 @@ namespace MediaBrowser.Model.Services /// </summary> public string Summary { get; set; } + public string Description { get; set; } + + public bool IsHidden { get; set; } + /// <summary> /// Gets or sets longer text to explain the behaviour of the route. /// </summary> diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index fce9dea4f..b61d63729 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -17,12 +17,6 @@ namespace MediaBrowser.Model.System public string OperatingSystemDisplayName { get; set; } /// <summary> - /// Gets or sets a value indicating whether [supports running as service]. - /// </summary> - /// <value><c>true</c> if [supports running as service]; otherwise, <c>false</c>.</value> - public bool SupportsRunningAsService { get; set; } - - /// <summary> /// Gets or sets the mac address. /// </summary> /// <value>The mac address.</value> @@ -36,6 +30,8 @@ namespace MediaBrowser.Model.System /// <value><c>true</c> if this instance has pending restart; otherwise, <c>false</c>.</value> public bool HasPendingRestart { get; set; } + public bool IsShuttingDown { get; set; } + /// <summary> /// Gets or sets a value indicating whether [supports library monitor]. /// </summary> diff --git a/MediaBrowser.Model/Text/ITextEncoding.cs b/MediaBrowser.Model/Text/ITextEncoding.cs index 96dca0c04..619d90a2b 100644 --- a/MediaBrowser.Model/Text/ITextEncoding.cs +++ b/MediaBrowser.Model/Text/ITextEncoding.cs @@ -7,8 +7,8 @@ namespace MediaBrowser.Model.Text { Encoding GetASCIIEncoding(); - string GetDetectedEncodingName(byte[] bytes, string language, bool enableLanguageDetection); - Encoding GetDetectedEncoding(byte[] bytes, string language, bool enableLanguageDetection); + string GetDetectedEncodingName(byte[] bytes, int size, string language, bool enableLanguageDetection); + Encoding GetDetectedEncoding(byte[] bytes, int size, string language, bool enableLanguageDetection); Encoding GetEncodingFromCharset(string charset); } } diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index 12748a8a7..2c3dc0e31 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -14,9 +14,9 @@ namespace MediaBrowser.Providers.BoxSets { public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo> { - protected override ItemUpdateType BeforeSave(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override ItemUpdateType BeforeSaveInternal(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType); + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs index e24547614..0e6c07357 100644 --- a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs +++ b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -66,9 +67,7 @@ namespace Priority_Queue /// Removes every node from the queue. /// O(n) (So, don't do this often!) /// </summary> -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public void Clear() { Array.Clear(_nodes, 1, _numNodes); @@ -78,9 +77,7 @@ namespace Priority_Queue /// <summary> /// Returns (in O(1)!) whether the given node is in the queue. O(1) /// </summary> -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public bool Contains(TItem node) { #if DEBUG @@ -103,9 +100,7 @@ namespace Priority_Queue /// If the node is already enqueued, the result is undefined. /// O(log n) /// </summary> -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public void Enqueue(TItem node, TPriority priority) { #if DEBUG @@ -131,9 +126,7 @@ namespace Priority_Queue CascadeUp(_nodes[_numNodes]); } -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif private void Swap(TItem node1, TItem node2) { //Swap the nodes @@ -164,9 +157,7 @@ namespace Priority_Queue } } -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif private void CascadeDown(TItem node) { //aka Heapify-down @@ -228,9 +219,7 @@ namespace Priority_Queue /// Returns true if 'higher' has higher priority than 'lower', false otherwise. /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false /// </summary> -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif private bool HasHigherPriority(TItem higher, TItem lower) { var cmp = higher.Priority.CompareTo(lower.Priority); @@ -319,9 +308,7 @@ namespace Priority_Queue /// Calling this method on a node not in the queue results in undefined behavior /// O(log n) /// </summary> -#if NET_VERSION_4_5 [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif public void UpdatePriority(TItem node, TPriority priority) { #if DEBUG diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index e62ce0225..a6d4d4c33 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -206,8 +206,7 @@ namespace MediaBrowser.Providers.Manager { var image = item.GetImageInfo(type, 0); - // if it's a placeholder image then pretend like it's not there so that we can replace it - return image != null && !image.IsPlaceholder; + return image != null; } /// <summary> @@ -547,7 +546,7 @@ namespace MediaBrowser.Providers.Manager switch (type) { case ImageType.Primary: - return !(item is Movie || item is Series || item is Game); + return true; default: return true; } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 9a7ced0a5..b93f78341 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -262,8 +262,7 @@ namespace MediaBrowser.Providers.Manager personEntity.SetImage(new ItemImageInfo { Path = imageUrl, - Type = ImageType.Primary, - IsPlaceholder = true + Type = ImageType.Primary }, 0); } @@ -281,7 +280,16 @@ namespace MediaBrowser.Providers.Manager /// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param> /// <param name="currentUpdateType">Type of the current update.</param> /// <returns>ItemUpdateType.</returns> - protected virtual ItemUpdateType BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + private ItemUpdateType BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) + { + var updateType = BeforeSaveInternal(item, isFullRefresh, currentUpdateType); + + updateType |= item.OnMetadataChanged(); + + return updateType; + } + + protected virtual ItemUpdateType BeforeSaveInternal(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType) { var updateType = ItemUpdateType.None; @@ -295,13 +303,6 @@ namespace MediaBrowser.Providers.Manager updateType |= ItemUpdateType.MetadataImport; } - var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? 0; - if (inheritedParentalRatingValue != item.InheritedParentalRatingValue) - { - item.InheritedParentalRatingValue = inheritedParentalRatingValue; - updateType |= ItemUpdateType.MetadataImport; - } - return updateType; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index d249b6b00..c36d4cf63 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -1110,6 +1110,7 @@ namespace MediaBrowser.Providers.Manager { _disposeCancellationTokenSource.Cancel(); } + GC.SuppressFinalize(this); } } }
\ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 333f3d593..a9aa71bfa 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -146,6 +146,11 @@ namespace MediaBrowser.Providers.MediaInfo return _cachedTask; } + if (!item.IsCompleteMedia) + { + return _cachedTask; + } + if (item.IsShortcut) { FetchShortcutInfo(item); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 79e89a110..7c6b55d3a 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -33,15 +33,39 @@ namespace MediaBrowser.Providers.MediaInfo IEnumerable<string> languages, CancellationToken cancellationToken) { + var downloadedLanguages = new List<string>(); + + foreach (var lang in languages) + { + var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, + skipIfAudioTrackMatches, requirePerfectMatch, lang, cancellationToken).ConfigureAwait(false); + + if (downloaded) + { + downloadedLanguages.Add(lang); + } + } + + return downloadedLanguages; + } + + public Task<bool> DownloadSubtitles(Video video, + List<MediaStream> mediaStreams, + bool skipIfEmbeddedSubtitlesPresent, + bool skipIfAudioTrackMatches, + bool requirePerfectMatch, + string lang, + CancellationToken cancellationToken) + { if (video.LocationType != LocationType.FileSystem || video.VideoType != VideoType.VideoFile) { - return new List<string>(); + return Task.FromResult(false); } if (!video.IsCompleteMedia) { - return new List<string>(); + return Task.FromResult(false); } VideoContentType mediaType; @@ -57,30 +81,11 @@ namespace MediaBrowser.Providers.MediaInfo else { // These are the only supported types - return new List<string>(); + return Task.FromResult(false); } - var downloadedLanguages = new List<string>(); - - foreach (var lang in languages) - { - try - { - var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, requirePerfectMatch, lang, mediaType, cancellationToken) - .ConfigureAwait(false); - - if (downloaded) - { - downloadedLanguages.Add(lang); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading subtitles", ex); - } - } - - return downloadedLanguages; + return DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, + requirePerfectMatch, lang, mediaType, cancellationToken); } private async Task<bool> DownloadSubtitles(Video video, diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index 465c417e7..a2c10d6a8 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -120,7 +120,7 @@ namespace MediaBrowser.Providers.MediaInfo { get { - return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" }; + return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami", ".vtt" }; } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 61cf1cfc3..4d68735b9 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -80,17 +80,44 @@ namespace MediaBrowser.Providers.MediaInfo return; } - var videos = _libraryManager.GetItemList(new InternalItemsQuery + var dict = new Dictionary<Guid, BaseItem>(); + + foreach (var lang in options.DownloadLanguages) { - MediaTypes = new string[] { MediaType.Video }, - IsVirtualItem = false, - IncludeItemTypes = types.ToArray(types.Count), - DtoOptions = new DtoOptions(true) + var query = new InternalItemsQuery + { + MediaTypes = new string[] {MediaType.Video}, + IsVirtualItem = false, + IncludeItemTypes = types.ToArray(types.Count), + DtoOptions = new DtoOptions(true), + SourceTypes = new[] {SourceType.Library} + }; + + if (options.SkipIfAudioTrackMatches) + { + query.HasNoAudioTrackWithLanguage = lang; + } + + if (options.SkipIfEmbeddedSubtitlesPresent) + { + // Exclude if it already has any subtitles of the same language + query.HasNoSubtitleTrackWithLanguage = lang; + } + else + { + // Exclude if it already has external subtitles of the same language + query.HasNoExternalSubtitleTrackWithLanguage = lang; + } - }).OfType<Video>() - .Where(i => i.LocationType != LocationType.Remote) - .ToList(); + var videosByLanguage = _libraryManager.GetItemList(query); + foreach (var video in videosByLanguage) + { + dict[video.Id] = video; + } + } + + var videos = dict.Values.ToList(); if (videos.Count == 0) { return; @@ -100,9 +127,11 @@ namespace MediaBrowser.Providers.MediaInfo foreach (var video in videos) { + cancellationToken.ThrowIfCancellationRequested(); + try { - await DownloadSubtitles(video, options, cancellationToken).ConfigureAwait(false); + await DownloadSubtitles(video as Video, options, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index f666d8b7f..9b0d29cf0 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.MediaInfo { var video = item as Video; - if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut) + if (item.LocationType == LocationType.FileSystem && video != null && !video.IsPlaceHolder && !video.IsShortcut && video.IsCompleteMedia) { return true; } diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index ec1c49742..fe798d4f0 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Movies /// <summary> /// Class MovieDbProvider /// </summary> - public class MovieDbProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IDisposable, IHasOrder + public class MovieDbProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder { internal static MovieDbProvider Current { get; private set; } @@ -131,14 +131,6 @@ namespace MediaBrowser.Providers.Movies } /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - } - - /// <summary> /// The _TMDB settings task /// </summary> private TmdbSettingsResult _tmdbSettings; @@ -434,11 +426,6 @@ namespace MediaBrowser.Providers.Movies return await _httpClient.Get(options).ConfigureAwait(false); } - public void Dispose() - { - Dispose(true); - } - /// <summary> /// Class TmdbTitle /// </summary> diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index 4230c517a..48f520681 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -16,9 +16,9 @@ namespace MediaBrowser.Providers.Music { public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo> { - protected override ItemUpdateType BeforeSave(MusicAlbum item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override ItemUpdateType BeforeSaveInternal(MusicAlbum item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType); + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index 0ff161576..70ae52544 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -15,9 +15,9 @@ namespace MediaBrowser.Providers.Music { public class ArtistMetadataService : MetadataService<MusicArtist, ArtistInfo> { - protected override ItemUpdateType BeforeSave(MusicArtist item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override ItemUpdateType BeforeSaveInternal(MusicArtist item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType); + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index 0a95b449c..e2043faaf 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -299,7 +299,7 @@ namespace MediaBrowser.Providers.Omdb } } - var url = GetOmdbUrl(string.Format("i={0}&plot=full&tomatoes=true&r=json", imdbParam), cancellationToken); + var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), cancellationToken); using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false)) { diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs index d8c9ce801..9aeaa8d1f 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs @@ -36,10 +36,6 @@ namespace MediaBrowser.Providers.People private readonly IHttpClient _httpClient; private readonly ILogger _logger; - private int _requestCount; - private readonly object _requestCountLock = new object(); - private DateTime _lastRequestCountReset; - public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger) { _fileSystem = fileSystem; @@ -89,26 +85,8 @@ namespace MediaBrowser.Providers.People if (searchInfo.IsAutomated) { - lock (_requestCountLock) - { - if ((DateTime.UtcNow - _lastRequestCountReset).TotalHours >= 1) - { - _requestCount = 0; - _lastRequestCountReset = DateTime.UtcNow; - } - - var requestCount = _requestCount; - - if (requestCount >= 40) - { - //_logger.Debug("Throttling Tmdb people"); - - // This needs to be throttled - return new List<RemoteSearchResult>(); - } - - _requestCount = requestCount + 1; - } + // Don't hammer moviedb searching by name + return new List<RemoteSearchResult>(); } var url = string.Format(@"https://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), MovieDbProvider.ApiKey); @@ -179,7 +157,10 @@ namespace MediaBrowser.Providers.People var item = new Person(); result.HasMetadata = true; - item.Name = info.name; + // Take name from incoming info, don't rename the person + // TODO: This should go in PersonMetadataService, not each person provider + item.Name = id.Name; + item.HomePageUrl = info.homepage; if (!string.IsNullOrWhiteSpace(info.place_of_birth)) diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index d39d3baf5..a81facfb5 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -34,9 +34,9 @@ namespace MediaBrowser.Providers.Playlists } } - protected override ItemUpdateType BeforeSave(Playlist item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override ItemUpdateType BeforeSaveInternal(Playlist item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType); + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); if (isFullRefresh || currentUpdateType > ItemUpdateType.None) { diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 29533a46b..49175f62f 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -16,9 +16,9 @@ namespace MediaBrowser.Providers.TV { public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo> { - protected override ItemUpdateType BeforeSave(Episode item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override ItemUpdateType BeforeSaveInternal(Episode item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType); + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); var seriesName = item.FindSeriesName(); if (!string.Equals(item.SeriesName, seriesName, StringComparison.Ordinal)) diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index 7cd408791..4e712d3e3 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -17,9 +17,9 @@ namespace MediaBrowser.Providers.TV { public class SeasonMetadataService : MetadataService<Season, SeasonInfo> { - protected override ItemUpdateType BeforeSave(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType) + protected override ItemUpdateType BeforeSaveInternal(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType) { - var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType); + var updateType = base.BeforeSaveInternal(item, isFullRefresh, currentUpdateType); if (item.IndexNumber.HasValue && item.IndexNumber.Value == 0) { diff --git a/MediaBrowser.Server.Mac/Main.cs b/MediaBrowser.Server.Mac/Main.cs index 3863937f8..6af58e66c 100644 --- a/MediaBrowser.Server.Mac/Main.cs +++ b/MediaBrowser.Server.Mac/Main.cs @@ -56,20 +56,22 @@ namespace MediaBrowser.Server.Mac var appPaths = CreateApplicationPaths(appFolderPath, customProgramDataPath); - var logManager = new SimpleLogManager(appPaths.LogDirectoryPath, "server"); - logManager.ReloadLogger(LogSeverity.Info); - logManager.AddConsoleOutput(); + using (var logManager = new SimpleLogManager(appPaths.LogDirectoryPath, "server")) + { + logManager.ReloadLogger(LogSeverity.Info); + logManager.AddConsoleOutput(); - var logger = _logger = logManager.GetLogger("Main"); + var logger = _logger = logManager.GetLogger("Main"); - ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); + ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - StartApplication(appPaths, logManager, options); - NSApplication.Init (); - NSApplication.Main (args); - } + StartApplication(appPaths, logManager, options); + NSApplication.Init(); + NSApplication.Main(args); + } + } private static ServerApplicationPaths CreateApplicationPaths(string appFolderPath, string programDataPath) { diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 7ddafb636..d2ce0b40d 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -63,8 +63,7 @@ <HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath> </Reference> <Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> - <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\SkiaSharp.1.58.1\lib\net45\SkiaSharp.dll</HintPath> </Reference> <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL"> <HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath> diff --git a/MediaBrowser.Server.Mono/MonoAppHost.cs b/MediaBrowser.Server.Mono/MonoAppHost.cs index e51324ec4..ded7bb5f3 100644 --- a/MediaBrowser.Server.Mono/MonoAppHost.cs +++ b/MediaBrowser.Server.Mono/MonoAppHost.cs @@ -12,8 +12,6 @@ using MediaBrowser.IsoMounter; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; -using MediaBrowser.Model.Updates; -using MediaBrowser.Server.Startup.Common; namespace MediaBrowser.Server.Mono { @@ -44,7 +42,7 @@ namespace MediaBrowser.Server.Mono protected override void RestartInternal() { - MainClass.Restart(StartupOptions); + MainClass.Restart(); } protected override List<Assembly> GetAssembliesWithPartsInternal() diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 443699b5a..566a84da2 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -11,6 +11,7 @@ using System.Net.Security; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Emby.Drawing; using Emby.Server.Core.Cryptography; using Emby.Server.Core; using Emby.Server.Implementations; @@ -18,6 +19,7 @@ using Emby.Server.Implementations.EnvironmentInfo; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Logging; using Emby.Server.Implementations.Networking; +using MediaBrowser.Controller; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Mono.Unix.Native; @@ -28,15 +30,17 @@ namespace MediaBrowser.Server.Mono { public class MainClass { - private static ApplicationHost _appHost; - private static ILogger _logger; private static IFileSystem FileSystem; + private static IServerApplicationPaths _appPaths; + private static ILogManager _logManager; + + private static readonly TaskCompletionSource<bool> ApplicationTaskCompletionSource = new TaskCompletionSource<bool>(); + private static bool _restartOnShutdown; public static void Main(string[] args) { var applicationPath = Assembly.GetEntryAssembly().Location; - var appFolderPath = Path.GetDirectoryName(applicationPath); SetSqliteProvider(); @@ -46,26 +50,29 @@ namespace MediaBrowser.Server.Mono var customProgramDataPath = options.GetOption("-programdata"); var appPaths = CreateApplicationPaths(applicationPath, customProgramDataPath); + _appPaths = appPaths; - var logManager = new SimpleLogManager(appPaths.LogDirectoryPath, "server"); - logManager.ReloadLogger(LogSeverity.Info); - logManager.AddConsoleOutput(); + using (var logManager = new SimpleLogManager(appPaths.LogDirectoryPath, "server")) + { + _logManager = logManager; - var logger = _logger = logManager.GetLogger("Main"); + logManager.ReloadLogger(LogSeverity.Info); + logManager.AddConsoleOutput(); - ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); - - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + var logger = _logger = logManager.GetLogger("Main"); + + ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); + + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - try - { RunApplication(appPaths, logManager, options); - } - finally - { - logger.Info("Shutting down"); - _appHost.Dispose(); + _logger.Info("Disposing app host"); + + if (_restartOnShutdown) + { + StartNewInstance(options); + } } } @@ -86,8 +93,6 @@ namespace MediaBrowser.Server.Mono return new ServerApplicationPaths(programDataPath, appFolderPath, Path.GetDirectoryName(applicationPath)); } - private static readonly TaskCompletionSource<bool> ApplicationTaskCompletionSource = new TaskCompletionSource<bool>(); - private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options) { // Allow all https requests @@ -99,40 +104,42 @@ namespace MediaBrowser.Server.Mono FileSystem = fileSystem; - var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths, environmentInfo); - - _appHost = new MonoAppHost(appPaths, + using (var appHost = new MonoAppHost(appPaths, logManager, options, fileSystem, new PowerManagement(), "emby.mono.zip", environmentInfo, - imageEncoder, + new NullImageEncoder(), new SystemEvents(logManager.GetLogger("SystemEvents")), - new NetworkManager(logManager.GetLogger("NetworkManager"))); - - if (options.ContainsOption("-v")) + new NetworkManager(logManager.GetLogger("NetworkManager")))) { - Console.WriteLine(_appHost.ApplicationVersion.ToString()); - return; - } + if (options.ContainsOption("-v")) + { + Console.WriteLine(appHost.ApplicationVersion.ToString()); + return; + } + + Console.WriteLine("appHost.Init"); - Console.WriteLine("appHost.Init"); + var initProgress = new Progress<double>(); - var initProgress = new Progress<double>(); + var task = appHost.Init(initProgress); - var task = _appHost.Init(initProgress); - Task.WaitAll(task); + Task.WaitAll(task); - Console.WriteLine("Running startup tasks"); + appHost.ImageProcessor.ImageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => appHost.HttpClient, appPaths, environmentInfo); - task = _appHost.RunStartupTasks(); - Task.WaitAll(task); + Console.WriteLine("Running startup tasks"); - task = ApplicationTaskCompletionSource.Task; + task = appHost.RunStartupTasks(); + Task.WaitAll(task); - Task.WaitAll(task); + task = ApplicationTaskCompletionSource.Task; + + Task.WaitAll(task); + } } private static MonoEnvironmentInfo GetEnvironmentInfo() @@ -224,7 +231,7 @@ namespace MediaBrowser.Server.Mono { var exception = (Exception)e.ExceptionObject; - new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager, FileSystem, new ConsoleLogger()).Log(exception); + new UnhandledExceptionWriter(_appPaths, _logger, _logManager, FileSystem, new ConsoleLogger()).Log(exception); if (!Debugger.IsAttached) { @@ -243,11 +250,15 @@ namespace MediaBrowser.Server.Mono ApplicationTaskCompletionSource.SetResult(true); } - public static void Restart(StartupOptions startupOptions) + public static void Restart() { - _logger.Info("Disposing app host"); - _appHost.Dispose(); + _restartOnShutdown = true; + + Shutdown(); + } + private static void StartNewInstance(StartupOptions startupOptions) + { _logger.Info("Starting new instance"); string module = startupOptions.GetOption("-restartpath"); @@ -260,9 +271,9 @@ namespace MediaBrowser.Server.Mono if (!startupOptions.ContainsOption("-restartargs")) { var args = Environment.GetCommandLineArgs() - .Skip(1) - .Select(NormalizeCommandLineArgument) - .ToArray(); + .Skip(1) + .Select(NormalizeCommandLineArgument) + .ToArray(); commandLineArgsString = string.Join(" ", args); } @@ -271,9 +282,6 @@ namespace MediaBrowser.Server.Mono _logger.Info("Arguments: {0}", commandLineArgsString); Process.Start(module, commandLineArgsString); - - _logger.Info("Calling Environment.Exit"); - Environment.Exit(0); } private static string NormalizeCommandLineArgument(string arg) diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index 525a0098c..cff873f1f 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/MediaBrowser.Server.Mono/packages.config @@ -4,7 +4,7 @@ <package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" /> <package id="SharpCompress" version="0.14.0" targetFramework="net46" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> - <package id="SkiaSharp" version="1.58.0" targetFramework="net46" /> + <package id="SkiaSharp" version="1.58.1" targetFramework="net46" /> <package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net46" /> <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net46" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 6635cddb7..d17d9ee43 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -25,6 +25,7 @@ using Emby.Server.Implementations.EnvironmentInfo; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Logging; using MediaBrowser.Common.Net; +using MediaBrowser.Controller; using MediaBrowser.Model.IO; using SystemEvents = Emby.Server.Implementations.SystemEvents; @@ -32,13 +33,12 @@ namespace MediaBrowser.ServerApplication { public class MainStartup { - private static ApplicationHost _appHost; + private static IServerApplicationPaths _appPaths; + private static ILogManager _logManager; private static ILogger _logger; public static bool IsRunningAsService = false; - private static bool _canRestartService = false; - private static bool _appHostDisposed; [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetDllDirectory(string lpPathName); @@ -46,6 +46,7 @@ namespace MediaBrowser.ServerApplication public static string ApplicationPath; private static IFileSystem FileSystem; + private static bool _restartOnShutdown; /// <summary> /// Defines the entry point of the application. @@ -56,11 +57,6 @@ namespace MediaBrowser.ServerApplication var options = new StartupOptions(Environment.GetCommandLineArgs()); IsRunningAsService = options.ContainsOption("-service"); - if (IsRunningAsService) - { - //_canRestartService = CanRestartWindowsService(); - } - var currentProcess = Process.GetCurrentProcess(); ApplicationPath = currentProcess.MainModule.FileName; @@ -71,70 +67,83 @@ namespace MediaBrowser.ServerApplication SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3()); var appPaths = CreateApplicationPaths(ApplicationPath, IsRunningAsService); + _appPaths = appPaths; - var logManager = new SimpleLogManager(appPaths.LogDirectoryPath, "server"); - logManager.ReloadLogger(LogSeverity.Debug); - logManager.AddConsoleOutput(); + using (var logManager = new SimpleLogManager(appPaths.LogDirectoryPath, "server")) + { + _logManager = logManager; - var logger = _logger = logManager.GetLogger("Main"); + logManager.ReloadLogger(LogSeverity.Debug); + logManager.AddConsoleOutput(); - ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); + var logger = _logger = logManager.GetLogger("Main"); - // Install directly - if (options.ContainsOption("-installservice")) - { - logger.Info("Performing service installation"); - InstallService(ApplicationPath, logger); - return; - } + ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); - // Restart with admin rights, then install - if (options.ContainsOption("-installserviceasadmin")) - { - logger.Info("Performing service installation"); - RunServiceInstallation(ApplicationPath); - return; - } + // Uninstall directly + if (options.ContainsOption("-uninstallservice")) + { + logger.Info("Performing service uninstallation"); + UninstallService(ApplicationPath, logger); + return; + } - // Uninstall directly - if (options.ContainsOption("-uninstallservice")) - { - logger.Info("Performing service uninstallation"); - UninstallService(ApplicationPath, logger); - return; - } + // Restart with admin rights, then uninstall + if (options.ContainsOption("-uninstallserviceasadmin")) + { + logger.Info("Performing service uninstallation"); + RunServiceUninstallation(ApplicationPath); + return; + } - // Restart with admin rights, then uninstall - if (options.ContainsOption("-uninstallserviceasadmin")) - { - logger.Info("Performing service uninstallation"); - RunServiceUninstallation(ApplicationPath); - return; - } + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + if (IsAlreadyRunning(ApplicationPath, currentProcess)) + { + logger.Info("Shutting down because another instance of Emby Server is already running."); + return; + } - RunServiceInstallationIfNeeded(ApplicationPath); + if (PerformUpdateIfNeeded(appPaths, logger)) + { + logger.Info("Exiting to perform application update."); + return; + } - if (IsAlreadyRunning(ApplicationPath, currentProcess)) - { - logger.Info("Shutting down because another instance of Emby Server is already running."); - return; - } + RunApplication(appPaths, logManager, IsRunningAsService, options); - if (PerformUpdateIfNeeded(appPaths, logger)) - { - logger.Info("Exiting to perform application update."); - return; + logger.Info("Shutdown complete"); + + if (_restartOnShutdown) + { + logger.Info("Starting new server process"); + var restartCommandLine = GetRestartCommandLine(); + + Process.Start(restartCommandLine.Item1, restartCommandLine.Item2); + } } + } + public static Tuple<string, string> GetRestartCommandLine() + { + var currentProcess = Process.GetCurrentProcess(); + var processModulePath = currentProcess.MainModule.FileName; + + return new Tuple<string, string>(processModulePath, Environment.CommandLine); + } + + private static bool IsServiceInstalled() + { try { - RunApplication(appPaths, logManager, IsRunningAsService, options); + var serviceName = BackgroundService.GetExistingServiceName(); + var ctl = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == serviceName); + + return ctl != null; } - finally + catch { - OnServiceShutdown(); + return false; } } @@ -196,30 +205,37 @@ namespace MediaBrowser.ServerApplication private static bool IsAlreadyRunningAsService(string applicationPath) { - var serviceName = BackgroundService.GetExistingServiceName(); + try + { + 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(); + 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) + foreach (ManagementObject managementObject in managementObjectCollection) { - continue; - } - var path = obj.ToString(); + 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; + _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; + } } } + catch (COMException) + { + // Catch errors thrown due to WMI not being initialized + } return false; } @@ -236,7 +252,7 @@ namespace MediaBrowser.ServerApplication var resourcesPath = Path.GetDirectoryName(applicationPath); - if (runAsService) + if (runAsService && IsServiceInstalled()) { var systemPath = Path.GetDirectoryName(applicationPath); @@ -258,7 +274,7 @@ namespace MediaBrowser.ServerApplication { if (IsRunningAsService) { - return _canRestartService; + return false; } else { @@ -281,7 +297,7 @@ namespace MediaBrowser.ServerApplication if (IsRunningAsService) { - return _canRestartService; + return false; } else { @@ -305,7 +321,7 @@ namespace MediaBrowser.ServerApplication FileSystem = fileSystem; - _appHost = new WindowsAppHost(appPaths, + using (var appHost = new WindowsAppHost(appPaths, logManager, options, fileSystem, @@ -314,60 +330,59 @@ namespace MediaBrowser.ServerApplication environmentInfo, new NullImageEncoder(), new SystemEvents(logManager.GetLogger("SystemEvents")), - new Networking.NetworkManager(logManager.GetLogger("NetworkManager"))); - - var initProgress = new Progress<double>(); - - if (!runService) + new Networking.NetworkManager(logManager.GetLogger("NetworkManager")))) { - if (!options.ContainsOption("-nosplash")) ShowSplashScreen(_appHost.ApplicationVersion, initProgress, logManager.GetLogger("Splash")); + var initProgress = new Progress<double>(); - // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes - SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | - ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); - } + if (!runService) + { + if (!options.ContainsOption("-nosplash")) ShowSplashScreen(appHost.ApplicationVersion, initProgress, logManager.GetLogger("Splash")); - var task = _appHost.Init(initProgress); - Task.WaitAll(task); + // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes + SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | + ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); + } - if (!runService) - { - task = InstallVcredist2013IfNeeded(_appHost, _logger); + var task = appHost.Init(initProgress); Task.WaitAll(task); - // needed by skia - task = InstallVcredist2015IfNeeded(_appHost, _logger); - Task.WaitAll(task); - } + if (!runService) + { + task = InstallVcredist2013IfNeeded(appHost.HttpClient, _logger); + Task.WaitAll(task); - // set image encoder here - _appHost.ImageProcessor.ImageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); + // needed by skia + task = InstallVcredist2015IfNeeded(appHost.HttpClient, _logger); + Task.WaitAll(task); + } - task = task.ContinueWith(new Action<Task>(a => _appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); + // set image encoder here + appHost.ImageProcessor.ImageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => appHost.HttpClient, appPaths); - if (runService) - { - StartService(logManager); - } - else - { - Task.WaitAll(task); + task = task.ContinueWith(new Action<Task>(a => appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); - Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; + if (runService && IsServiceInstalled()) + { + StartService(logManager); + } + else + { + Task.WaitAll(task); - HideSplashScreen(); + HideSplashScreen(); - ShowTrayIcon(); + ShowTrayIcon(appHost); + } } } private static ServerNotifyIcon _serverNotifyIcon; private static TaskScheduler _mainTaskScheduler; - private static void ShowTrayIcon() + private static void ShowTrayIcon(ApplicationHost appHost) { //Application.EnableVisualStyles(); //Application.SetCompatibleTextRenderingDefault(false); - _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.LocalizationManager); + _serverNotifyIcon = new ServerNotifyIcon(appHost.LogManager, appHost, appHost.ServerConfigurationManager, appHost.LocalizationManager); _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); Application.Run(); } @@ -404,14 +419,6 @@ namespace MediaBrowser.ServerApplication } } - static void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e) - { - if (e.Reason == SessionSwitchReason.SessionLogon) - { - BrowserLauncher.OpenDashboard(_appHost); - } - } - public static void Invoke(Action action) { if (IsRunningAsService) @@ -431,46 +438,10 @@ namespace MediaBrowser.ServerApplication { var service = new BackgroundService(logManager.GetLogger("Service")); - service.Disposed += service_Disposed; - ServiceBase.Run(service); } /// <summary> - /// Handles the Disposed event of the service control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> - static void service_Disposed(object sender, EventArgs e) - { - OnServiceShutdown(); - } - - private static void OnServiceShutdown() - { - _logger.Info("Shutting down"); - - DisposeAppHost(); - } - - /// <summary> - /// Installs the service. - /// </summary> - private static void InstallService(string applicationPath, ILogger logger) - { - try - { - ManagedInstallerClass.InstallHelper(new[] { applicationPath }); - - logger.Info("Service installation succeeded"); - } - catch (Exception ex) - { - logger.ErrorException("Uninstall failed", ex); - } - } - - /// <summary> /// Uninstalls the service. /// </summary> private static void UninstallService(string applicationPath, ILogger logger) @@ -487,40 +458,6 @@ namespace MediaBrowser.ServerApplication } } - private static void RunServiceInstallationIfNeeded(string applicationPath) - { - var serviceName = BackgroundService.GetExistingServiceName(); - var ctl = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == serviceName); - - if (ctl == null) - { - RunServiceInstallation(applicationPath); - } - } - - /// <summary> - /// Runs the service installation. - /// </summary> - private static void RunServiceInstallation(string applicationPath) - { - var startInfo = new ProcessStartInfo - { - FileName = applicationPath, - - Arguments = "-installservice", - - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - Verb = "runas", - ErrorDialog = false - }; - - using (var process = Process.Start(startInfo)) - { - process.WaitForExit(); - } - } - /// <summary> /// Runs the service uninstallation. /// </summary> @@ -553,7 +490,7 @@ namespace MediaBrowser.ServerApplication { var exception = (Exception)e.ExceptionObject; - new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager, FileSystem, new ConsoleLogger()).Log(exception); + new UnhandledExceptionWriter(_appPaths, _logger, _logManager, FileSystem, new ConsoleLogger()).Log(exception); if (!IsRunningAsService) { @@ -608,51 +545,28 @@ namespace MediaBrowser.ServerApplication public static void Shutdown() { - if (IsRunningAsService) + if (IsRunningAsService && IsServiceInstalled()) { ShutdownWindowsService(); } else { - DisposeAppHost(); - ShutdownWindowsApplication(); } } public static void Restart() { - DisposeAppHost(); - if (IsRunningAsService) { - RestartWindowsService(); } else { - //_logger.Info("Hiding server notify icon"); - //_serverNotifyIcon.Visible = false; - - _logger.Info("Starting new instance"); - //Application.Restart(); - Process.Start(ApplicationPath); - + _restartOnShutdown = true; ShutdownWindowsApplication(); } } - private static void DisposeAppHost() - { - if (!_appHostDisposed) - { - _logger.Info("Disposing app host"); - - _appHostDisposed = true; - _appHost.Dispose(); - _logger.Info("App host dispose complete"); - } - } - private static void ShutdownWindowsApplication() { if (_serverNotifyIcon != null) @@ -662,9 +576,7 @@ namespace MediaBrowser.ServerApplication } _logger.Info("Calling Application.Exit"); - //Application.Exit(); - - Environment.Exit(0); + Application.Exit(); } private static void ShutdownWindowsService() @@ -680,23 +592,7 @@ namespace MediaBrowser.ServerApplication } } - private static void RestartWindowsService() - { - _logger.Info("Restarting background service"); - - var startInfo = new ProcessStartInfo - { - FileName = "cmd.exe", - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - Verb = "runas", - ErrorDialog = false, - Arguments = String.Format("/c sc stop {0} & sc start {0} & sc start {0}", BackgroundService.GetExistingServiceName()) - }; - Process.Start(startInfo); - } - - private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger) + private static async Task InstallVcredist2013IfNeeded(IHttpClient httpClient, ILogger logger) { // Reference // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed @@ -728,7 +624,7 @@ namespace MediaBrowser.ServerApplication try { - await InstallVcredist(GetVcredist2013Url()).ConfigureAwait(false); + await InstallVcredist(GetVcredist2013Url(), httpClient).ConfigureAwait(false); } catch (Exception ex) { @@ -748,7 +644,7 @@ namespace MediaBrowser.ServerApplication return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x86.exe"; } - private static async Task InstallVcredist2015IfNeeded(ApplicationHost appHost, ILogger logger) + private static async Task InstallVcredist2015IfNeeded(IHttpClient httpClient, ILogger logger) { // Reference // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed @@ -804,7 +700,7 @@ namespace MediaBrowser.ServerApplication try { - await InstallVcredist(GetVcredist2015Url()).ConfigureAwait(false); + await InstallVcredist(GetVcredist2015Url(), httpClient).ConfigureAwait(false); } catch (Exception ex) { @@ -824,10 +720,8 @@ namespace MediaBrowser.ServerApplication return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2015/vc_redist.x86.exe"; } - private async static Task InstallVcredist(string url) + private async static Task InstallVcredist(string url, IHttpClient httpClient) { - var httpClient = _appHost.HttpClient; - var tmp = await httpClient.GetTempFile(new HttpRequestOptions { Url = url, diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 15abaaa62..23db82cf1 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -85,7 +85,7 @@ <HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath> </Reference> <Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> - <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath> + <HintPath>..\packages\SkiaSharp.1.58.1\lib\net45\SkiaSharp.dll</HintPath> </Reference> <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL"> <HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath> @@ -162,6 +162,14 @@ </EmbeddedResource> </ItemGroup> <ItemGroup> + <Content Include="..\packages\SkiaSharp.1.58.1\runtimes\win7-x64\native\libSkiaSharp.dll"> + <Link>x64\libSkiaSharp.dll</Link> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> + <Content Include="..\packages\SkiaSharp.1.58.1\runtimes\win7-x86\native\libSkiaSharp.dll"> + <Link>x86\libSkiaSharp.dll</Link> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> <Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll"> <Link>MediaBrowser.InstallUtil.dll</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -174,17 +182,11 @@ <Link>MediaBrowser.Updater.exe</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <Content Include="x64\libSkiaSharp.dll"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </Content> <Content Include="x64\sqlite3.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <EmbeddedResource Include="Icon.ico" /> <Content Include="Resources\Images\mb3logo800.png" /> - <Content Include="x86\libSkiaSharp.dll"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </Content> <Content Include="x86\sqlite3.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index a6664c42f..19079fbc9 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -31,11 +31,6 @@ namespace MediaBrowser.ServerApplication fileSystem.AddShortcutHandler(new LnkShortcutHandler()); } - public override bool IsRunningAsService - { - get { return MainStartup.IsRunningAsService; } - } - protected override IConnectManager CreateConnectManager() { return new ConnectManager(); @@ -111,14 +106,6 @@ namespace MediaBrowser.ServerApplication } } - public override bool SupportsRunningAsService - { - get - { - return true; - } - } - public override bool CanSelfRestart { get diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 3af1bcf4d..85d2613bb 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -3,7 +3,7 @@ <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" /> <package id="SharpCompress" version="0.14.0" targetFramework="net462" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net462" /> - <package id="SkiaSharp" version="1.58.0" targetFramework="net462" /> + <package id="SkiaSharp" version="1.58.1" targetFramework="net462" /> <package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net462" /> <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net462" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/x64/libSkiaSharp.dll.REMOVED.git-id b/MediaBrowser.ServerApplication/x64/libSkiaSharp.dll.REMOVED.git-id deleted file mode 100644 index 4027f61a0..000000000 --- a/MediaBrowser.ServerApplication/x64/libSkiaSharp.dll.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -20e469be83c5d41bb44d085c36550780e788a8ef
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs b/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs index 33289e76c..b69c14fee 100644 --- a/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs +++ b/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs @@ -11,6 +11,9 @@ namespace MediaBrowser.WebDashboard.Api /// </summary> /// <value>The name.</value> public string Name { get; set; } + public bool EnableInMainMenu { get; set; } + + public string DisplayName { get; set; } /// <summary> /// Gets the type of the configuration page. @@ -27,15 +30,22 @@ namespace MediaBrowser.WebDashboard.Api public ConfigurationPageInfo(IPluginConfigurationPage page) { Name = page.Name; + ConfigurationPageType = page.ConfigurationPageType; - // Don't use "N" because it needs to match Plugin.Id - PluginId = page.Plugin.Id.ToString(); + if (page.Plugin != null) + { + DisplayName = page.Plugin.Name; + // Don't use "N" because it needs to match Plugin.Id + PluginId = page.Plugin.Id.ToString(); + } } public ConfigurationPageInfo(IPlugin plugin, PluginPageInfo page) { Name = page.Name; + EnableInMainMenu = page.EnableInMainMenu; + DisplayName = string.IsNullOrWhiteSpace(page.DisplayName) ? plugin.Name : page.DisplayName; // Don't use "N" because it needs to match Plugin.Id PluginId = plugin.Id.ToString(); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index c6bbca672..47662b509 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -32,6 +32,7 @@ namespace MediaBrowser.WebDashboard.Api /// </summary> /// <value>The type of the page.</value> public ConfigurationPageType? PageType { get; set; } + public bool? EnableInMainMenu { get; set; } } /// <summary> @@ -47,13 +48,13 @@ namespace MediaBrowser.WebDashboard.Api public string Name { get; set; } } - [Route("/web/Package", "GET")] + [Route("/web/Package", "GET", IsHidden = true)] public class GetDashboardPackage { public string Mode { get; set; } } - [Route("/robots.txt", "GET")] + [Route("/robots.txt", "GET", IsHidden = true)] public class GetRobotsTxt { } @@ -61,7 +62,7 @@ namespace MediaBrowser.WebDashboard.Api /// <summary> /// Class GetDashboardResource /// </summary> - [Route("/web/{ResourceName*}", "GET")] + [Route("/web/{ResourceName*}", "GET", IsHidden = true)] public class GetDashboardResource { /// <summary> @@ -76,7 +77,7 @@ namespace MediaBrowser.WebDashboard.Api public string V { get; set; } } - [Route("/favicon.ico", "GET")] + [Route("/favicon.ico", "GET", IsHidden = true)] public class GetFavIcon { } @@ -221,25 +222,20 @@ namespace MediaBrowser.WebDashboard.Api /// <returns>System.Object.</returns> public object Get(GetDashboardConfigurationPages request) { - const string unavilableMessage = "The server is still loading. Please try again momentarily."; + const string unavailableMessage = "The server is still loading. Please try again momentarily."; var instance = ServerEntryPoint.Instance; if (instance == null) { - throw new InvalidOperationException(unavilableMessage); + throw new InvalidOperationException(unavailableMessage); } var pages = instance.PluginConfigurationPages; if (pages == null) { - throw new InvalidOperationException(unavilableMessage); - } - - if (request.PageType.HasValue) - { - pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value).ToList(); + throw new InvalidOperationException(unavailableMessage); } // Don't allow a failing plugin to fail them all @@ -261,6 +257,16 @@ namespace MediaBrowser.WebDashboard.Api configPages.AddRange(_appHost.Plugins.SelectMany(GetConfigPages)); + if (request.PageType.HasValue) + { + configPages = configPages.Where(p => p.ConfigurationPageType == request.PageType.Value).ToList(); + } + + if (request.EnableInMainMenu.HasValue) + { + configPages = configPages.Where(p => p.EnableInMainMenu == request.EnableInMainMenu.Value).ToList(); + } + return _resultFactory.GetOptimizedResult(Request, configPages); } diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index b1c687a6e..98460f767 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -81,6 +81,7 @@ namespace MediaBrowser.XbmcMetadata public void Dispose() { _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved; + GC.SuppressFinalize(this); } private async void SaveMetadataForItem(BaseItem item, ItemUpdateType updateReason) diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index 85f9f92ba..0283e4a7b 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -1,4 +1,7 @@ -using System.Linq; +using System; +using System.IO; +using System.Linq; +using System.Text; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -70,6 +73,17 @@ namespace MediaBrowser.XbmcMetadata.Parsers { movie.CollectionName = val; } + else + { + try + { + ParseSetXml(val, movie); + } + catch (Exception ex) + { + Logger.ErrorException("Error parsing set node", ex); + } + } } break; @@ -109,6 +123,63 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + private void ParseSetXml(string xml, Movie movie) + { + using (var ms = new MemoryStream()) + { + //xml = xml.Substring(xml.IndexOf('<')); + //xml = xml.Substring(0, xml.LastIndexOf('>')); + xml = "<set>" + xml + "</set>"; + + var bytes = Encoding.UTF8.GetBytes(xml); + + ms.Write(bytes, 0, bytes.Length); + ms.Position = 0; + + // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions + try + { + var settings = XmlReaderSettingsFactory.Create(false); + + settings.CheckCharacters = false; + settings.IgnoreProcessingInstructions = true; + settings.IgnoreComments = true; + + // Use XmlReader for best performance + using (var reader = XmlReader.Create(ms, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "name": + movie.CollectionName = reader.ReadElementContentAsString(); + break; + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + } + catch (XmlException) + { + + } + } + } + public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) { } diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 87c8a70ae..a3cb05bf7 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.747</version> + <version>3.0.748</version> <title>Emby.Common</title> <authors>Emby Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 20c043007..6c2f2d399 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.747</version> + <version>3.0.748</version> <title>Emby.Server.Core</title> <authors>Emby Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Emby Server.</description> <copyright>Copyright © Emby 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.747" /> + <dependency id="MediaBrowser.Common" version="3.0.748" /> </dependencies> </metadata> <files> diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj index ef1f32207..aea144a5a 100644 --- a/RSSDP/RSSDP.csproj +++ b/RSSDP/RSSDP.csproj @@ -12,9 +12,8 @@ <AssemblyName>RSSDP</AssemblyName> <DefaultLanguage>en-US</DefaultLanguage> <FileAlignment>512</FileAlignment> - <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> - <TargetFrameworkProfile>Profile7</TargetFrameworkProfile> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkProfile /> + <TargetFrameworkVersion>v4.6</TargetFrameworkVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -77,8 +76,18 @@ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> <Name>MediaBrowser.Model</Name> </ProjectReference> + <Reference Include="System" /> + <Reference Include="System.Configuration" /> + <Reference Include="System.Core" /> + <Reference Include="System.Runtime.Serialization" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> </ItemGroup> - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/RSSDP/RSSDP.nuget.targets b/RSSDP/RSSDP.nuget.targets deleted file mode 100644 index e69ce0e64..000000000 --- a/RSSDP/RSSDP.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no"?> -<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> - <Target Name="EmitMSBuildWarning" BeforeTargets="Build"> - <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." /> - </Target> -</Project>
\ No newline at end of file diff --git a/SocketHttpListener/Net/EndPointListener.cs b/SocketHttpListener/Net/EndPointListener.cs index 2106bbec5..2b1479e42 100644 --- a/SocketHttpListener/Net/EndPointListener.cs +++ b/SocketHttpListener/Net/EndPointListener.cs @@ -3,6 +3,8 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Threading; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; @@ -11,37 +13,37 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; +using ProtocolType = MediaBrowser.Model.Net.ProtocolType; +using SocketType = MediaBrowser.Model.Net.SocketType; namespace SocketHttpListener.Net { sealed class EndPointListener { HttpListener listener; - IpEndPointInfo endpoint; - IAcceptSocket sock; - Dictionary<ListenerPrefix,HttpListener> prefixes; // Dictionary <ListenerPrefix, HttpListener> + IPEndPoint endpoint; + Socket sock; + Dictionary<ListenerPrefix, HttpListener> prefixes; // Dictionary <ListenerPrefix, HttpListener> List<ListenerPrefix> unhandled; // List<ListenerPrefix> unhandled; host = '*' List<ListenerPrefix> all; // List<ListenerPrefix> all; host = '+' - ICertificate cert; + X509Certificate cert; bool secure; Dictionary<HttpConnection, HttpConnection> unregistered; private readonly ILogger _logger; private bool _closed; private bool _enableDualMode; private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamFactory _streamFactory; private readonly ISocketFactory _socketFactory; private readonly ITextEncoding _textEncoding; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly IFileSystem _fileSystem; private readonly IEnvironmentInfo _environment; - public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) + public EndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { this.listener = listener; _logger = logger; _cryptoProvider = cryptoProvider; - _streamFactory = streamFactory; _socketFactory = socketFactory; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; @@ -51,8 +53,8 @@ namespace SocketHttpListener.Net this.secure = secure; this.cert = cert; - _enableDualMode = addr.Equals(IpAddressInfo.IPv6Any); - endpoint = new IpEndPointInfo(addr, port); + _enableDualMode = addr.Equals(IPAddress.IPv6Any); + endpoint = new IPEndPoint(addr, port); prefixes = new Dictionary<ListenerPrefix, HttpListener>(); unregistered = new Dictionary<HttpConnection, HttpConnection>(); @@ -72,18 +74,18 @@ namespace SocketHttpListener.Net { try { - sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode); + sock = CreateSocket(endpoint.Address.AddressFamily, _enableDualMode); } catch (SocketCreateException ex) { - if (_enableDualMode && endpoint.IpAddress.Equals(IpAddressInfo.IPv6Any) && - (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || + if (_enableDualMode && endpoint.Address.Equals(IPAddress.IPv6Any) && + (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || // mono on bsd is throwing this string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase))) { - endpoint = new IpEndPointInfo(IpAddressInfo.Any, endpoint.Port); + endpoint = new IPEndPoint(IPAddress.Any, endpoint.Port); _enableDualMode = false; - sock = _socketFactory.CreateSocket(endpoint.IpAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, _enableDualMode); + sock = CreateSocket(endpoint.Address.AddressFamily, _enableDualMode); } else { @@ -96,11 +98,42 @@ namespace SocketHttpListener.Net // This is the number TcpListener uses. sock.Listen(2147483647); - sock.StartAccept(ProcessAccept, () => _closed); + new SocketAcceptor(_logger, sock, ProcessAccept, () => _closed).StartAccept(); _closed = false; } - private async void ProcessAccept(IAcceptSocket accepted) + private Socket CreateSocket(AddressFamily addressFamily, bool dualMode) + { + try + { + var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + + if (dualMode) + { + socket.DualMode = true; + } + + return socket; + } + catch (SocketException ex) + { + throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); + } + catch (ArgumentException ex) + { + if (dualMode) + { + // Mono for BSD incorrectly throws ArgumentException instead of SocketException + throw new SocketCreateException("AddressFamilyNotSupported", ex); + } + else + { + throw; + } + } + } + + private async void ProcessAccept(Socket accepted) { try { @@ -112,7 +145,7 @@ namespace SocketHttpListener.Net return; } - HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem, _environment).ConfigureAwait(false); + HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem, _environment).ConfigureAwait(false); //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); lock (listener.unregistered) diff --git a/SocketHttpListener/Net/EndPointManager.cs b/SocketHttpListener/Net/EndPointManager.cs index ddb110ec1..557caa59a 100644 --- a/SocketHttpListener/Net/EndPointManager.cs +++ b/SocketHttpListener/Net/EndPointManager.cs @@ -66,25 +66,25 @@ namespace SocketHttpListener.Net epl.AddPrefix(lp, listener); } - private static IpAddressInfo GetIpAnyAddress(HttpListener listener) + private static IPAddress GetIpAnyAddress(HttpListener listener) { - return listener.EnableDualMode ? IpAddressInfo.IPv6Any : IpAddressInfo.Any; + return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any; } static async Task<EndPointListener> GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) { var networkManager = listener.NetworkManager; - IpAddressInfo addr; + IPAddress addr; if (host == "*" || host == "+") addr = GetIpAnyAddress(listener); - else if (networkManager.TryParseIpAddress(host, out addr) == false) + else if (IPAddress.TryParse(host, out addr) == false) { try { var all = (await networkManager.GetHostAddressesAsync(host).ConfigureAwait(false)); - addr = (all.Length == 0 ? null : all[0]) ?? + addr = (all.Length == 0 ? null : IPAddress.Parse(all[0].Address)) ?? GetIpAnyAddress(listener); } catch @@ -94,10 +94,10 @@ namespace SocketHttpListener.Net } Dictionary<int, EndPointListener> p = null; // Dictionary<int, EndPointListener> - if (!ip_to_endpoints.TryGetValue(addr.Address, out p)) + if (!ip_to_endpoints.TryGetValue(addr.ToString(), out p)) { p = new Dictionary<int, EndPointListener>(); - ip_to_endpoints[addr.Address] = p; + ip_to_endpoints[addr.ToString()] = p; } EndPointListener epl = null; @@ -107,25 +107,25 @@ namespace SocketHttpListener.Net } else { - epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo); + epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo); p[port] = epl; } return epl; } - public static void RemoveEndPoint(EndPointListener epl, IpEndPointInfo ep) + public static void RemoveEndPoint(EndPointListener epl, IPEndPoint ep) { lock (ip_to_endpoints) { // Dictionary<int, EndPointListener> p Dictionary<int, EndPointListener> p; - if (ip_to_endpoints.TryGetValue(ep.IpAddress.Address, out p)) + if (ip_to_endpoints.TryGetValue(ep.Address.ToString(), out p)) { p.Remove(ep.Port); if (p.Count == 0) { - ip_to_endpoints.Remove(ep.IpAddress.Address); + ip_to_endpoints.Remove(ep.Address.ToString()); } } epl.Close(); diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs index e66443c59..4e8158964 100644 --- a/SocketHttpListener/Net/HttpConnection.cs +++ b/SocketHttpListener/Net/HttpConnection.cs @@ -1,5 +1,9 @@ using System; using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Cryptography; @@ -16,7 +20,7 @@ namespace SocketHttpListener.Net { private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); const int BufferSize = 8192; - IAcceptSocket _socket; + Socket _socket; Stream _stream; EndPointListener _epl; MemoryStream _memoryStream; @@ -31,21 +35,20 @@ namespace SocketHttpListener.Net bool _contextBound; bool secure; int _timeout = 300000; // 90k ms for first request, 15k ms from then on - IpEndPointInfo local_ep; + IPEndPoint local_ep; HttpListener _lastListener; int[] client_cert_errors; - ICertificate cert; - Stream ssl_stream; + X509Certificate cert; + SslStream ssl_stream; private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; - private readonly IStreamFactory _streamFactory; private readonly IFileSystem _fileSystem; private readonly IEnvironmentInfo _environment; - private HttpConnection(ILogger logger, IAcceptSocket socket, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) + private HttpConnection(ILogger logger, Socket socket, EndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; this._socket = socket; @@ -57,14 +60,13 @@ namespace SocketHttpListener.Net _textEncoding = textEncoding; _fileSystem = fileSystem; _environment = environment; - _streamFactory = streamFactory; } private async Task InitStream() { if (secure == false) { - _stream = _streamFactory.CreateNetworkStream(_socket, false); + _stream = new SocketStream(_socket, false); } else { @@ -81,16 +83,16 @@ namespace SocketHttpListener.Net //}); //_stream = ssl_stream.AuthenticatedStream; - ssl_stream = _streamFactory.CreateSslStream(_streamFactory.CreateNetworkStream(_socket, false), false); - await _streamFactory.AuthenticateSslStreamAsServer(ssl_stream, cert).ConfigureAwait(false); + ssl_stream = new SslStream(new SocketStream(_socket, false), false); + await ssl_stream.AuthenticateAsServerAsync(cert).ConfigureAwait(false); _stream = ssl_stream; } Init(); } - public static async Task<HttpConnection> Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) + public static async Task<HttpConnection> Create(ILogger logger, Socket sock, EndPointListener epl, bool secure, X509Certificate cert, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { - var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem, environment); + var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, memoryStreamFactory, textEncoding, fileSystem, environment); await connection.InitStream().ConfigureAwait(false); @@ -134,21 +136,21 @@ namespace SocketHttpListener.Net get { return _reuses; } } - public IpEndPointInfo LocalEndPoint + public IPEndPoint LocalEndPoint { get { if (local_ep != null) return local_ep; - local_ep = (IpEndPointInfo)_socket.LocalEndPoint; + local_ep = (IPEndPoint)_socket.LocalEndPoint; return local_ep; } } - public IpEndPointInfo RemoteEndPoint + public IPEndPoint RemoteEndPoint { - get { return (IpEndPointInfo)_socket.RemoteEndPoint; } + get { return _socket.RemoteEndPoint as IPEndPoint; } } public bool IsSecure @@ -513,12 +515,12 @@ namespace SocketHttpListener.Net return; } - IAcceptSocket s = _socket; + Socket s = _socket; _socket = null; try { if (s != null) - s.Shutdown(true); + s.Shutdown(SocketShutdown.Both); } catch { diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs index b3e01425c..32c5e90e0 100644 --- a/SocketHttpListener/Net/HttpListener.cs +++ b/SocketHttpListener/Net/HttpListener.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; +using System.Security.Cryptography.X509Certificates; using MediaBrowser.Common.Net; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; @@ -17,7 +18,6 @@ namespace SocketHttpListener.Net public sealed class HttpListener : IDisposable { internal ICryptoProvider CryptoProvider { get; private set; } - internal IStreamFactory StreamFactory { get; private set; } internal ISocketFactory SocketFactory { get; private set; } internal IFileSystem FileSystem { get; private set; } internal ITextEncoding TextEncoding { get; private set; } @@ -38,15 +38,14 @@ namespace SocketHttpListener.Net Dictionary<HttpListenerContext, HttpListenerContext> registry; // Dictionary<HttpListenerContext,HttpListenerContext> Dictionary<HttpConnection, HttpConnection> connections; private ILogger _logger; - private ICertificate _certificate; + private X509Certificate _certificate; public Action<HttpListenerContext> OnContext { get; set; } - public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) + public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) { _logger = logger; CryptoProvider = cryptoProvider; - StreamFactory = streamFactory; SocketFactory = socketFactory; NetworkManager = networkManager; TextEncoding = textEncoding; @@ -59,18 +58,18 @@ namespace SocketHttpListener.Net auth_schemes = AuthenticationSchemes.Anonymous; } - public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) - :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo) + public HttpListener(X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) + :this(new NullLogger(), certificate, cryptoProvider, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo) { } - public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) - : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo) + public HttpListener(ILogger logger, X509Certificate certificate, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) + : this(logger, cryptoProvider, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo) { _certificate = certificate; } - public void LoadCert(ICertificate cert) + public void LoadCert(X509Certificate cert) { _certificate = cert; } @@ -150,7 +149,7 @@ namespace SocketHttpListener.Net // } //} - internal ICertificate Certificate + internal X509Certificate Certificate { get { return _certificate; } } @@ -249,6 +248,7 @@ namespace SocketHttpListener.Net Close(true); //TODO: Should we force here or not? disposed = true; + GC.SuppressFinalize(this); } internal void CheckDisposed() diff --git a/SocketHttpListener/Net/HttpListenerRequest.cs b/SocketHttpListener/Net/HttpListenerRequest.cs index f9df52593..5e391424f 100644 --- a/SocketHttpListener/Net/HttpListenerRequest.cs +++ b/SocketHttpListener/Net/HttpListenerRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Net; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Net; @@ -513,7 +514,14 @@ namespace SocketHttpListener.Net public bool IsLocal { - get { return RemoteEndPoint.IpAddress.Equals(IpAddressInfo.Loopback) || RemoteEndPoint.IpAddress.Equals(IpAddressInfo.IPv6Loopback) || LocalEndPoint.IpAddress.Equals(RemoteEndPoint.IpAddress); } + get + { + var remoteEndPoint = RemoteEndPoint; + + return remoteEndPoint.Address.Equals(IPAddress.Loopback) || + remoteEndPoint.Address.Equals(IPAddress.IPv6Loopback) || + LocalEndPoint.Address.Equals(remoteEndPoint.Address); + } } public bool IsSecureConnection @@ -557,7 +565,7 @@ namespace SocketHttpListener.Net } } - public IpEndPointInfo LocalEndPoint + public IPEndPoint LocalEndPoint { get { return context.Connection.LocalEndPoint; } } @@ -577,7 +585,7 @@ namespace SocketHttpListener.Net get { return raw_url; } } - public IpEndPointInfo RemoteEndPoint + public IPEndPoint RemoteEndPoint { get { return context.Connection.RemoteEndPoint; } } @@ -651,10 +659,5 @@ namespace SocketHttpListener.Net return _websocketRequest; } } - - public Task<ICertificate> GetClientCertificateAsync() - { - return Task.FromResult<ICertificate>(null); - } } } diff --git a/SocketHttpListener/Net/HttpResponseStream.Managed.cs b/SocketHttpListener/Net/HttpResponseStream.Managed.cs index b700c293d..2de3fbb94 100644 --- a/SocketHttpListener/Net/HttpResponseStream.Managed.cs +++ b/SocketHttpListener/Net/HttpResponseStream.Managed.cs @@ -51,13 +51,13 @@ namespace SocketHttpListener.Net private bool _trailer_sent; private Stream _stream; private readonly IMemoryStreamFactory _memoryStreamFactory; - private readonly IAcceptSocket _socket; + private readonly Socket _socket; private readonly bool _supportsDirectSocketAccess; private readonly IEnvironmentInfo _environment; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, IAcceptSocket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger) + internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger) { _response = response; _ignore_errors = ignore_errors; @@ -285,17 +285,13 @@ namespace SocketHttpListener.Net } } - private bool EnableSendFileWithSocket = false; - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { - if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !_response.SendChunked && _response.ContentLength64 > 8192) - { - if (EnableSendFileWithSocket) - { - return TransmitFileOverSocket(path, offset, count, fileShareMode, cancellationToken); - } - } + //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !_response.SendChunked) + //{ + // return TransmitFileOverSocket(path, offset, count, fileShareMode, cancellationToken); + //} + return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken); } @@ -318,7 +314,9 @@ namespace SocketHttpListener.Net return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken); } - //_logger.Info("Socket sending file {0} {1}", path, response.ContentLength64); + _stream.Flush(); + + _logger.Info("Socket sending file {0}", path); var taskCompletion = new TaskCompletionSource<bool>(); @@ -335,7 +333,7 @@ namespace SocketHttpListener.Net } }; - var result = _socket.BeginSendFile(path, preBuffer, _emptyBuffer, new AsyncCallback(callback), null); + var result = _socket.BeginSendFile(path, preBuffer, _emptyBuffer, TransmitFileOptions.UseDefaultWorkerThread, new AsyncCallback(callback), null); if (result.CompletedSynchronously) { diff --git a/SocketHttpListener/Net/ListenerPrefix.cs b/SocketHttpListener/Net/ListenerPrefix.cs index 2c314da50..605b7b88c 100644 --- a/SocketHttpListener/Net/ListenerPrefix.cs +++ b/SocketHttpListener/Net/ListenerPrefix.cs @@ -11,7 +11,7 @@ namespace SocketHttpListener.Net ushort port; string path; bool secure; - IpAddressInfo[] addresses; + IPAddress[] addresses; public HttpListener Listener; public ListenerPrefix(string prefix) @@ -25,7 +25,7 @@ namespace SocketHttpListener.Net return original; } - public IpAddressInfo[] Addresses + public IPAddress[] Addresses { get { return addresses; } set { addresses = value; } diff --git a/Emby.Server.Implementations/Net/SocketAcceptor.cs b/SocketHttpListener/Net/SocketAcceptor.cs index 288ba93ad..36332f52b 100644 --- a/Emby.Server.Implementations/Net/SocketAcceptor.cs +++ b/SocketHttpListener/Net/SocketAcceptor.cs @@ -1,19 +1,17 @@ using System; using System.Net.Sockets; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; -namespace Emby.Server.Implementations.Net +namespace SocketHttpListener.Net { public class SocketAcceptor { private readonly ILogger _logger; private readonly Socket _originalSocket; private readonly Func<bool> _isClosed; - private readonly Action<IAcceptSocket> _onAccept; - private readonly bool _isDualMode; + private readonly Action<Socket> _onAccept; - public SocketAcceptor(ILogger logger, Socket originalSocket, Action<IAcceptSocket> onAccept, Func<bool> isClosed, bool isDualMode) + public SocketAcceptor(ILogger logger, Socket originalSocket, Action<Socket> onAccept, Func<bool> isClosed) { if (logger == null) { @@ -35,7 +33,6 @@ namespace Emby.Server.Implementations.Net _logger = logger; _originalSocket = originalSocket; _isClosed = isClosed; - _isDualMode = isDualMode; _onAccept = onAccept; } @@ -117,7 +114,7 @@ namespace Emby.Server.Implementations.Net if (acceptSocket != null) { //ProcessAccept(acceptSocket); - _onAccept(new NetAcceptSocket(acceptSocket, _logger, _isDualMode)); + _onAccept(acceptSocket); } // Accept the next connection request diff --git a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs index 034ac17d2..803c67b83 100644 --- a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs +++ b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs @@ -254,7 +254,7 @@ namespace SocketHttpListener.Net.WebSockets /// </summary> /// <value> /// </value> - public override IpEndPointInfo ServerEndPoint + public override IPEndPoint ServerEndPoint { get { @@ -281,7 +281,7 @@ namespace SocketHttpListener.Net.WebSockets /// </summary> /// <value> /// </value> - public override IpEndPointInfo UserEndPoint + public override IPEndPoint UserEndPoint { get { diff --git a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs index 3ffa6e639..9665ab789 100644 --- a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs +++ b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs @@ -151,7 +151,7 @@ namespace SocketHttpListener.Net.WebSockets /// <value> /// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint. /// </value> - public abstract IpEndPointInfo ServerEndPoint { get; } + public abstract IPEndPoint ServerEndPoint { get; } /// <summary> /// Gets the client information (identity, authentication, and security roles). @@ -167,7 +167,7 @@ namespace SocketHttpListener.Net.WebSockets /// <value> /// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint. /// </value> - public abstract IpEndPointInfo UserEndPoint { get; } + public abstract IPEndPoint UserEndPoint { get; } /// <summary> /// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication diff --git a/SocketHttpListener/Primitives/ICertificate.cs b/SocketHttpListener/Primitives/ICertificate.cs deleted file mode 100644 index ec21e9445..000000000 --- a/SocketHttpListener/Primitives/ICertificate.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace SocketHttpListener.Primitives -{ - public interface ICertificate - { - } -} diff --git a/SocketHttpListener/Primitives/IStreamFactory.cs b/SocketHttpListener/Primitives/IStreamFactory.cs deleted file mode 100644 index 4b623b940..000000000 --- a/SocketHttpListener/Primitives/IStreamFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Net; - -namespace SocketHttpListener.Primitives -{ - public interface IStreamFactory - { - Stream CreateNetworkStream(IAcceptSocket acceptSocket, bool ownsSocket); - Stream CreateSslStream(Stream innerStream, bool leaveInnerStreamOpen); - - Task AuthenticateSslStreamAsServer(Stream stream, ICertificate certificate); - } -} diff --git a/SocketHttpListener/SocketHttpListener.csproj b/SocketHttpListener/SocketHttpListener.csproj index 1aa788931..6ed42ea88 100644 --- a/SocketHttpListener/SocketHttpListener.csproj +++ b/SocketHttpListener/SocketHttpListener.csproj @@ -82,6 +82,7 @@ <Compile Include="Net\HttpStreamAsyncResult.cs" /> <Compile Include="Net\HttpVersion.cs" /> <Compile Include="Net\ListenerPrefix.cs" /> + <Compile Include="Net\SocketAcceptor.cs" /> <Compile Include="Net\UriScheme.cs" /> <Compile Include="Net\WebHeaderCollection.cs" /> <Compile Include="Net\WebHeaderEncoding.cs" /> @@ -89,11 +90,10 @@ <Compile Include="Net\WebSockets\WebSocketContext.cs" /> <Compile Include="Opcode.cs" /> <Compile Include="PayloadData.cs" /> - <Compile Include="Primitives\ICertificate.cs" /> - <Compile Include="Primitives\IStreamFactory.cs" /> <Compile Include="Primitives\ITextEncoding.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Rsv.cs" /> + <Compile Include="SocketStream.cs" /> <Compile Include="WebSocket.cs" /> <Compile Include="WebSocketException.cs" /> <Compile Include="WebSocketFrame.cs" /> diff --git a/SocketHttpListener/SocketStream.cs b/SocketHttpListener/SocketStream.cs new file mode 100644 index 000000000..a4f31eab9 --- /dev/null +++ b/SocketHttpListener/SocketStream.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace SocketHttpListener +{ + public class SocketStream : Stream + { + private readonly Socket _socket; + + public SocketStream(Socket socket, bool ownsSocket) + { + _socket = socket; + } + + public override void Flush() + { + } + + public override bool CanRead + { + get { return true; } + } + public override bool CanSeek + { + get { return false; } + } + public override bool CanWrite + { + get { return true; } + } + public override long Length + { + get { throw new NotImplementedException(); } + } + public override long Position + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public override void Write(byte[] buffer, int offset, int count) + { + _socket.Send(buffer, offset, count, SocketFlags.None); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + _socket.EndSend(asyncResult); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _socket.Receive(buffer, offset, count, SocketFlags.None); + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state); + } + + public override int EndRead(IAsyncResult asyncResult) + { + return _socket.EndReceive(asyncResult); + } + } +} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs index 9966d3fcf..57c075e32 100644 --- a/SocketHttpListener/WebSocket.cs +++ b/SocketHttpListener/WebSocket.cs @@ -880,6 +880,7 @@ namespace SocketHttpListener void IDisposable.Dispose() { Close(CloseStatusCode.Away, null); + GC.SuppressFinalize(this); } #endregion |
