mirror of https://github.com/jellyfin/jellyfin.git
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;
|
||||
|
@ -38,7 +38,8 @@ namespace Emby.Drawing.ImageMagick
|
|||
// Some common file name extensions for RAW picture files include: .cr2, .crw, .dng, .nef, .orf, .rw2, .pef, .arw, .sr2, .srf, and .tif.
|
||||
return new[]
|
||||
{
|
||||
"tiff",
|
||||
"tiff",
|
||||
"tif",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
|
@ -327,6 +328,7 @@ namespace Emby.Drawing.ImageMagick
|
|||
{
|
||||
_disposed = true;
|
||||
Wand.CloseEnvironment();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
|
|
|
@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +79,7 @@ namespace Emby.Drawing.Skia
|
|||
|
||||
private static bool IsTransparent(SKColor color)
|
||||
{
|
||||
|
||||
|
||||
return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
|
||||
}
|
||||
|
||||
|
@ -193,16 +196,31 @@ namespace Emby.Drawing.Skia
|
|||
{
|
||||
using (var stream = new SKFileStream(path))
|
||||
{
|
||||
var codec = SKCodec.Create(stream);
|
||||
using (var codec = SKCodec.Create(stream))
|
||||
{
|
||||
if (codec == null)
|
||||
{
|
||||
origin = SKCodecOrigin.TopLeft;
|
||||
return null;
|
||||
}
|
||||
|
||||
// create the bitmap
|
||||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||
// decode
|
||||
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
// create the bitmap
|
||||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||
|
||||
origin = codec.Origin;
|
||||
if (bitmap != null)
|
||||
{
|
||||
// decode
|
||||
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||
|
||||
return bitmap;
|
||||
origin = codec.Origin;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin = SKCodecOrigin.TopLeft;
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +257,7 @@ namespace Emby.Drawing.Skia
|
|||
return Decode(path, forceAnalyzeBitmap, out origin);
|
||||
}
|
||||
|
||||
private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation)
|
||||
private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient)
|
||||
{
|
||||
SKCodecOrigin origin;
|
||||
|
||||
|
@ -247,11 +265,14 @@ namespace Emby.Drawing.Skia
|
|||
{
|
||||
var bitmap = GetBitmap(path, cropWhitespace, true, out origin);
|
||||
|
||||
if (origin != SKCodecOrigin.TopLeft)
|
||||
if (bitmap != null)
|
||||
{
|
||||
using (bitmap)
|
||||
if (origin != SKCodecOrigin.TopLeft)
|
||||
{
|
||||
return RotateAndFlip(bitmap, origin);
|
||||
using (bitmap)
|
||||
{
|
||||
return OrientImage(bitmap, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,82 +282,153 @@ namespace Emby.Drawing.Skia
|
|||
return GetBitmap(path, cropWhitespace, false, out origin);
|
||||
}
|
||||
|
||||
private SKBitmap RotateAndFlip(SKBitmap original, SKCodecOrigin origin)
|
||||
private SKBitmap OrientImage(SKBitmap bitmap, SKCodecOrigin origin)
|
||||
{
|
||||
// these are the origins that represent a 90 degree turn in some fashion
|
||||
var differentOrientations = new SKCodecOrigin[]
|
||||
{
|
||||
SKCodecOrigin.LeftBottom,
|
||||
SKCodecOrigin.LeftTop,
|
||||
SKCodecOrigin.RightBottom,
|
||||
SKCodecOrigin.RightTop
|
||||
};
|
||||
//var transformations = {
|
||||
// 2: { rotate: 0, flip: true},
|
||||
// 3: { rotate: 180, flip: false},
|
||||
// 4: { rotate: 180, flip: true},
|
||||
// 5: { rotate: 90, flip: true},
|
||||
// 6: { rotate: 90, flip: false},
|
||||
// 7: { rotate: 270, flip: true},
|
||||
// 8: { rotate: 270, flip: false},
|
||||
//}
|
||||
|
||||
// check if we need to turn the image
|
||||
bool isDifferentOrientation = differentOrientations.Any(o => o == origin);
|
||||
|
||||
// define new width/height
|
||||
var width = isDifferentOrientation ? original.Height : original.Width;
|
||||
var height = isDifferentOrientation ? original.Width : original.Height;
|
||||
|
||||
var bitmap = new SKBitmap(width, height, true);
|
||||
|
||||
// todo: the stuff in this switch statement should be rewritten to use pointers
|
||||
switch (origin)
|
||||
{
|
||||
case SKCodecOrigin.LeftBottom:
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(y, original.Width - 1 - x, original.GetPixel(x, y));
|
||||
break;
|
||||
|
||||
case SKCodecOrigin.RightTop:
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(original.Height - 1 - y, x, original.GetPixel(x, y));
|
||||
break;
|
||||
|
||||
case SKCodecOrigin.RightBottom:
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(original.Height - 1 - y, original.Width - 1 - x, original.GetPixel(x, y));
|
||||
|
||||
break;
|
||||
|
||||
case SKCodecOrigin.LeftTop:
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(y, x, original.GetPixel(x, y));
|
||||
break;
|
||||
|
||||
case SKCodecOrigin.BottomLeft:
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(x, original.Height - 1 - y, original.GetPixel(x, y));
|
||||
break;
|
||||
|
||||
case SKCodecOrigin.BottomRight:
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(original.Width - 1 - x, original.Height - 1 - y, original.GetPixel(x, y));
|
||||
break;
|
||||
|
||||
case SKCodecOrigin.TopRight:
|
||||
{
|
||||
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
surface.Translate(rotated.Width, 0);
|
||||
surface.Scale(-1, 1);
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
for (var x = 0; x < original.Width; x++)
|
||||
for (var y = 0; y < original.Height; y++)
|
||||
bitmap.SetPixel(original.Width - 1 - x, y, original.GetPixel(x, y));
|
||||
break;
|
||||
return rotated;
|
||||
}
|
||||
|
||||
case SKCodecOrigin.BottomRight:
|
||||
{
|
||||
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
float px = bitmap.Width;
|
||||
px /= 2;
|
||||
|
||||
float py = bitmap.Height;
|
||||
py /= 2;
|
||||
|
||||
surface.RotateDegrees(180, px, py);
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
return rotated;
|
||||
}
|
||||
|
||||
case SKCodecOrigin.BottomLeft:
|
||||
{
|
||||
var rotated = new SKBitmap(bitmap.Width, bitmap.Height);
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
float px = bitmap.Width;
|
||||
px /= 2;
|
||||
|
||||
float py = bitmap.Height;
|
||||
py /= 2;
|
||||
|
||||
surface.Translate(rotated.Width, 0);
|
||||
surface.Scale(-1, 1);
|
||||
|
||||
surface.RotateDegrees(180, px, py);
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
return rotated;
|
||||
}
|
||||
|
||||
case SKCodecOrigin.LeftTop:
|
||||
{
|
||||
// TODO: Remove dual canvases, had trouble with flipping
|
||||
using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width))
|
||||
{
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
surface.Translate(rotated.Width, 0);
|
||||
|
||||
surface.RotateDegrees(90);
|
||||
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
|
||||
using (var flippedCanvas = new SKCanvas(flippedBitmap))
|
||||
{
|
||||
flippedCanvas.Translate(flippedBitmap.Width, 0);
|
||||
flippedCanvas.Scale(-1, 1);
|
||||
flippedCanvas.DrawBitmap(rotated, 0, 0);
|
||||
}
|
||||
|
||||
return flippedBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
case SKCodecOrigin.RightTop:
|
||||
{
|
||||
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
surface.Translate(rotated.Width, 0);
|
||||
surface.RotateDegrees(90);
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
return rotated;
|
||||
}
|
||||
|
||||
case SKCodecOrigin.RightBottom:
|
||||
{
|
||||
// TODO: Remove dual canvases, had trouble with flipping
|
||||
using (var rotated = new SKBitmap(bitmap.Height, bitmap.Width))
|
||||
{
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
surface.Translate(0, rotated.Height);
|
||||
surface.RotateDegrees(270);
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
var flippedBitmap = new SKBitmap(rotated.Width, rotated.Height);
|
||||
using (var flippedCanvas = new SKCanvas(flippedBitmap))
|
||||
{
|
||||
flippedCanvas.Translate(flippedBitmap.Width, 0);
|
||||
flippedCanvas.Scale(-1, 1);
|
||||
flippedCanvas.DrawBitmap(rotated, 0, 0);
|
||||
}
|
||||
|
||||
return flippedBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
case SKCodecOrigin.LeftBottom:
|
||||
{
|
||||
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
|
||||
using (var surface = new SKCanvas(rotated))
|
||||
{
|
||||
surface.Translate(0, rotated.Height);
|
||||
surface.RotateDegrees(270);
|
||||
surface.DrawBitmap(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
return rotated;
|
||||
}
|
||||
|
||||
default:
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
|
||||
|
@ -357,11 +449,11 @@ namespace Emby.Drawing.Skia
|
|||
var blur = options.Blur ?? 0;
|
||||
var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
|
||||
|
||||
using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient, orientation))
|
||||
using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace, autoOrient))
|
||||
{
|
||||
if (bitmap == null)
|
||||
{
|
||||
throw new Exception(string.Format("Skia unable to read image {0}", inputPath));
|
||||
throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath));
|
||||
}
|
||||
|
||||
//_logger.Info("Color type {0}", bitmap.Info.ColorType);
|
||||
|
@ -505,10 +597,6 @@ namespace Emby.Drawing.Skia
|
|||
get { return "Skia"; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool SupportsImageCollageCreation
|
||||
{
|
||||
get { return true; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,34 +139,34 @@ namespace Emby.Server.Implementations.Data
|
|||
RunDefaultInitialization(connection);
|
||||
|
||||
var createMediaStreamsTableCommand
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
|
||||
string[] queries = {
|
||||
"PRAGMA locking_mode=EXCLUSIVE",
|
||||
"PRAGMA locking_mode=EXCLUSIVE",
|
||||
|
||||
"create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)",
|
||||
"create table if not exists TypedBaseItems (guid GUID primary key NOT NULL, type TEXT NOT NULL, data BLOB NULL, ParentId GUID NULL, Path TEXT NULL)",
|
||||
|
||||
"create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))",
|
||||
"create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
|
||||
"create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)",
|
||||
"create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))",
|
||||
"create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
|
||||
"create index if not exists idx_AncestorIds5 on AncestorIds(AncestorIdText,ItemId)",
|
||||
|
||||
"create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)",
|
||||
"create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)",
|
||||
|
||||
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
|
||||
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
|
||||
|
||||
"drop index if exists idxPeopleItemId",
|
||||
"create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)",
|
||||
"create index if not exists idxPeopleName on People(Name)",
|
||||
"drop index if exists idxPeopleItemId",
|
||||
"create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)",
|
||||
"create index if not exists idxPeopleName on People(Name)",
|
||||
|
||||
"create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
|
||||
"create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
|
||||
|
||||
createMediaStreamsTableCommand,
|
||||
createMediaStreamsTableCommand,
|
||||
|
||||
"create index if not exists idx_mediastreams1 on mediastreams(ItemId)",
|
||||
"create index if not exists idx_mediastreams1 on mediastreams(ItemId)",
|
||||
|
||||
"pragma shrink_memory"
|
||||
"pragma shrink_memory"
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
connection.RunQueries(queries);
|
||||
|
||||
|
@ -252,6 +252,8 @@ namespace Emby.Server.Implementations.Data
|
|||
AddColumn(db, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
|
||||
|
||||
existingColumnNames = GetColumnNames(db, "ItemValues");
|
||||
AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames);
|
||||
|
@ -276,80 +278,80 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
string[] postQueries =
|
||||
|
||||
{
|
||||
// obsolete
|
||||
"drop index if exists idx_TypedBaseItems",
|
||||
"drop index if exists idx_mediastreams",
|
||||
"drop index if exists idx_"+ChaptersTableName,
|
||||
"drop index if exists idx_UserDataKeys1",
|
||||
"drop index if exists idx_UserDataKeys2",
|
||||
"drop index if exists idx_TypeTopParentId3",
|
||||
"drop index if exists idx_TypeTopParentId2",
|
||||
"drop index if exists idx_TypeTopParentId4",
|
||||
"drop index if exists idx_Type",
|
||||
"drop index if exists idx_TypeTopParentId",
|
||||
"drop index if exists idx_GuidType",
|
||||
"drop index if exists idx_TopParentId",
|
||||
"drop index if exists idx_TypeTopParentId6",
|
||||
"drop index if exists idx_ItemValues2",
|
||||
"drop index if exists Idx_ProviderIds",
|
||||
"drop index if exists idx_ItemValues3",
|
||||
"drop index if exists idx_ItemValues4",
|
||||
"drop index if exists idx_ItemValues5",
|
||||
"drop index if exists idx_UserDataKeys3",
|
||||
"drop table if exists UserDataKeys",
|
||||
"drop table if exists ProviderIds",
|
||||
"drop index if exists Idx_ProviderIds1",
|
||||
"drop table if exists Images",
|
||||
"drop index if exists idx_Images",
|
||||
"drop index if exists idx_TypeSeriesPresentationUniqueKey",
|
||||
"drop index if exists idx_SeriesPresentationUniqueKey",
|
||||
"drop index if exists idx_TypeSeriesPresentationUniqueKey2",
|
||||
"drop index if exists idx_AncestorIds3",
|
||||
"drop index if exists idx_AncestorIds4",
|
||||
"drop index if exists idx_AncestorIds2",
|
||||
{
|
||||
// obsolete
|
||||
"drop index if exists idx_TypedBaseItems",
|
||||
"drop index if exists idx_mediastreams",
|
||||
"drop index if exists idx_"+ChaptersTableName,
|
||||
"drop index if exists idx_UserDataKeys1",
|
||||
"drop index if exists idx_UserDataKeys2",
|
||||
"drop index if exists idx_TypeTopParentId3",
|
||||
"drop index if exists idx_TypeTopParentId2",
|
||||
"drop index if exists idx_TypeTopParentId4",
|
||||
"drop index if exists idx_Type",
|
||||
"drop index if exists idx_TypeTopParentId",
|
||||
"drop index if exists idx_GuidType",
|
||||
"drop index if exists idx_TopParentId",
|
||||
"drop index if exists idx_TypeTopParentId6",
|
||||
"drop index if exists idx_ItemValues2",
|
||||
"drop index if exists Idx_ProviderIds",
|
||||
"drop index if exists idx_ItemValues3",
|
||||
"drop index if exists idx_ItemValues4",
|
||||
"drop index if exists idx_ItemValues5",
|
||||
"drop index if exists idx_UserDataKeys3",
|
||||
"drop table if exists UserDataKeys",
|
||||
"drop table if exists ProviderIds",
|
||||
"drop index if exists Idx_ProviderIds1",
|
||||
"drop table if exists Images",
|
||||
"drop index if exists idx_Images",
|
||||
"drop index if exists idx_TypeSeriesPresentationUniqueKey",
|
||||
"drop index if exists idx_SeriesPresentationUniqueKey",
|
||||
"drop index if exists idx_TypeSeriesPresentationUniqueKey2",
|
||||
"drop index if exists idx_AncestorIds3",
|
||||
"drop index if exists idx_AncestorIds4",
|
||||
"drop index if exists idx_AncestorIds2",
|
||||
|
||||
"create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)",
|
||||
"create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
|
||||
"create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)",
|
||||
"create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
|
||||
|
||||
"create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)",
|
||||
"create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)",
|
||||
//"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)",
|
||||
"create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)",
|
||||
"create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)",
|
||||
"create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)",
|
||||
//"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)",
|
||||
"create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)",
|
||||
|
||||
// covering index
|
||||
"create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)",
|
||||
// covering index
|
||||
"create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)",
|
||||
|
||||
// series
|
||||
"create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)",
|
||||
// series
|
||||
"create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)",
|
||||
|
||||
// series counts
|
||||
// seriesdateplayed sort order
|
||||
"create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)",
|
||||
// series counts
|
||||
// seriesdateplayed sort order
|
||||
"create index if not exists idx_TypeSeriesPresentationUniqueKey3 on TypedBaseItems(SeriesPresentationUniqueKey,Type,IsFolder,IsVirtualItem)",
|
||||
|
||||
// live tv programs
|
||||
"create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
|
||||
// live tv programs
|
||||
"create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
|
||||
|
||||
// covering index for getitemvalues
|
||||
"create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)",
|
||||
// covering index for getitemvalues
|
||||
"create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)",
|
||||
|
||||
// used by movie suggestions
|
||||
"create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)",
|
||||
"create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)",
|
||||
// used by movie suggestions
|
||||
"create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)",
|
||||
"create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)",
|
||||
|
||||
// latest items
|
||||
"create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)",
|
||||
"create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)",
|
||||
// latest items
|
||||
"create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)",
|
||||
"create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)",
|
||||
|
||||
// resume
|
||||
"create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)",
|
||||
// resume
|
||||
"create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)",
|
||||
|
||||
// items by name
|
||||
"create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)",
|
||||
"create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)",
|
||||
// items by name
|
||||
"create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)",
|
||||
"create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)",
|
||||
|
||||
// Used to update inherited tags
|
||||
"create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)",
|
||||
// Used to update inherited tags
|
||||
"create index if not exists idx_ItemValues8 on ItemValues(Type, ItemId, Value)",
|
||||
};
|
||||
|
||||
connection.RunQueries(postQueries);
|
||||
|
@ -457,7 +459,9 @@ namespace Emby.Server.Implementations.Data
|
|||
"Artists",
|
||||
"AlbumArtists",
|
||||
"ExternalId",
|
||||
"SeriesPresentationUniqueKey"
|
||||
"SeriesPresentationUniqueKey",
|
||||
"ShowId",
|
||||
"OwnerId"
|
||||
};
|
||||
|
||||
private readonly string[] _mediaStreamSaveColumns =
|
||||
|
@ -577,7 +581,9 @@ namespace Emby.Server.Implementations.Data
|
|||
"Artists",
|
||||
"AlbumArtists",
|
||||
"ExternalId",
|
||||
"SeriesPresentationUniqueKey"
|
||||
"SeriesPresentationUniqueKey",
|
||||
"ShowId",
|
||||
"OwnerId"
|
||||
};
|
||||
|
||||
var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
|
||||
|
@ -781,13 +787,14 @@ namespace Emby.Server.Implementations.Data
|
|||
saveItemStatement.TryBind("@PremiereDate", item.PremiereDate);
|
||||
saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
|
||||
|
||||
if (item.ParentId == Guid.Empty)
|
||||
var parentId = item.ParentId;
|
||||
if (parentId == Guid.Empty)
|
||||
{
|
||||
saveItemStatement.TryBindNull("@ParentId");
|
||||
}
|
||||
else
|
||||
{
|
||||
saveItemStatement.TryBind("@ParentId", item.ParentId);
|
||||
saveItemStatement.TryBind("@ParentId", parentId);
|
||||
}
|
||||
|
||||
if (item.Genres.Count > 0)
|
||||
|
@ -1044,6 +1051,26 @@ namespace Emby.Server.Implementations.Data
|
|||
saveItemStatement.TryBind("@AlbumArtists", albumArtists);
|
||||
saveItemStatement.TryBind("@ExternalId", item.ExternalId);
|
||||
|
||||
var program = item as LiveTvProgram;
|
||||
if (program != null)
|
||||
{
|
||||
saveItemStatement.TryBind("@ShowId", program.ShowId);
|
||||
}
|
||||
else
|
||||
{
|
||||
saveItemStatement.TryBindNull("@ShowId");
|
||||
}
|
||||
|
||||
var ownerId = item.OwnerId;
|
||||
if (ownerId != Guid.Empty)
|
||||
{
|
||||
saveItemStatement.TryBind("@OwnerId", ownerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
saveItemStatement.TryBindNull("@OwnerId");
|
||||
}
|
||||
|
||||
saveItemStatement.MoveNext();
|
||||
}
|
||||
|
||||
|
@ -1140,19 +1167,17 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
|
||||
return path +
|
||||
delimeter +
|
||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
||||
delimeter +
|
||||
image.Type +
|
||||
delimeter +
|
||||
image.IsPlaceholder;
|
||||
delimeter +
|
||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
||||
delimeter +
|
||||
image.Type;
|
||||
}
|
||||
|
||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
||||
{
|
||||
var parts = value.Split(new[] { '*' }, StringSplitOptions.None);
|
||||
|
||||
if (parts.Length != 4)
|
||||
if (parts.Length < 3)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -1160,9 +1185,18 @@ namespace Emby.Server.Implementations.Data
|
|||
var image = new ItemImageInfo();
|
||||
|
||||
image.Path = parts[0];
|
||||
image.DateModified = new DateTime(long.Parse(parts[1], CultureInfo.InvariantCulture), DateTimeKind.Utc);
|
||||
image.Type = (ImageType)Enum.Parse(typeof(ImageType), parts[2], true);
|
||||
image.IsPlaceholder = string.Equals(parts[3], true.ToString(), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
long ticks;
|
||||
if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out ticks))
|
||||
{
|
||||
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
ImageType type;
|
||||
if (Enum.TryParse(parts[2], true, out type))
|
||||
{
|
||||
image.Type = type;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
@ -1696,17 +1730,17 @@ namespace Emby.Server.Implementations.Data
|
|||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
trailer.TrailerTypes = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(
|
||||
i =>
|
||||
{
|
||||
TrailerType parsedValue;
|
||||
|
||||
if (Enum.TryParse(i, true, out parsedValue))
|
||||
i =>
|
||||
{
|
||||
return parsedValue;
|
||||
}
|
||||
return (TrailerType?)null;
|
||||
TrailerType parsedValue;
|
||||
|
||||
}).Where(i => i.HasValue).Select(i => i.Value).ToList();
|
||||
if (Enum.TryParse(i, true, out parsedValue))
|
||||
{
|
||||
return parsedValue;
|
||||
}
|
||||
return (TrailerType?)null;
|
||||
|
||||
}).Where(i => i.HasValue).Select(i => i.Value).ToList();
|
||||
}
|
||||
}
|
||||
index++;
|
||||
|
@ -1935,6 +1969,29 @@ namespace Emby.Server.Implementations.Data
|
|||
index++;
|
||||
}
|
||||
|
||||
if (enableProgramAttributes)
|
||||
{
|
||||
var program = item as LiveTvProgram;
|
||||
if (program != null)
|
||||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
program.ShowId = reader.GetString(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
item.OwnerId = reader.GetGuid(index);
|
||||
}
|
||||
index++;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@ -2135,8 +2192,7 @@ namespace Emby.Server.Implementations.Data
|
|||
//return true;
|
||||
}
|
||||
|
||||
var sortingFields = query.SortBy.ToList();
|
||||
sortingFields.AddRange(query.OrderBy.Select(i => i.Item1));
|
||||
var sortingFields = query.OrderBy.Select(i => i.Item1).ToList();
|
||||
|
||||
if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -2192,8 +2248,8 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
|
||||
private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
|
||||
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
||||
.ToList();
|
||||
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
|
||||
.ToList();
|
||||
|
||||
private IEnumerable<string> GetColumnNamesFromField(ItemFields field)
|
||||
{
|
||||
|
@ -2295,13 +2351,13 @@ namespace Emby.Server.Implementations.Data
|
|||
private bool HasStartDate(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -2358,11 +2414,11 @@ namespace Emby.Server.Implementations.Data
|
|||
private bool HasArtistFields(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"PhotoAlbum"
|
||||
};
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -2391,9 +2447,9 @@ namespace Emby.Server.Implementations.Data
|
|||
private bool HasSeriesFields(InternalItemsQuery query)
|
||||
{
|
||||
var excludeParentTypes = new string[]
|
||||
{
|
||||
{
|
||||
"PhotoAlbum"
|
||||
};
|
||||
};
|
||||
|
||||
if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -2442,6 +2498,7 @@ namespace Emby.Server.Implementations.Data
|
|||
list.Remove("IsPremiere");
|
||||
list.Remove("EpisodeTitle");
|
||||
list.Remove("IsRepeat");
|
||||
list.Remove("ShowId");
|
||||
}
|
||||
|
||||
if (!HasEpisodeAttributes(query))
|
||||
|
@ -2975,16 +3032,7 @@ namespace Emby.Server.Implementations.Data
|
|||
private string GetOrderByText(InternalItemsQuery query)
|
||||
{
|
||||
var orderBy = query.OrderBy.ToList();
|
||||
var enableOrderInversion = true;
|
||||
|
||||
if (orderBy.Count == 0)
|
||||
{
|
||||
orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder)));
|
||||
}
|
||||
else
|
||||
{
|
||||
enableOrderInversion = false;
|
||||
}
|
||||
var enableOrderInversion = false;
|
||||
|
||||
if (query.SimilarTo != null)
|
||||
{
|
||||
|
@ -2993,12 +3041,10 @@ namespace Emby.Server.Implementations.Data
|
|||
orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
|
||||
orderBy.Add(new Tuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
|
||||
//orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
|
||||
query.SortOrder = SortOrder.Descending;
|
||||
enableOrderInversion = false;
|
||||
}
|
||||
}
|
||||
|
||||
query.OrderBy = orderBy;
|
||||
query.OrderBy = orderBy.ToArray();
|
||||
|
||||
if (orderBy.Count == 0)
|
||||
{
|
||||
|
@ -4235,6 +4281,54 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
|
||||
{
|
||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
|
||||
{
|
||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
|
||||
{
|
||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
|
||||
{
|
||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
if (query.HasChapterImages.HasValue)
|
||||
{
|
||||
if (query.HasChapterImages.Value)
|
||||
{
|
||||
whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) not null)");
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("((select imagepath from Chapters2 where Chapters2.ItemId=A.Guid and imagepath not null limit 1) is null)");
|
||||
}
|
||||
}
|
||||
|
||||
if (query.HasDeadParentId.HasValue && query.HasDeadParentId.Value)
|
||||
{
|
||||
whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)");
|
||||
|
@ -4316,7 +4410,7 @@ namespace Emby.Server.Implementations.Data
|
|||
index++;
|
||||
}
|
||||
|
||||
whereClauses.Add(string.Join(" OR ", includeIds.ToArray()));
|
||||
whereClauses.Add("(" + string.Join(" OR ", includeIds.ToArray()) + ")");
|
||||
}
|
||||
if (query.ExcludeItemIds.Length > 0)
|
||||
{
|
||||
|
@ -4400,7 +4494,6 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList();
|
||||
var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
|
||||
|
||||
|
@ -4654,63 +4747,24 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
private void UpdateInheritedTags(CancellationToken cancellationToken)
|
||||
{
|
||||
var newValues = new List<Tuple<Guid, string[]>>();
|
||||
|
||||
var commandText = @"select guid,
|
||||
(select group_concat(Value, '|') from ItemValues where (ItemValues.ItemId = Outer.Guid OR ItemValues.ItemId in ((Select AncestorId from AncestorIds where AncestorIds.ItemId=Outer.guid))) and ItemValues.Type = 4) NewInheritedTags,
|
||||
(select group_concat(Value, '|') from ItemValues where ItemValues.ItemId = Outer.Guid and ItemValues.Type = 6) CurrentInheritedTags
|
||||
from typedbaseitems as Outer
|
||||
where (NewInheritedTags <> CurrentInheritedTags or (NewInheritedTags is null) <> (CurrentInheritedTags is null))
|
||||
limit 100";
|
||||
|
||||
using (WriteLock.Write())
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
foreach (var row in connection.Query(commandText))
|
||||
connection.ExecuteAll(string.Join(";", new string[]
|
||||
{
|
||||
var id = row.GetGuid(0);
|
||||
string value = row.IsDBNull(1) ? null : row.GetString(1);
|
||||
"delete from itemvalues where type = 6",
|
||||
|
||||
var valuesArray = string.IsNullOrWhiteSpace(value) ? new string[] { } : value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
"insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
|
||||
|
||||
newValues.Add(new Tuple<Guid, string[]>(id, valuesArray));
|
||||
}
|
||||
@"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
|
||||
FROM AncestorIds
|
||||
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
|
||||
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 "
|
||||
|
||||
Logger.Debug("UpdateInheritedTags - {0} rows", newValues.Count);
|
||||
if (newValues.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var insertStatement = PrepareStatement(connection, "insert into ItemValues (ItemId, Type, Value, CleanValue) values (@ItemId, 6, @Value, @CleanValue)"))
|
||||
{
|
||||
using (var deleteStatement = PrepareStatement(connection, "delete from ItemValues where ItemId=@ItemId and Type=6"))
|
||||
{
|
||||
foreach (var item in newValues)
|
||||
{
|
||||
var guidBlob = item.Item1.ToGuidBlob();
|
||||
|
||||
deleteStatement.Reset();
|
||||
deleteStatement.TryBind("@ItemId", guidBlob);
|
||||
deleteStatement.MoveNext();
|
||||
|
||||
foreach (var itemValue in item.Item2)
|
||||
{
|
||||
insertStatement.Reset();
|
||||
|
||||
insertStatement.TryBind("@ItemId", guidBlob);
|
||||
insertStatement.TryBind("@Value", itemValue);
|
||||
|
||||
insertStatement.TryBind("@CleanValue", GetCleanValue(itemValue));
|
||||
|
||||
insertStatement.MoveNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
}, TransactionMode);
|
||||
}
|
||||
|
@ -5503,7 +5557,7 @@ limit 100";
|
|||
var listIndex = 0;
|
||||
|
||||
using (var statement = PrepareStatement(connection,
|
||||
"insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)"))
|
||||
"insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values (@ItemId, @Name, @Role, @PersonType, @SortOrder, @ListOrder)"))
|
||||
{
|
||||
foreach (var person in people)
|
||||
{
|
||||
|
@ -5632,8 +5686,8 @@ limit 100";
|
|||
connection.Execute("delete from mediastreams where ItemId=@ItemId", id.ToGuidBlob());
|
||||
|
||||
using (var statement = PrepareStatement(connection, string.Format("replace into mediastreams ({0}) values ({1})",
|
||||
string.Join(",", _mediaStreamSaveColumns),
|
||||
string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray()))))
|
||||
string.Join(",", _mediaStreamSaveColumns),
|
||||
string.Join(",", _mediaStreamSaveColumns.Select(i => "@" + i).ToArray()))))
|
||||
{
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
|
|
|
@ -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,11 +424,14 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Quotes are valid in linux. They'll possibly cause issues here
|
||||
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
if (!options.ResponseHeaders.ContainsKey("Content-Disposition"))
|
||||
{
|
||||
options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\"";
|
||||
// Quotes are valid in linux. They'll possibly cause issues here
|
||||
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
return GetStaticResult(requestContext, options);
|
||||
|
@ -487,69 +490,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return result;
|
||||
}
|
||||
|
||||
var compress = ShouldCompressResponse(requestContext, contentType);
|
||||
var hasHeaders = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false);
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
|
||||
return hasHeaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shoulds the compress response.
|
||||
/// </summary>
|
||||
/// <param name="requestContext">The request context.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
private bool ShouldCompressResponse(IRequest requestContext, string contentType)
|
||||
{
|
||||
// It will take some work to support compression with byte range requests
|
||||
if (!string.IsNullOrWhiteSpace(requestContext.Headers.Get("Range")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't compress media
|
||||
if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't compress images
|
||||
if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(contentType, "application/x-javascript", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (string.Equals(contentType, "application/xml", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The us culture
|
||||
/// </summary>
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
private async Task<IHasHeaders> GetStaticResult(IRequest requestContext, StaticResultOptions options, bool compress)
|
||||
{
|
||||
var isHeadRequest = options.IsHeadRequest;
|
||||
var factoryFn = options.ContentFactory;
|
||||
var contentType = options.ContentType;
|
||||
var responseHeaders = options.ResponseHeaders;
|
||||
|
||||
//var requestedCompressionType = GetCompressionType(requestContext);
|
||||
|
@ -558,22 +500,28 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
|
||||
if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
|
||||
{
|
||||
return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
|
||||
var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
|
||||
{
|
||||
OnComplete = options.OnComplete,
|
||||
OnError = options.OnError,
|
||||
FileShare = options.FileShare
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
return hasHeaders;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(rangeHeader))
|
||||
{
|
||||
var stream = await factoryFn().ConfigureAwait(false);
|
||||
|
||||
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
|
||||
var hasHeaders = new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
|
||||
{
|
||||
OnComplete = options.OnComplete
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
return hasHeaders;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -588,14 +536,22 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
return GetHttpResult(new byte[] { }, contentType, true);
|
||||
}
|
||||
|
||||
return new StreamWriter(stream, contentType, _logger)
|
||||
var hasHeaders = new StreamWriter(stream, contentType, _logger)
|
||||
{
|
||||
OnComplete = options.OnComplete,
|
||||
OnError = options.OnError
|
||||
};
|
||||
|
||||
AddResponseHeaders(hasHeaders, options.ResponseHeaders);
|
||||
return hasHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The us culture
|
||||
/// </summary>
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Adds the caching responseHeaders.
|
||||
/// </summary>
|
||||
|
|
|
@ -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"];
|
||||
|
||||
|
@ -161,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||
// There should be at least to parts
|
||||
if (parts.Length != 2) return null;
|
||||
|
||||
var acceptedNames = new[] { "MediaBrowser", "Emby"};
|
||||
var acceptedNames = new[] { "MediaBrowser", "Emby" };
|
||||
|
||||
// It has to be a digest request
|
||||
if (!acceptedNames.Contains(parts[0] ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
video.ExtraType = ExtraType.Trailer;
|
||||
else
|
||||
{
|
||||
// item is new
|
||||
video.ExtraType = ExtraType.Trailer;
|
||||
}
|
||||
video.TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer };
|
||||
|
||||
return video;
|
||||
|
@ -2621,7 +2644,7 @@ namespace Emby.Server.Implementations.Library
|
|||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
var videoListResolver = new VideoListResolver(namingOptions, new NullLogger());
|
||||
var videoListResolver = new VideoListResolver(namingOptions);
|
||||
|
||||
var videos = videoListResolver.Resolve(fileSystemChildren);
|
||||
|
||||
|
@ -2747,7 +2770,7 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
private void SetExtraTypeFromFilename(Video item)
|
||||
{
|
||||
var resolver = new ExtraResolver(GetNamingOptions(), new NullLogger(), new RegexProvider());
|
||||
var resolver = new ExtraResolver(GetNamingOptions(), new RegexProvider());
|
||||
|
||||
var result = resolver.GetExtraInfo(item.Path);
|
||||
|
||||
|
@ -2842,13 +2865,6 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var newImage = item.GetImageInfo(image.Type, imageIndex);
|
||||
|
||||
if (newImage != null)
|
||||
{
|
||||
newImage.IsPlaceholder = image.IsPlaceholder;
|
||||
}
|
||||
|
||||
await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return item.GetImageInfo(image.Type, imageIndex);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -621,7 +605,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
ActiveRecordingInfo activeRecordingInfo;
|
||||
|
||||
if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo))
|
||||
{
|
||||
{
|
||||
activeRecordingInfo.Timer = timer;
|
||||
activeRecordingInfo.CancellationTokenSource.Cancel();
|
||||
}
|
||||
|
@ -672,11 +656,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
timer.Id = Guid.NewGuid().ToString("N");
|
||||
|
||||
ProgramInfo programInfo = null;
|
||||
LiveTvProgram programInfo = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(timer.ProgramId))
|
||||
{
|
||||
programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
|
||||
programInfo = GetProgramInfoFromCache(timer);
|
||||
}
|
||||
if (programInfo == null)
|
||||
{
|
||||
|
@ -686,7 +670,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
if (programInfo != null)
|
||||
{
|
||||
RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
|
||||
CopyProgramInfoToTimerInfo(programInfo, timer);
|
||||
}
|
||||
|
||||
timer.IsManual = true;
|
||||
|
@ -698,24 +682,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
info.Id = Guid.NewGuid().ToString("N");
|
||||
|
||||
List<ProgramInfo> epgData;
|
||||
if (info.RecordAnyChannel)
|
||||
{
|
||||
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
|
||||
var channelIds = channels.Select(i => i.Id).ToList();
|
||||
epgData = GetEpgDataForChannels(channelIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
epgData = GetEpgDataForChannel(info.ChannelId);
|
||||
}
|
||||
|
||||
// populate info.seriesID
|
||||
var program = epgData.FirstOrDefault(i => string.Equals(i.Id, info.ProgramId, StringComparison.OrdinalIgnoreCase));
|
||||
var program = GetProgramInfoFromCache(info.ProgramId);
|
||||
|
||||
if (program != null)
|
||||
{
|
||||
info.SeriesId = program.SeriesId;
|
||||
info.SeriesId = program.ExternalSeriesId;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -750,7 +722,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
_timerProvider.AddOrUpdate(timer, false);
|
||||
}
|
||||
|
||||
await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false);
|
||||
await UpdateTimersForSeriesTimer(info, true, false).ConfigureAwait(false);
|
||||
|
||||
return info.Id;
|
||||
}
|
||||
|
@ -779,19 +751,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
_seriesTimerProvider.Update(instance);
|
||||
|
||||
List<ProgramInfo> epgData;
|
||||
if (instance.RecordAnyChannel)
|
||||
{
|
||||
var channels = await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false);
|
||||
var channelIds = channels.Select(i => i.Id).ToList();
|
||||
epgData = GetEpgDataForChannels(channelIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
epgData = GetEpgDataForChannel(instance.ChannelId);
|
||||
}
|
||||
|
||||
await UpdateTimersForSeriesTimer(epgData, instance, true, true).ConfigureAwait(false);
|
||||
await UpdateTimersForSeriesTimer(instance, true, true).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -962,23 +922,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll());
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetProgramsAsyncInternal(channelId, startDateUtc, endDateUtc, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting programs", ex);
|
||||
return GetEpgDataForChannel(channelId).Where(i => i.StartDate <= endDateUtc && i.EndDate >= startDateUtc);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId)
|
||||
{
|
||||
if (info.EnableAllTuners)
|
||||
|
@ -994,7 +937,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ProgramInfo>> GetProgramsAsyncInternal(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false);
|
||||
var channel = channels.First(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
|
||||
|
@ -1037,8 +980,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
if (programs.Count > 0)
|
||||
{
|
||||
SaveEpgDataForChannel(channelId, programs);
|
||||
|
||||
return programs;
|
||||
}
|
||||
}
|
||||
|
@ -1464,11 +1405,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
throw new ArgumentNullException("timer");
|
||||
}
|
||||
|
||||
ProgramInfo programInfo = null;
|
||||
LiveTvProgram programInfo = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(timer.ProgramId))
|
||||
{
|
||||
programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
|
||||
programInfo = GetProgramInfoFromCache(timer);
|
||||
}
|
||||
if (programInfo == null)
|
||||
{
|
||||
|
@ -1478,8 +1419,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
if (programInfo != null)
|
||||
{
|
||||
RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
|
||||
activeRecordingInfo.Program = programInfo;
|
||||
CopyProgramInfoToTimerInfo(programInfo, timer);
|
||||
//activeRecordingInfo.Program = programInfo;
|
||||
}
|
||||
|
||||
string seriesPath = null;
|
||||
|
@ -1488,14 +1429,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
string liveStreamId = null;
|
||||
|
||||
OnRecordingStatusChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var recorder = await GetRecorder().ConfigureAwait(false);
|
||||
|
||||
var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("Opening recording stream from tuner provider");
|
||||
var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
|
@ -1509,23 +1449,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
recordPath = EnsureFileUnique(recordPath, timer.Id);
|
||||
|
||||
_libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(recordPath));
|
||||
activeRecordingInfo.Path = recordPath;
|
||||
|
||||
var duration = recordingEndDate - DateTime.UtcNow;
|
||||
|
||||
_logger.Info("Beginning recording. Will record for {0} minutes.",
|
||||
duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||
_logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
_logger.Info("Writing file to path: " + recordPath);
|
||||
_logger.Info("Opening recording stream from tuner provider");
|
||||
|
||||
Action onStarted = () =>
|
||||
Action onStarted = async () =>
|
||||
{
|
||||
timer.Status = RecordingStatus.InProgress;
|
||||
_timerProvider.AddOrUpdate(timer, false);
|
||||
|
||||
SaveRecordingMetadata(timer, recordPath, seriesPath);
|
||||
await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false);
|
||||
TriggerRefresh(recordPath);
|
||||
EnforceKeepUpTo(timer, seriesPath);
|
||||
};
|
||||
|
@ -1559,7 +1496,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
|
||||
TriggerRefresh(recordPath);
|
||||
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
|
||||
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
|
||||
|
||||
ActiveRecordingInfo removed;
|
||||
_activeRecordings.TryRemove(timer.Id, out removed);
|
||||
|
@ -1585,17 +1522,29 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
_timerProvider.Delete(timer);
|
||||
}
|
||||
|
||||
OnRecordingStatusChanged();
|
||||
}
|
||||
|
||||
private void TriggerRefresh(string path)
|
||||
{
|
||||
_logger.Debug("Triggering refresh on {0}", path);
|
||||
|
||||
var item = GetAffectedBaseItem(_fileSystem.GetDirectoryName(path));
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
item.ChangedExternally();
|
||||
_logger.Debug("Refreshing recording parent {0}", item.Path);
|
||||
|
||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
|
||||
{
|
||||
ValidateChildren = true,
|
||||
RefreshPaths = new List<string>
|
||||
{
|
||||
path,
|
||||
_fileSystem.GetDirectoryName(path),
|
||||
_fileSystem.GetDirectoryName(_fileSystem.GetDirectoryName(path))
|
||||
}
|
||||
|
||||
}, RefreshPriority.High);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1603,6 +1552,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
BaseItem item = null;
|
||||
|
||||
var parentPath = _fileSystem.GetDirectoryName(path);
|
||||
|
||||
while (item == null && !string.IsNullOrEmpty(path))
|
||||
{
|
||||
item = _libraryManager.FindByPath(path, null);
|
||||
|
@ -1612,14 +1563,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
if (item != null)
|
||||
{
|
||||
// If the item has been deleted find the first valid parent that still exists
|
||||
while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
|
||||
if (item.GetType() == typeof(Folder) && string.Equals(item.Path, parentPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item = item.GetParent();
|
||||
|
||||
if (item == null)
|
||||
var parentItem = item.GetParent();
|
||||
if (parentItem != null && !(parentItem is AggregateFolder))
|
||||
{
|
||||
break;
|
||||
item = parentItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1627,14 +1576,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return item;
|
||||
}
|
||||
|
||||
private void OnRecordingStatusChanged()
|
||||
{
|
||||
EventHelper.FireEventIfNotNull(RecordingStatusChanged, this, new RecordingStatusChangedEventArgs
|
||||
{
|
||||
|
||||
}, _logger);
|
||||
}
|
||||
|
||||
private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(timer.SeriesTimerId))
|
||||
|
@ -1687,8 +1628,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
SortBy = new[] { ItemSortBy.DateCreated },
|
||||
SortOrder = SortOrder.Descending,
|
||||
OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
|
||||
IsVirtualItem = false,
|
||||
IsFolder = false,
|
||||
Recursive = true,
|
||||
|
@ -1810,7 +1750,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
var config = GetConfiguration();
|
||||
|
||||
var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false);
|
||||
var regInfo = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false);
|
||||
|
||||
if (regInfo.IsValid)
|
||||
{
|
||||
|
@ -2020,7 +1960,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
}
|
||||
|
||||
private async void SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath)
|
||||
private async Task SaveRecordingMetadata(TimerInfo timer, string recordingPath, string seriesPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -2337,18 +2277,49 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
}
|
||||
|
||||
private ProgramInfo GetProgramInfoFromCache(string channelId, string programId)
|
||||
private LiveTvProgram GetProgramInfoFromCache(string programId)
|
||||
{
|
||||
var epgData = GetEpgDataForChannel(channelId);
|
||||
return epgData.FirstOrDefault(p => string.Equals(p.Id, programId, StringComparison.OrdinalIgnoreCase));
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
ItemIds = new[] { _liveTvManager.GetInternalProgramId(Name, programId).ToString("N") },
|
||||
Limit = 1,
|
||||
DtoOptions = new DtoOptions()
|
||||
};
|
||||
|
||||
return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault();
|
||||
}
|
||||
|
||||
private ProgramInfo GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
|
||||
private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer)
|
||||
{
|
||||
var epgData = GetEpgDataForChannel(channelId);
|
||||
var startDateTicks = startDateUtc.Ticks;
|
||||
// Find the first program that starts within 3 minutes
|
||||
return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks);
|
||||
return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId);
|
||||
}
|
||||
|
||||
private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId)
|
||||
{
|
||||
return GetProgramInfoFromCache(programId);
|
||||
}
|
||||
|
||||
private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
|
||||
{
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
Limit = 1,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
EnableImages = false
|
||||
},
|
||||
MinStartDate = startDateUtc.AddMinutes(-3),
|
||||
MaxStartDate = startDateUtc.AddMinutes(3),
|
||||
OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, channelId).ToString("N") };
|
||||
}
|
||||
|
||||
return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault();
|
||||
}
|
||||
|
||||
private LiveTvOptions GetConfiguration()
|
||||
|
@ -2422,9 +2393,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
}
|
||||
|
||||
private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers)
|
||||
private async Task UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers)
|
||||
{
|
||||
var allTimers = GetTimersForSeries(seriesTimer, epgData)
|
||||
var allTimers = GetTimersForSeries(seriesTimer)
|
||||
.ToList();
|
||||
|
||||
var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
|
||||
|
@ -2521,23 +2492,160 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
}
|
||||
|
||||
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
|
||||
private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer)
|
||||
{
|
||||
if (seriesTimer == null)
|
||||
{
|
||||
throw new ArgumentNullException("seriesTimer");
|
||||
}
|
||||
if (allPrograms == null)
|
||||
|
||||
if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
|
||||
{
|
||||
throw new ArgumentNullException("allPrograms");
|
||||
return new List<TimerInfo>();
|
||||
}
|
||||
|
||||
// Exclude programs that have already ended
|
||||
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow);
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
ExternalSeriesId = seriesTimer.SeriesId,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
EnableImages = false
|
||||
},
|
||||
MinEndDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
|
||||
if (!seriesTimer.RecordAnyChannel)
|
||||
{
|
||||
query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, seriesTimer.ChannelId).ToString("N") };
|
||||
}
|
||||
|
||||
return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
|
||||
var tempChannelCache = new Dictionary<string, LiveTvChannel>();
|
||||
|
||||
return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().Select(i => CreateTimer(i, seriesTimer, tempChannelCache));
|
||||
}
|
||||
|
||||
private TimerInfo CreateTimer(LiveTvProgram parent, SeriesTimerInfo seriesTimer, Dictionary<string, LiveTvChannel> tempChannelCache)
|
||||
{
|
||||
string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(channelId) && !string.IsNullOrWhiteSpace(parent.ChannelId))
|
||||
{
|
||||
LiveTvChannel channel;
|
||||
|
||||
if (!tempChannelCache.TryGetValue(parent.ChannelId, out channel))
|
||||
{
|
||||
channel = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
|
||||
ItemIds = new[] { parent.ChannelId },
|
||||
DtoOptions = new DtoOptions()
|
||||
|
||||
}).Cast<LiveTvChannel>().FirstOrDefault();
|
||||
|
||||
if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId))
|
||||
{
|
||||
tempChannelCache[parent.ChannelId] = channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel != null || tempChannelCache.TryGetValue(parent.ChannelId, out channel))
|
||||
{
|
||||
channelId = channel.ExternalId;
|
||||
}
|
||||
}
|
||||
|
||||
var timer = new TimerInfo
|
||||
{
|
||||
ChannelId = channelId,
|
||||
Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"),
|
||||
StartDate = parent.StartDate,
|
||||
EndDate = parent.EndDate.Value,
|
||||
ProgramId = parent.ExternalId,
|
||||
PrePaddingSeconds = seriesTimer.PrePaddingSeconds,
|
||||
PostPaddingSeconds = seriesTimer.PostPaddingSeconds,
|
||||
IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired,
|
||||
IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired,
|
||||
KeepUntil = seriesTimer.KeepUntil,
|
||||
Priority = seriesTimer.Priority,
|
||||
Name = parent.Name,
|
||||
Overview = parent.Overview,
|
||||
SeriesId = parent.ExternalSeriesId,
|
||||
SeriesTimerId = seriesTimer.Id,
|
||||
ShowId = parent.ShowId
|
||||
};
|
||||
|
||||
CopyProgramInfoToTimerInfo(parent, timer, tempChannelCache);
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo)
|
||||
{
|
||||
var tempChannelCache = new Dictionary<string, LiveTvChannel>();
|
||||
CopyProgramInfoToTimerInfo(programInfo, timerInfo, tempChannelCache);
|
||||
}
|
||||
|
||||
private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo, Dictionary<string, LiveTvChannel> tempChannelCache)
|
||||
{
|
||||
string channelId = null;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(programInfo.ChannelId))
|
||||
{
|
||||
LiveTvChannel channel;
|
||||
|
||||
if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out channel))
|
||||
{
|
||||
channel = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvChannel).Name },
|
||||
ItemIds = new[] { programInfo.ChannelId },
|
||||
DtoOptions = new DtoOptions()
|
||||
|
||||
}).Cast<LiveTvChannel>().FirstOrDefault();
|
||||
|
||||
if (channel != null && !string.IsNullOrWhiteSpace(channel.ExternalId))
|
||||
{
|
||||
tempChannelCache[programInfo.ChannelId] = channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel != null || tempChannelCache.TryGetValue(programInfo.ChannelId, out channel))
|
||||
{
|
||||
channelId = channel.ExternalId;
|
||||
}
|
||||
}
|
||||
|
||||
timerInfo.Name = programInfo.Name;
|
||||
timerInfo.StartDate = programInfo.StartDate;
|
||||
timerInfo.EndDate = programInfo.EndDate.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
timerInfo.ChannelId = channelId;
|
||||
}
|
||||
|
||||
timerInfo.SeasonNumber = programInfo.ParentIndexNumber;
|
||||
timerInfo.EpisodeNumber = programInfo.IndexNumber;
|
||||
timerInfo.IsMovie = programInfo.IsMovie;
|
||||
timerInfo.IsKids = programInfo.IsKids;
|
||||
timerInfo.IsNews = programInfo.IsNews;
|
||||
timerInfo.IsSports = programInfo.IsSports;
|
||||
timerInfo.ProductionYear = programInfo.ProductionYear;
|
||||
timerInfo.EpisodeTitle = programInfo.EpisodeTitle;
|
||||
timerInfo.OriginalAirDate = programInfo.PremiereDate;
|
||||
timerInfo.IsProgramSeries = programInfo.IsSeries;
|
||||
|
||||
timerInfo.IsSeries = programInfo.IsSeries;
|
||||
timerInfo.IsLive = programInfo.IsLive;
|
||||
timerInfo.IsPremiere = programInfo.IsPremiere;
|
||||
|
||||
timerInfo.HomePageUrl = programInfo.HomePageUrl;
|
||||
timerInfo.CommunityRating = programInfo.CommunityRating;
|
||||
timerInfo.Overview = programInfo.Overview;
|
||||
timerInfo.OfficialRating = programInfo.OfficialRating;
|
||||
timerInfo.IsRepeat = programInfo.IsRepeat;
|
||||
timerInfo.SeriesId = programInfo.ExternalSeriesId;
|
||||
}
|
||||
|
||||
private bool IsProgramAlreadyInLibrary(TimerInfo program)
|
||||
|
@ -2578,51 +2686,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
|
||||
{
|
||||
_logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series");
|
||||
return new List<ProgramInfo>();
|
||||
}
|
||||
|
||||
return allPrograms.Where(i => string.Equals(i.SeriesId, seriesTimer.SeriesId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private string GetChannelEpgCachePath(string channelId)
|
||||
{
|
||||
return Path.Combine(_config.CommonApplicationPaths.CachePath, "embytvepg", channelId + ".json");
|
||||
}
|
||||
|
||||
private readonly object _epgLock = new object();
|
||||
private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData)
|
||||
{
|
||||
var path = GetChannelEpgCachePath(channelId);
|
||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
|
||||
lock (_epgLock)
|
||||
{
|
||||
_jsonSerializer.SerializeToFile(epgData, path);
|
||||
}
|
||||
}
|
||||
private List<ProgramInfo> GetEpgDataForChannel(string channelId)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_epgLock)
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromFile<List<ProgramInfo>>(GetChannelEpgCachePath(channelId));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<ProgramInfo>();
|
||||
}
|
||||
}
|
||||
private List<ProgramInfo> GetEpgDataForChannels(List<string> channelIds)
|
||||
{
|
||||
return channelIds.SelectMany(GetEpgDataForChannel).ToList();
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -2631,6 +2694,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
pair.Value.CancellationTokenSource.Cancel();
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public List<VirtualFolderInfo> GetRecordingFolders()
|
||||
|
|
|
@ -284,8 +284,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
protected string GetOutputSizeParam()
|
||||
{
|
||||
var filters = new List<string>();
|
||||
|
||||
filters.Add("yadif=0:-1:0");
|
||||
|
||||
if (string.Equals(GetEncodingOptions().DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("yadif=1:-1:0");
|
||||
}
|
||||
else
|
||||
{
|
||||
filters.Add("yadif=0:-1:0");
|
||||
}
|
||||
|
||||
var output = string.Empty;
|
||||
|
||||
|
|
|
@ -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,19 +106,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
{
|
||||
_logger.Info("Beginning multicastStream.CopyUntilCancelled");
|
||||
|
||||
if (_enableFileBuffer)
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
||||
{
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
||||
{
|
||||
StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Resolve(openTaskCompletionSource);
|
||||
|
||||
await _multicastStream.CopyUntilCancelled(response.Content, null, cancellationToken).ConfigureAwait(false);
|
||||
StreamHelper.CopyTo(response.Content, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +134,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
}
|
||||
|
||||
_liveStreamTaskCompletionSource.TrySetResult(true);
|
||||
//await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
|
||||
await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -158,56 +148,31 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
|
||||
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_enableFileBuffer)
|
||||
{
|
||||
return CopyFileTo(_tempFilePath, stream, cancellationToken);
|
||||
}
|
||||
return _multicastStream.CopyToAsync(stream, cancellationToken);
|
||||
//return CopyFileTo(_tempFilePath, stream, cancellationToken);
|
||||
return CopyFileTo(_tempFilePath, stream, cancellationToken);
|
||||
}
|
||||
|
||||
protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken)
|
||||
{
|
||||
long startPosition = -20000;
|
||||
if (startPosition < 0)
|
||||
{
|
||||
var length = FileSystem.GetFileInfo(path).Length;
|
||||
startPosition = Math.Max(length - startPosition, 0);
|
||||
}
|
||||
|
||||
_logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
|
||||
using (var inputStream = GetInputStream(path, startPosition, allowAsync))
|
||||
using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
|
||||
{
|
||||
if (startPosition > 0)
|
||||
{
|
||||
inputStream.Position = startPosition;
|
||||
inputStream.Seek(-20000, SeekOrigin.End);
|
||||
}
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
long bytesRead;
|
||||
|
||||
if (allowAsync)
|
||||
{
|
||||
bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken);
|
||||
bytesRead = 1;
|
||||
}
|
||||
StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken);
|
||||
|
||||
//var position = fs.Position;
|
||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -126,17 +123,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (_enableFileBuffer)
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
||||
{
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
||||
{
|
||||
CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
|
||||
CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,56 +168,33 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
});
|
||||
}
|
||||
|
||||
public async Task CopyToAsync(Stream outputStream, CancellationToken cancellationToken)
|
||||
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_enableFileBuffer)
|
||||
{
|
||||
await _multicastStream.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var path = _tempFilePath;
|
||||
return CopyFileTo(_tempFilePath, stream, cancellationToken);
|
||||
}
|
||||
|
||||
protected async Task CopyFileTo(string path, Stream outputStream, CancellationToken cancellationToken)
|
||||
{
|
||||
long startPosition = -20000;
|
||||
if (startPosition < 0)
|
||||
{
|
||||
var length = FileSystem.GetFileInfo(path).Length;
|
||||
startPosition = Math.Max(length - startPosition, 0);
|
||||
}
|
||||
|
||||
_logger.Info("Live stream starting position is {0} bytes", startPosition.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var allowAsync = Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||
|
||||
using (var inputStream = GetInputStream(path, startPosition, allowAsync))
|
||||
using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
|
||||
{
|
||||
if (startPosition > 0)
|
||||
{
|
||||
inputStream.Position = startPosition;
|
||||
inputStream.Seek(-20000, SeekOrigin.End);
|
||||
}
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
long bytesRead;
|
||||
|
||||
if (allowAsync)
|
||||
{
|
||||
bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 2, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken);
|
||||
bytesRead = 1;
|
||||
}
|
||||
StreamHelper.CopyTo(inputStream, outputStream, 81920, cancellationToken);
|
||||
|
||||
//var position = fs.Position;
|
||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -285,22 +252,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||
//return taskCompletion.Task;
|
||||
}
|
||||
|
||||
private void StreamCopyCallback(IAsyncResult result)
|
||||
{
|
||||
var copier = (AsyncStreamCopier)result.AsyncState;
|
||||
var taskCompletion = copier.TaskCompletionSource;
|
||||
|
||||
try
|
||||
{
|
||||
copier.EndCopy(result);
|
||||
taskCompletion.TrySetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public class UdpClientStream : Stream
|
||||
{
|
||||
private static int RtpHeaderBytes = 12;
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -58,8 +58,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
/// </summary>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
return new[] {
|
||||
|
||||
new TaskTriggerInfo
|
||||
{
|
||||
Type = TaskTriggerInfo.TriggerDaily,
|
||||
|
@ -88,6 +88,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||
IsFolder = false,
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
},
|
||||
SourceTypes = new SourceType[] { SourceType.Library },
|
||||
HasChapterImages = false,
|
||||
IsVirtualItem = false
|
||||
|
||||
})
|
||||
.OfType<Video>()
|
||||
|
|
|
@ -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);
|
||||
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -133,7 +136,10 @@ namespace Emby.Server.Implementations.Services
|
|||
{
|
||||
bytes = Encoding.UTF8.GetBytes(responseText);
|
||||
response.SetContentLength(bytes.Length);
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
if (bytes.Length > 0)
|
||||
{
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -150,8 +156,15 @@ namespace Emby.Server.Implementations.Services
|
|||
serializer(result, ms);
|
||||
|
||||
ms.Position = 0;
|
||||
response.SetContentLength(ms.Length);
|
||||
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
|
||||
|
||||
var contentLength = ms.Length;
|
||||
|
||||
response.SetContentLength(contentLength);
|
||||
|
||||
if (contentLength > 0)
|
||||
{
|
||||
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
//serializer(result, outputStream);
|
||||
|
|
|
@ -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,22 +222,26 @@ namespace Emby.Server.Implementations.Services
|
|||
}
|
||||
}
|
||||
|
||||
if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null)
|
||||
if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")))
|
||||
{
|
||||
foreach (var name in request.FormData.Keys)
|
||||
var formData = request.FormData;
|
||||
if (formData != null)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
foreach (var name in formData.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
|
||||
var values = request.FormData.GetValues(name);
|
||||
if (values.Count == 1)
|
||||
{
|
||||
map[name] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
var values = formData.GetValues(name);
|
||||
if (values.Count == 1)
|
||||
{
|
||||
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||
map[name] = values[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < values.Count; i++)
|
||||
{
|
||||
map[name + (i == 0 ? "" : "#" + i)] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -270,12 +268,16 @@ namespace Emby.Server.Implementations.Services
|
|||
map[name] = request.QueryString[name];
|
||||
}
|
||||
|
||||
if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")) && request.FormData != null)
|
||||
if ((IsMethod(request.Verb, "POST") || IsMethod(request.Verb, "PUT")))
|
||||
{
|
||||
foreach (var name in request.FormData.Keys)
|
||||
var formData = request.FormData;
|
||||
if (formData != null)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
map[name] = request.FormData[name];
|
||||
foreach (var name in formData.Keys)
|
||||
{
|
||||
if (name == null) continue; //thank you ASP.NET
|
||||
map[name] = formData[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
if (prop.GetMethod == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (excludeType == prop.PropertyType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ignored = false;
|
||||
foreach (var attr in prop.GetCustomAttributes(true))
|
||||
{
|
||||
if (IgnoreAttributesNamed.Contains(attr.GetType().Name))
|
||||
{
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignored)
|
||||
{
|
||||
list.Add(prop);
|
||||
}
|
||||
}
|
||||
|
||||
// else return those properties that are not decorated with IgnoreDataMember
|
||||
return readableProperties
|
||||
.Where(prop => prop.GetCustomAttributes(true)
|
||||
.All(attr =>
|
||||
{
|
||||
var name = attr.GetType().Name;
|
||||
return !IgnoreAttributesNamed.Contains(name);
|
||||
}))
|
||||
.Where(prop => !_excludeTypes.Contains(prop.PropertyType))
|
||||
.ToArray();
|
||||
return list;
|
||||
}
|
||||
|
||||
private static PropertyInfo[] GetPublicProperties(Type type)
|
||||
private static List<PropertyInfo> GetPublicProperties(Type type)
|
||||
{
|
||||
if (type.GetTypeInfo().IsInterface)
|
||||
{
|
||||
|
@ -269,12 +265,19 @@ namespace Emby.Server.Implementations.Services
|
|||
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||
}
|
||||
|
||||
return propertyInfos.ToArray(propertyInfos.Count);
|
||||
return propertyInfos;
|
||||
}
|
||||
|
||||
return GetTypesPublicProperties(type)
|
||||
.Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
|
||||
.ToArray();
|
||||
var list = new List<PropertyInfo>();
|
||||
|
||||
foreach (var t in GetTypesPublicProperties(type))
|
||||
{
|
||||
if (t.GetIndexParameters().Length == 0)
|
||||
{
|
||||
list.Add(t);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static PropertyInfo[] GetTypesPublicProperties(Type subType)
|
||||
|
@ -289,16 +292,11 @@ namespace Emby.Server.Implementations.Services
|
|||
return pis.ToArray(pis.Count);
|
||||
}
|
||||
|
||||
|
||||
public bool IsValid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Provide for quick lookups based on hashes that can be determined from a request url
|
||||
/// </summary>
|
||||
public string FirstMatchHashKey { get; private set; }
|
||||
|
||||
public string UniqueMatchHashKey { get; private set; }
|
||||
|
||||
private readonly StringMapTypeDeserializer typeDeserializer;
|
||||
|
||||
private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
|
||||
|
@ -321,8 +319,14 @@ namespace Emby.Server.Implementations.Services
|
|||
score += Math.Max((10 - VariableArgsCount), 1) * 100;
|
||||
|
||||
//Exact verb match is better than ANY
|
||||
var exactVerb = String.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase);
|
||||
score += exactVerb ? 10 : 1;
|
||||
if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
score += 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
score += 1;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
@ -346,7 +350,7 @@ namespace Emby.Server.Implementations.Services
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!this.allowsAllVerbs && !StringContains(this.allowedVerbs, httpMethod))
|
||||
if (!Verbs.Contains(httpMethod, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
//logger.Info("allowsAllVerbs mismatch for {0} for {1} allowedverbs {2}", httpMethod, string.Join("/", withPathInfoParts), this.allowedVerbs);
|
||||
return false;
|
||||
|
@ -457,8 +461,7 @@ namespace Emby.Server.Implementations.Services
|
|||
|
||||
public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
|
||||
{
|
||||
var requestComponents = pathInfo.Split(PathSeperatorChar)
|
||||
.Where(x => !String.IsNullOrEmpty(x)).ToArray();
|
||||
var requestComponents = pathInfo.Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
ExplodeComponents(ref requestComponents);
|
||||
|
||||
|
@ -555,10 +558,5 @@ namespace Emby.Server.Implementations.Services
|
|||
|
||||
return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return UniqueMatchHashKey.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 });
|
||||
|
|
|
@ -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…
Reference in New Issue