Merge pull request #2897 from MediaBrowser/beta

Beta
This commit is contained in:
Luke 2017-09-20 13:22:39 -04:00 committed by GitHub
commit eb2a133004
276 changed files with 3376 additions and 5822 deletions

View File

@ -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()
{
}
}
}

View File

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

View File

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

View File

@ -394,6 +394,7 @@ namespace Emby.Dlna.Main
_communicationsServer.Dispose();
_communicationsServer = null;
}
GC.SuppressFinalize(this);
}
public void DisposeDlnaServer()

View File

@ -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()
{
}
}
}

View File

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

View File

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

View File

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

View File

@ -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" />
@ -55,9 +54,6 @@
<ItemGroup>
<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>
@ -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.

View File

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

View File

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

View File

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

View File

@ -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[]
{
SKCodecOrigin.LeftBottom,
SKCodecOrigin.LeftTop,
SKCodecOrigin.RightBottom,
SKCodecOrigin.RightTop
};
//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},
//}
// check if we need to turn the image
bool isDifferentOrientation = differentOrientations.Any(o => o == origin);
// define new width/height
var width = isDifferentOrientation ? original.Height : original.Width;
var height = isDifferentOrientation ? original.Width : original.Height;
var bitmap = new SKBitmap(width, height, true);
// todo: the stuff in this switch statement should be rewritten to use pointers
switch (origin)
{
case SKCodecOrigin.LeftBottom:
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;
case SKCodecOrigin.RightTop:
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.RightBottom:
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));
break;
case SKCodecOrigin.LeftTop:
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;
case SKCodecOrigin.BottomLeft:
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:
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;
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);
}
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.BottomRight:
{
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated))
{
float px = bitmap.Width;
px /= 2;
float py = bitmap.Height;
py /= 2;
surface.RotateDegrees(180, px, py);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
case SKCodecOrigin.BottomLeft:
{
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
using (var surface = new SKCanvas(rotated))
{
float px = bitmap.Width;
px /= 2;
float py = bitmap.Height;
py /= 2;
surface.Translate(rotated.Width, 0);
surface.Scale(-1, 1);
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);
surface.RotateDegrees(90);
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 flippedBitmap;
}
}
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);
}
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 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;
}
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; }

View File

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

View File

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

View File

@ -61,9 +61,5 @@ namespace Emby.Drawing
{
throw new NotImplementedException();
}
public void Dispose()
{
}
}
}

View File

@ -491,6 +491,7 @@ namespace Emby.Server.Implementations.Activity
//_logManager.LoggerLoaded -= _logManager_LoggerLoaded;
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
GC.SuppressFinalize(this);
}
/// <summary>

View File

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

View File

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

View File

@ -77,6 +77,7 @@ namespace Emby.Server.Implementations.Data
{
Close();
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -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",
{
// 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_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)",
"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)",
// 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
"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)",
// 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)",
// 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)",
// 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)",
// 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)",
// 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)",
// 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)",
// 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)",
// 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;
}).Where(i => i.HasValue).Select(i => i.Value).ToList();
if (Enum.TryParse(i, true, out parsedValue))
{
return parsedValue;
}
return (TrailerType?)null;
}).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))
connection.ExecuteAll(string.Join(";", new string[]
{
var id = row.GetGuid(0);
string value = row.IsDBNull(1) ? null : row.GetString(1);
"delete from itemvalues where type = 6",
var valuesArray = string.IsNullOrWhiteSpace(value) ? new string[] { } : value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
"insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
newValues.Add(new Tuple<Guid, string[]>(id, valuesArray));
}
@"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 "
Logger.Debug("UpdateInheritedTags - {0} rows", newValues.Count);
if (newValues.Count == 0)
{
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();
deleteStatement.Reset();
deleteStatement.TryBind("@ItemId", guidBlob);
deleteStatement.MoveNext();
foreach (var itemValue in item.Item2)
{
insertStatement.Reset();
insertStatement.TryBind("@ItemId", guidBlob);
insertStatement.TryBind("@Value", itemValue);
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)
{

View File

@ -104,6 +104,7 @@ namespace Emby.Server.Implementations.Diagnostics
public void Dispose()
{
_process.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

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

View File

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

View File

@ -112,6 +112,7 @@ namespace Emby.Server.Implementations.EntryPoints
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
DisposeTimer();
GC.SuppressFinalize(this);
}
private void DisposeTimer()

View File

@ -294,6 +294,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
_disposed = true;
DisposeNat();
GC.SuppressFinalize(this);
}
private void DisposeNat()

View File

@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints
_timer.Dispose();
_timer = null;
}
GC.SuppressFinalize(this);
}
}
}

View File

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

View File

@ -68,6 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints
_timer.Dispose();
_timer = null;
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -72,6 +72,7 @@ namespace Emby.Server.Implementations.EntryPoints
_liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
_liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
_liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
GC.SuppressFinalize(this);
}
}
}

View File

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

View File

@ -174,6 +174,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>

View File

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

View File

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

View File

@ -65,6 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>

View File

@ -130,6 +130,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
GC.SuppressFinalize(this);
}
}
}

View File

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

View File

@ -822,27 +822,6 @@ namespace Emby.Server.Implementations.HttpClientManager
return url;
}
/// <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>

View File

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

View File

@ -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,6 +738,16 @@ namespace Emby.Server.Implementations.HttpServer
return "emby/" + path;
}
private string NormalizeMediaBrowserRoutePath(string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
return "/mediabrowser" + path;
}
return "mediabrowser/" + path;
}
private string DoubleNormalizeEmbyRoutePath(string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
@ -784,16 +758,6 @@ namespace Emby.Server.Implementations.HttpServer
return "emby/emby/" + path;
}
private string NormalizeRoutePath(string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
return "/mediabrowser" + path;
}
return "mediabrowser/" + path;
}
private bool _disposed;
private readonly object _disposeLock = new object();
protected virtual void Dispose(bool disposing)
@ -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);
}
}
}

View File

@ -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,14 +536,22 @@ 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -123,6 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -70,6 +70,7 @@ namespace Emby.Server.Implementations.IO
{
mounter.Dispose();
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -649,6 +649,7 @@ namespace Emby.Server.Implementations.IO
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}

View File

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

View File

@ -524,6 +524,7 @@ namespace Emby.Server.Implementations.Library
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private readonly object _disposeLock = new object();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, GetSha1String(string.Empty), StringComparison.OrdinalIgnoreCase);
return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase);
}
private string GetEmptyHashedString(User user)
{
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)
{
throw new ArgumentNullException("newPasswordSha1");
newPasswordHash = GetHashedString(user, newPassword);
}
user.EasyPassword = newPasswordSha1;
if (string.IsNullOrWhiteSpace(newPasswordHash))
{
throw new ArgumentNullException("newPasswordHash");
}
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()
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -271,6 +271,7 @@ namespace Emby.Server.Implementations.News
_timer.Dispose();
_timer = null;
}
GC.SuppressFinalize(this);
}
}
}

View File

@ -551,6 +551,7 @@ namespace Emby.Server.Implementations.Notifications
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
_userManager.UserLockedOut -= _userManager_UserLockedOut;
GC.SuppressFinalize(this);
}
private void DisposeLibraryUpdateTimer()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,10 +45,15 @@ namespace Emby.Server.Implementations.Services
var bytesResponse = this.Response as byte[];
if (bytesResponse != null)
{
if (response != null)
response.SetContentLength(bytesResponse.Length);
var contentLength = bytesResponse.Length;
await responseStream.WriteAsync(bytesResponse, 0, bytesResponse.Length).ConfigureAwait(false);
if (response != null)
response.SetContentLength(contentLength);
if (contentLength > 0)
{
await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false);
}
return;
}

View File

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

View File

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

View File

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

View File

@ -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
foreach (var name in formData.Keys)
{
if (name == null) continue; //thank you ASP.NET
var values = request.FormData.GetValues(name);
if (values.Count == 1)
{
map[name] = values[0];
}
else
{
for (var i = 0; i < values.Count; i++)
var values = formData.GetValues(name);
if (values.Count == 1)
{
map[name + (i == 0 ? "" : "#" + i)] = values[i];
map[name] = values[0];
}
else
{
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];
}
}
}

View File

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

View File

@ -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);
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))
{
ignored = true;
break;
}
}
if (!ignored)
{
list.Add(prop);
}
}
// else return those properties that are not decorated with IgnoreDataMember
return readableProperties
.Where(prop => prop.GetCustomAttributes(true)
.All(attr =>
{
var name = attr.GetType().Name;
return !IgnoreAttributesNamed.Contains(name);
}))
.Where(prop => !_excludeTypes.Contains(prop.PropertyType))
.ToArray();
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();
}
}
}

View File

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

View File

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

View File

@ -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()
{
}
}
}

View File

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

View File

@ -102,6 +102,7 @@ namespace Emby.Server.Implementations.Session
public void Dispose()
{
_serverManager.WebSocketConnected -= _serverManager_WebSocketConnected;
GC.SuppressFinalize(this);
}
/// <summary>

Some files were not shown because too many files have changed in this diff Show More