mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
commit
eb2a133004
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -394,6 +394,7 @@ namespace Emby.Dlna.Main
|
||||
_communicationsServer.Dispose();
|
||||
_communicationsServer = null;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void DisposeDlnaServer()
|
||||
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
@ -39,6 +39,7 @@ namespace Emby.Drawing.ImageMagick
|
||||
return new[]
|
||||
{
|
||||
"tiff",
|
||||
"tif",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
@ -327,6 +328,7 @@ namespace Emby.Drawing.ImageMagick
|
||||
{
|
||||
_disposed = true;
|
||||
Wand.CloseEnvironment();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -48,7 +48,10 @@ namespace Emby.Drawing.Skia
|
||||
"astc",
|
||||
"ktx",
|
||||
"pkm",
|
||||
"wbmp"
|
||||
"wbmp",
|
||||
|
||||
// working on windows at least
|
||||
"cr2"
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -193,18 +196,33 @@ 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);
|
||||
|
||||
if (bitmap != null)
|
||||
{
|
||||
// decode
|
||||
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
|
||||
origin = codec.Origin;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin = SKCodecOrigin.TopLeft;
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultBitmap = SKBitmap.Decode(path);
|
||||
|
||||
@ -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 (bitmap != null)
|
||||
{
|
||||
if (origin != SKCodecOrigin.TopLeft)
|
||||
{
|
||||
using (bitmap)
|
||||
{
|
||||
return RotateAndFlip(bitmap, origin);
|
||||
return OrientImage(bitmap, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,83 +282,154 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
@ -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>
|
@ -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()
|
||||
|
@ -61,9 +61,5 @@ namespace Emby.Drawing
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -491,6 +491,7 @@ namespace Emby.Server.Implementations.Activity
|
||||
//_logManager.LoggerLoaded -= _logManager_LoggerLoaded;
|
||||
|
||||
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
{
|
||||
|
@ -77,6 +77,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
Close();
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -1143,16 +1170,14 @@ namespace Emby.Server.Implementations.Data
|
||||
delimeter +
|
||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
||||
delimeter +
|
||||
image.Type +
|
||||
delimeter +
|
||||
image.IsPlaceholder;
|
||||
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;
|
||||
}
|
||||
@ -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))
|
||||
{
|
||||
@ -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);
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ namespace Emby.Server.Implementations.Diagnostics
|
||||
public void Dispose()
|
||||
{
|
||||
_process.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -112,6 +112,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
|
||||
|
||||
DisposeTimer();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeTimer()
|
||||
|
@ -294,6 +294,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
_disposed = true;
|
||||
DisposeNat();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeNat()
|
||||
|
@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -68,6 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
_liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
|
||||
_liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
|
||||
_liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -130,6 +130,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
public void Dispose()
|
||||
{
|
||||
_sessionManager.SessionStarted -= _sessionManager_SessionStarted;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -424,12 +424,15 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (!options.ResponseHeaders.ContainsKey("Content-Disposition"))
|
||||
{
|
||||
// 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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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"];
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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()
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
mounter.Dispose();
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -649,6 +649,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -524,6 +524,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private readonly object _disposeLock = new object();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
|
@ -285,7 +285,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var filters = new List<string>();
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -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}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,21 +106,12 @@ 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))
|
||||
{
|
||||
StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Resolve(openTaskCompletionSource);
|
||||
|
||||
await _multicastStream.CopyUntilCancelled(response.Content, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@ -144,7 +134,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
|
||||
_liveStreamTaskCompletionSource.TrySetResult(true);
|
||||
//await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
|
||||
await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
@ -157,57 +147,32 @@ 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
@ -125,8 +122,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_logger.Info("Opened HDHR UDP stream from {0}", remoteAddress);
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (_enableFileBuffer)
|
||||
{
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
||||
@ -134,11 +129,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
@ -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;
|
||||
return CopyFileTo(_tempFilePath, stream, cancellationToken);
|
||||
}
|
||||
|
||||
var path = _tempFilePath;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -271,6 +271,7 @@ namespace Emby.Server.Implementations.News
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -551,6 +551,7 @@ namespace Emby.Server.Implementations.Notifications
|
||||
|
||||
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
|
||||
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DisposeLibraryUpdateTimer()
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 },
|
||||
|
@ -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>()
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
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);
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -150,9 +156,16 @@ namespace Emby.Server.Implementations.Services
|
||||
serializer(result, ms);
|
||||
|
||||
ms.Position = 0;
|
||||
response.SetContentLength(ms.Length);
|
||||
|
||||
var contentLength = ms.Length;
|
||||
|
||||
response.SetContentLength(contentLength);
|
||||
|
||||
if (contentLength > 0)
|
||||
{
|
||||
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
//serializer(result, outputStream);
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,13 +222,16 @@ 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)
|
||||
{
|
||||
foreach (var name in formData.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
|
||||
var values = request.FormData.GetValues(name);
|
||||
var values = formData.GetValues(name);
|
||||
if (values.Count == 1)
|
||||
{
|
||||
map[name] = values[0];
|
||||
@ -248,6 +245,7 @@ namespace Emby.Server.Implementations.Services
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
foreach (var name in formData.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
map[name] = request.FormData[name];
|
||||
map[name] = formData[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,8 +21,6 @@ namespace Emby.Server.Implementations.Services
|
||||
readonly bool[] componentsWithSeparators;
|
||||
|
||||
private readonly string restPath;
|
||||
private readonly string allowedVerbs;
|
||||
private readonly bool allowsAllVerbs;
|
||||
public bool IsWildCardPath { get; private set; }
|
||||
|
||||
private readonly string[] literalsToMatch;
|
||||
@ -46,35 +44,21 @@ namespace Emby.Server.Implementations.Services
|
||||
/// </summary>
|
||||
public int TotalComponentsCount { get; set; }
|
||||
|
||||
public string[] Verbs
|
||||
{
|
||||
get
|
||||
{
|
||||
return allowsAllVerbs
|
||||
? new[] { "ANY" }
|
||||
: AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
public string[] Verbs { get; private set; }
|
||||
|
||||
public Type RequestType { get; private set; }
|
||||
|
||||
public string Path { get { return this.restPath; } }
|
||||
|
||||
public string Summary { get; private set; }
|
||||
|
||||
public string Notes { get; private set; }
|
||||
|
||||
public bool AllowsAllVerbs { get { return this.allowsAllVerbs; } }
|
||||
|
||||
public string AllowedVerbs { get { return this.allowedVerbs; } }
|
||||
public string Description { get; private set; }
|
||||
public bool IsHidden { get; private set; }
|
||||
|
||||
public int Priority { get; set; } //passed back to RouteAttribute
|
||||
|
||||
public static string[] GetPathPartsForMatching(string pathInfo)
|
||||
{
|
||||
var parts = pathInfo.ToLower().Split(PathSeperatorChar)
|
||||
.Where(x => !String.IsNullOrEmpty(x)).ToArray();
|
||||
return parts;
|
||||
return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
|
||||
@ -109,18 +93,15 @@ namespace Emby.Server.Implementations.Services
|
||||
return list;
|
||||
}
|
||||
|
||||
public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, string summary = null, string notes = null)
|
||||
public RestPath(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type requestType, string path, string verbs, bool isHidden = false, string summary = null, string description = null)
|
||||
{
|
||||
this.RequestType = requestType;
|
||||
this.Summary = summary;
|
||||
this.Notes = notes;
|
||||
this.IsHidden = isHidden;
|
||||
this.Description = description;
|
||||
this.restPath = path;
|
||||
|
||||
this.allowsAllVerbs = verbs == null || String.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase);
|
||||
if (!this.allowsAllVerbs)
|
||||
{
|
||||
this.allowedVerbs = verbs.ToUpper();
|
||||
}
|
||||
this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpper().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var componentsList = new List<string>();
|
||||
|
||||
@ -153,7 +134,6 @@ namespace Emby.Server.Implementations.Services
|
||||
this.PathComponentsCount = this.componentsWithSeparators.Length;
|
||||
string firstLiteralMatch = null;
|
||||
|
||||
var sbHashKey = new StringBuilder();
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
var component = components[i];
|
||||
@ -172,7 +152,6 @@ namespace Emby.Server.Implementations.Services
|
||||
else
|
||||
{
|
||||
this.literalsToMatch[i] = component.ToLower();
|
||||
sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch);
|
||||
|
||||
if (firstLiteralMatch == null)
|
||||
{
|
||||
@ -198,9 +177,6 @@ namespace Emby.Server.Implementations.Services
|
||||
? this.PathComponentsCount + PathSeperator + firstLiteralMatch
|
||||
: WildCardChar + PathSeperator + firstLiteralMatch;
|
||||
|
||||
this.IsValid = sbHashKey.Length > 0;
|
||||
this.UniqueMatchHashKey = sbHashKey.ToString();
|
||||
|
||||
this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
|
||||
RegisterCaseInsenstivePropertyNameMappings();
|
||||
}
|
||||
@ -220,26 +196,46 @@ namespace Emby.Server.Implementations.Services
|
||||
};
|
||||
|
||||
|
||||
private static List<Type> _excludeTypes = new List<Type> { typeof(Stream) };
|
||||
private static Type excludeType = typeof(Stream);
|
||||
|
||||
internal static PropertyInfo[] GetSerializableProperties(Type type)
|
||||
internal static List<PropertyInfo> GetSerializableProperties(Type type)
|
||||
{
|
||||
var properties = GetPublicProperties(type);
|
||||
var readableProperties = properties.Where(x => x.GetMethod != null);
|
||||
var list = new List<PropertyInfo>();
|
||||
var props = GetPublicProperties(type);
|
||||
|
||||
// else return those properties that are not decorated with IgnoreDataMember
|
||||
return readableProperties
|
||||
.Where(prop => prop.GetCustomAttributes(true)
|
||||
.All(attr =>
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var name = attr.GetType().Name;
|
||||
return !IgnoreAttributesNamed.Contains(name);
|
||||
}))
|
||||
.Where(prop => !_excludeTypes.Contains(prop.PropertyType))
|
||||
.ToArray();
|
||||
if (prop.GetMethod == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
private static PropertyInfo[] GetPublicProperties(Type type)
|
||||
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 list;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 });
|
||||
|
260
Emby.Server.Implementations/Services/SwaggerService.cs
Normal file
260
Emby.Server.Implementations/Services/SwaggerService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user