commit
03b93e02b9
|
@ -64,7 +64,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
<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>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -195,12 +195,26 @@ namespace Emby.Drawing.Skia
|
||||||
{
|
{
|
||||||
var codec = SKCodec.Create(stream);
|
var codec = SKCodec.Create(stream);
|
||||||
|
|
||||||
|
if (codec == null)
|
||||||
|
{
|
||||||
|
origin = SKCodecOrigin.TopLeft;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// create the bitmap
|
// create the bitmap
|
||||||
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
|
||||||
// decode
|
|
||||||
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
|
||||||
|
|
||||||
origin = codec.Origin;
|
if (bitmap != null)
|
||||||
|
{
|
||||||
|
// decode
|
||||||
|
codec.GetPixels(bitmap.Info, bitmap.GetPixels());
|
||||||
|
|
||||||
|
origin = codec.Origin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
origin = SKCodecOrigin.TopLeft;
|
||||||
|
}
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
@ -239,7 +253,7 @@ namespace Emby.Drawing.Skia
|
||||||
return Decode(path, forceAnalyzeBitmap, out origin);
|
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;
|
SKCodecOrigin origin;
|
||||||
|
|
||||||
|
@ -247,11 +261,14 @@ namespace Emby.Drawing.Skia
|
||||||
{
|
{
|
||||||
var bitmap = GetBitmap(path, cropWhitespace, true, out origin);
|
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 RotateAndFlip(bitmap, origin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,11 +374,11 @@ namespace Emby.Drawing.Skia
|
||||||
var blur = options.Blur ?? 0;
|
var blur = options.Blur ?? 0;
|
||||||
var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(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)
|
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);
|
//_logger.Info("Color type {0}", bitmap.Info.ColorType);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="SkiaSharp" version="1.58.0" targetFramework="portable45-net45+win8" />
|
<package id="SkiaSharp" version="1.58.1" targetFramework="portable45-net45+win8" />
|
||||||
</packages>
|
</packages>
|
|
@ -296,11 +296,6 @@ namespace Emby.Drawing
|
||||||
var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
|
var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
|
||||||
_fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath));
|
_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))
|
if (options.CropWhiteSpace && !SupportsTransparency(originalImagePath))
|
||||||
{
|
{
|
||||||
options.CropWhiteSpace = false;
|
options.CropWhiteSpace = false;
|
||||||
|
@ -321,6 +316,15 @@ namespace Emby.Drawing
|
||||||
|
|
||||||
return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// If it fails for whatever reason, return the original image
|
// If it fails for whatever reason, return the original image
|
||||||
|
|
|
@ -710,13 +710,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
Summary = route.Summary
|
Summary = route.Summary
|
||||||
});
|
});
|
||||||
|
|
||||||
routes.Add(new RouteAttribute(NormalizeRoutePath(route.Path), route.Verbs)
|
|
||||||
{
|
|
||||||
Notes = route.Notes,
|
|
||||||
Priority = route.Priority,
|
|
||||||
Summary = route.Summary
|
|
||||||
});
|
|
||||||
|
|
||||||
routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs)
|
routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs)
|
||||||
{
|
{
|
||||||
Notes = route.Notes,
|
Notes = route.Notes,
|
||||||
|
@ -773,16 +766,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
return "emby/emby/" + path;
|
return "emby/emby/" + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string NormalizeRoutePath(string path)
|
|
||||||
{
|
|
||||||
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "/mediabrowser" + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "mediabrowser/" + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private readonly object _disposeLock = new object();
|
private readonly object _disposeLock = new object();
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
|
|
|
@ -424,11 +424,14 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Quotes are valid in linux. They'll possibly cause issues here
|
if (!options.ResponseHeaders.ContainsKey("Content-Disposition"))
|
||||||
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
|
|
||||||
if (!string.IsNullOrWhiteSpace(filename))
|
|
||||||
{
|
{
|
||||||
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);
|
return GetStaticResult(requestContext, options);
|
||||||
|
|
|
@ -75,11 +75,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
var attrs = appHost.GetRouteAttributes(requestType);
|
var attrs = appHost.GetRouteAttributes(requestType);
|
||||||
foreach (RouteAttribute attr in attrs)
|
foreach (RouteAttribute attr in attrs)
|
||||||
{
|
{
|
||||||
var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary, attr.Notes);
|
var restPath = new RestPath(appHost.CreateInstance, appHost.GetParseFn, requestType, attr.Path, attr.Verbs, attr.Summary);
|
||||||
|
|
||||||
if (!restPath.IsValid)
|
|
||||||
throw new NotSupportedException(string.Format(
|
|
||||||
"RestPath '{0}' on Type '{1}' is not Valid", attr.Path, requestType.GetMethodName()));
|
|
||||||
|
|
||||||
RegisterRestPath(restPath);
|
RegisterRestPath(restPath);
|
||||||
}
|
}
|
||||||
|
@ -92,8 +88,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
if (!restPath.Path.StartsWith("/"))
|
if (!restPath.Path.StartsWith("/"))
|
||||||
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
|
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
|
||||||
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
|
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
|
||||||
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. " +
|
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName()));
|
||||||
"See https://github.com/ServiceStack/ServiceStack/wiki/Routing for info on valid routes.", restPath.Path, restPath.RequestType.GetMethodName()));
|
|
||||||
|
|
||||||
List<RestPath> pathsAtFirstMatch;
|
List<RestPath> pathsAtFirstMatch;
|
||||||
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
|
if (!RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out pathsAtFirstMatch))
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
{
|
{
|
||||||
public static class ServiceExecExtensions
|
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
|
"OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", // RFC 2616
|
||||||
"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518
|
"PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", // RFC 2518
|
||||||
"VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT",
|
"VERSION-CONTROL", "REPORT", "CHECKOUT", "CHECKIN", "UNCHECKOUT",
|
||||||
|
@ -22,7 +22,9 @@ namespace Emby.Server.Implementations.Services
|
||||||
"SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/
|
"SEARCH", // https://datatracker.ietf.org/doc/draft-reschke-webdav-search/
|
||||||
"BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY",
|
"BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "NOTIFY",
|
||||||
"POLL", "SUBSCRIBE", "UNSUBSCRIBE"
|
"POLL", "SUBSCRIBE", "UNSUBSCRIBE"
|
||||||
});
|
};
|
||||||
|
|
||||||
|
public static HashSet<string> AllVerbsSet = new HashSet<string>(AllVerbs);
|
||||||
|
|
||||||
public static List<MethodInfo> GetActions(this Type serviceType)
|
public static List<MethodInfo> GetActions(this Type serviceType)
|
||||||
{
|
{
|
||||||
|
@ -56,7 +58,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
|
|
||||||
internal static class ServiceExecGeneral
|
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)
|
public static void CreateServiceRunnersFor(Type requestType, List<ServiceMethod> actions)
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,8 +21,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
readonly bool[] componentsWithSeparators;
|
readonly bool[] componentsWithSeparators;
|
||||||
|
|
||||||
private readonly string restPath;
|
private readonly string restPath;
|
||||||
private readonly string allowedVerbs;
|
|
||||||
private readonly bool allowsAllVerbs;
|
|
||||||
public bool IsWildCardPath { get; private set; }
|
public bool IsWildCardPath { get; private set; }
|
||||||
|
|
||||||
private readonly string[] literalsToMatch;
|
private readonly string[] literalsToMatch;
|
||||||
|
@ -46,15 +44,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalComponentsCount { get; set; }
|
public int TotalComponentsCount { get; set; }
|
||||||
|
|
||||||
public string[] Verbs
|
public string[] Verbs { get; private set; }
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return allowsAllVerbs
|
|
||||||
? new[] { "ANY" }
|
|
||||||
: AllowedVerbs.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Type RequestType { get; private set; }
|
public Type RequestType { get; private set; }
|
||||||
|
|
||||||
|
@ -62,19 +52,11 @@ namespace Emby.Server.Implementations.Services
|
||||||
|
|
||||||
public string Summary { get; private set; }
|
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 int Priority { get; set; } //passed back to RouteAttribute
|
public int Priority { get; set; } //passed back to RouteAttribute
|
||||||
|
|
||||||
public static string[] GetPathPartsForMatching(string pathInfo)
|
public static string[] GetPathPartsForMatching(string pathInfo)
|
||||||
{
|
{
|
||||||
var parts = pathInfo.ToLower().Split(PathSeperatorChar)
|
return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
.Where(x => !String.IsNullOrEmpty(x)).ToArray();
|
|
||||||
return parts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
|
public static List<string> GetFirstMatchHashKeys(string[] pathPartsForMatching)
|
||||||
|
@ -109,18 +91,13 @@ namespace Emby.Server.Implementations.Services
|
||||||
return list;
|
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, string summary = null)
|
||||||
{
|
{
|
||||||
this.RequestType = requestType;
|
this.RequestType = requestType;
|
||||||
this.Summary = summary;
|
this.Summary = summary;
|
||||||
this.Notes = notes;
|
|
||||||
this.restPath = path;
|
this.restPath = path;
|
||||||
|
|
||||||
this.allowsAllVerbs = verbs == null || String.Equals(verbs, WildCard, StringComparison.OrdinalIgnoreCase);
|
this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpper().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (!this.allowsAllVerbs)
|
|
||||||
{
|
|
||||||
this.allowedVerbs = verbs.ToUpper();
|
|
||||||
}
|
|
||||||
|
|
||||||
var componentsList = new List<string>();
|
var componentsList = new List<string>();
|
||||||
|
|
||||||
|
@ -153,7 +130,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
this.PathComponentsCount = this.componentsWithSeparators.Length;
|
this.PathComponentsCount = this.componentsWithSeparators.Length;
|
||||||
string firstLiteralMatch = null;
|
string firstLiteralMatch = null;
|
||||||
|
|
||||||
var sbHashKey = new StringBuilder();
|
|
||||||
for (var i = 0; i < components.Length; i++)
|
for (var i = 0; i < components.Length; i++)
|
||||||
{
|
{
|
||||||
var component = components[i];
|
var component = components[i];
|
||||||
|
@ -172,7 +148,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.literalsToMatch[i] = component.ToLower();
|
this.literalsToMatch[i] = component.ToLower();
|
||||||
sbHashKey.Append(i + PathSeperatorChar.ToString() + this.literalsToMatch);
|
|
||||||
|
|
||||||
if (firstLiteralMatch == null)
|
if (firstLiteralMatch == null)
|
||||||
{
|
{
|
||||||
|
@ -198,9 +173,6 @@ namespace Emby.Server.Implementations.Services
|
||||||
? this.PathComponentsCount + PathSeperator + firstLiteralMatch
|
? this.PathComponentsCount + PathSeperator + firstLiteralMatch
|
||||||
: WildCardChar + PathSeperator + firstLiteralMatch;
|
: WildCardChar + PathSeperator + firstLiteralMatch;
|
||||||
|
|
||||||
this.IsValid = sbHashKey.Length > 0;
|
|
||||||
this.UniqueMatchHashKey = sbHashKey.ToString();
|
|
||||||
|
|
||||||
this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
|
this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
|
||||||
RegisterCaseInsenstivePropertyNameMappings();
|
RegisterCaseInsenstivePropertyNameMappings();
|
||||||
}
|
}
|
||||||
|
@ -220,26 +192,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 list = new List<PropertyInfo>();
|
||||||
var readableProperties = properties.Where(x => x.GetMethod != null);
|
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
|
// else return those properties that are not decorated with IgnoreDataMember
|
||||||
return readableProperties
|
return list;
|
||||||
.Where(prop => prop.GetCustomAttributes(true)
|
|
||||||
.All(attr =>
|
|
||||||
{
|
|
||||||
var name = attr.GetType().Name;
|
|
||||||
return !IgnoreAttributesNamed.Contains(name);
|
|
||||||
}))
|
|
||||||
.Where(prop => !_excludeTypes.Contains(prop.PropertyType))
|
|
||||||
.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PropertyInfo[] GetPublicProperties(Type type)
|
private static List<PropertyInfo> GetPublicProperties(Type type)
|
||||||
{
|
{
|
||||||
if (type.GetTypeInfo().IsInterface)
|
if (type.GetTypeInfo().IsInterface)
|
||||||
{
|
{
|
||||||
|
@ -269,12 +261,19 @@ namespace Emby.Server.Implementations.Services
|
||||||
propertyInfos.InsertRange(0, newPropertyInfos);
|
propertyInfos.InsertRange(0, newPropertyInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
return propertyInfos.ToArray(propertyInfos.Count);
|
return propertyInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetTypesPublicProperties(type)
|
var list = new List<PropertyInfo>();
|
||||||
.Where(t => t.GetIndexParameters().Length == 0) // ignore indexed properties
|
|
||||||
.ToArray();
|
foreach (var t in GetTypesPublicProperties(type))
|
||||||
|
{
|
||||||
|
if (t.GetIndexParameters().Length == 0)
|
||||||
|
{
|
||||||
|
list.Add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PropertyInfo[] GetTypesPublicProperties(Type subType)
|
private static PropertyInfo[] GetTypesPublicProperties(Type subType)
|
||||||
|
@ -289,16 +288,11 @@ namespace Emby.Server.Implementations.Services
|
||||||
return pis.ToArray(pis.Count);
|
return pis.ToArray(pis.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool IsValid { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provide for quick lookups based on hashes that can be determined from a request url
|
/// Provide for quick lookups based on hashes that can be determined from a request url
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FirstMatchHashKey { get; private set; }
|
public string FirstMatchHashKey { get; private set; }
|
||||||
|
|
||||||
public string UniqueMatchHashKey { get; private set; }
|
|
||||||
|
|
||||||
private readonly StringMapTypeDeserializer typeDeserializer;
|
private readonly StringMapTypeDeserializer typeDeserializer;
|
||||||
|
|
||||||
private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
|
||||||
|
@ -321,8 +315,14 @@ namespace Emby.Server.Implementations.Services
|
||||||
score += Math.Max((10 - VariableArgsCount), 1) * 100;
|
score += Math.Max((10 - VariableArgsCount), 1) * 100;
|
||||||
|
|
||||||
//Exact verb match is better than ANY
|
//Exact verb match is better than ANY
|
||||||
var exactVerb = String.Equals(httpMethod, AllowedVerbs, StringComparison.OrdinalIgnoreCase);
|
if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase))
|
||||||
score += exactVerb ? 10 : 1;
|
{
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
return false;
|
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);
|
//logger.Info("allowsAllVerbs mismatch for {0} for {1} allowedverbs {2}", httpMethod, string.Join("/", withPathInfoParts), this.allowedVerbs);
|
||||||
return false;
|
return false;
|
||||||
|
@ -457,8 +457,7 @@ namespace Emby.Server.Implementations.Services
|
||||||
|
|
||||||
public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
|
public object CreateRequest(string pathInfo, Dictionary<string, string> queryStringAndFormData, object fromInstance)
|
||||||
{
|
{
|
||||||
var requestComponents = pathInfo.Split(PathSeperatorChar)
|
var requestComponents = pathInfo.Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
.Where(x => !String.IsNullOrEmpty(x)).ToArray();
|
|
||||||
|
|
||||||
ExplodeComponents(ref requestComponents);
|
ExplodeComponents(ref requestComponents);
|
||||||
|
|
||||||
|
@ -555,10 +554,5 @@ namespace Emby.Server.Implementations.Services
|
||||||
|
|
||||||
return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
|
return this.typeDeserializer.PopulateFromMap(fromInstance, requestKeyValuesMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return UniqueMatchHashKey.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -518,9 +518,18 @@ namespace MediaBrowser.Api.Library
|
||||||
LogDownload(item, user, auth);
|
LogDownload(item, user, auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var path = item.Path;
|
||||||
|
|
||||||
|
// Quotes are valid in linux. They'll possibly cause issues here
|
||||||
|
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
|
||||||
|
if (!string.IsNullOrWhiteSpace(filename))
|
||||||
|
{
|
||||||
|
headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
{
|
{
|
||||||
Path = item.Path,
|
Path = path,
|
||||||
ResponseHeaders = headers
|
ResponseHeaders = headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,21 +186,6 @@ namespace MediaBrowser.Controller.Entities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
|
||||||
public string SlugName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var name = Name;
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SlugReplaceChars.Aggregate(name, (current, c) => current.Replace(c, SlugChar));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public bool IsUnaired
|
public bool IsUnaired
|
||||||
{
|
{
|
||||||
|
@ -664,27 +649,34 @@ namespace MediaBrowser.Controller.Entities
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortable = Name.Trim().ToLower();
|
var sortable = Name.Trim().ToLower();
|
||||||
sortable = ConfigurationManager.Configuration.SortRemoveCharacters.Aggregate(sortable, (current, search) => current.Replace(search.ToLower(), string.Empty));
|
|
||||||
|
|
||||||
sortable = ConfigurationManager.Configuration.SortReplaceCharacters.Aggregate(sortable, (current, search) => current.Replace(search.ToLower(), " "));
|
foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
|
||||||
|
{
|
||||||
|
sortable = sortable.Replace(removeChar, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
|
||||||
|
{
|
||||||
|
sortable = sortable.Replace(replaceChar, " ");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
|
foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
|
||||||
{
|
{
|
||||||
var searchLower = search.ToLower();
|
|
||||||
// Remove from beginning if a space follows
|
// Remove from beginning if a space follows
|
||||||
if (sortable.StartsWith(searchLower + " "))
|
if (sortable.StartsWith(search + " "))
|
||||||
{
|
{
|
||||||
sortable = sortable.Remove(0, searchLower.Length + 1);
|
sortable = sortable.Remove(0, search.Length + 1);
|
||||||
}
|
}
|
||||||
// Remove from middle if surrounded by spaces
|
// Remove from middle if surrounded by spaces
|
||||||
sortable = sortable.Replace(" " + searchLower + " ", " ");
|
sortable = sortable.Replace(" " + search + " ", " ");
|
||||||
|
|
||||||
// Remove from end if followed by a space
|
// Remove from end if followed by a space
|
||||||
if (sortable.EndsWith(" " + searchLower))
|
if (sortable.EndsWith(" " + search))
|
||||||
{
|
{
|
||||||
sortable = sortable.Remove(sortable.Length - (searchLower.Length + 1));
|
sortable = sortable.Remove(sortable.Length - (search.Length + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ModifySortChunks(sortable);
|
return ModifySortChunks(sortable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,7 +763,15 @@ namespace MediaBrowser.Controller.Entities
|
||||||
public T FindParent<T>()
|
public T FindParent<T>()
|
||||||
where T : Folder
|
where T : Folder
|
||||||
{
|
{
|
||||||
return GetParents().OfType<T>().FirstOrDefault();
|
foreach (var parent in GetParents())
|
||||||
|
{
|
||||||
|
var item = parent as T;
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
|
@ -2140,8 +2140,8 @@ namespace MediaBrowser.Controller.Entities
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = System.IO.Path.GetFileNameWithoutExtension(Path);
|
var filename = System.IO.Path.GetFileNameWithoutExtension(Path);
|
||||||
var extensions = new[] { ".nfo", ".xml", ".srt" }.ToList();
|
var extensions = new List<string> { ".nfo", ".xml", ".srt" };
|
||||||
extensions.AddRange(SupportedImageExtensionsList);
|
extensions.AddRange(SupportedImageExtensions);
|
||||||
|
|
||||||
return FileSystem.GetFiles(FileSystem.GetDirectoryName(Path), extensions.ToArray(extensions.Count), false, false)
|
return FileSystem.GetFiles(FileSystem.GetDirectoryName(Path), extensions.ToArray(extensions.Count), false, false)
|
||||||
.Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase))
|
.Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -2392,7 +2392,14 @@ namespace MediaBrowser.Controller.Entities
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetParents().FirstOrDefault(i => i.IsTopParent);
|
foreach (var parent in GetParents())
|
||||||
|
{
|
||||||
|
if (parent.IsTopParent)
|
||||||
|
{
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
|
|
|
@ -63,8 +63,7 @@
|
||||||
<HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath>
|
<HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
<Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
|
<HintPath>..\packages\SkiaSharp.1.58.1\lib\net45\SkiaSharp.dll</HintPath>
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
<HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||||
|
@ -196,4 +195,11 @@
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
|
<Import Project="..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets" Condition="Exists('..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets')" />
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\SkiaSharp.1.58.1\build\net45\SkiaSharp.targets'))" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
|
@ -4,7 +4,7 @@
|
||||||
<package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" />
|
<package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" />
|
||||||
<package id="SharpCompress" version="0.14.0" targetFramework="net46" />
|
<package id="SharpCompress" version="0.14.0" targetFramework="net46" />
|
||||||
<package id="SimpleInjector" version="4.0.8" targetFramework="net46" />
|
<package id="SimpleInjector" version="4.0.8" targetFramework="net46" />
|
||||||
<package id="SkiaSharp" version="1.58.0" targetFramework="net46" />
|
<package id="SkiaSharp" version="1.58.1" targetFramework="net46" />
|
||||||
<package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net46" />
|
<package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net46" />
|
||||||
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net46" />
|
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net46" />
|
||||||
</packages>
|
</packages>
|
|
@ -85,7 +85,7 @@
|
||||||
<HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath>
|
<HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
<Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
|
<HintPath>..\packages\SkiaSharp.1.58.1\lib\net45\SkiaSharp.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
<HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||||
|
@ -162,6 +162,14 @@
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Content Include="..\packages\SkiaSharp.1.58.1\runtimes\win7-x64\native\libSkiaSharp.dll">
|
||||||
|
<Link>x64\libSkiaSharp.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="..\packages\SkiaSharp.1.58.1\runtimes\win7-x86\native\libSkiaSharp.dll">
|
||||||
|
<Link>x86\libSkiaSharp.dll</Link>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll">
|
<Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll">
|
||||||
<Link>MediaBrowser.InstallUtil.dll</Link>
|
<Link>MediaBrowser.InstallUtil.dll</Link>
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
@ -174,17 +182,11 @@
|
||||||
<Link>MediaBrowser.Updater.exe</Link>
|
<Link>MediaBrowser.Updater.exe</Link>
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="x64\libSkiaSharp.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="x64\sqlite3.dll">
|
<Content Include="x64\sqlite3.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<EmbeddedResource Include="Icon.ico" />
|
<EmbeddedResource Include="Icon.ico" />
|
||||||
<Content Include="Resources\Images\mb3logo800.png" />
|
<Content Include="Resources\Images\mb3logo800.png" />
|
||||||
<Content Include="x86\libSkiaSharp.dll">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="x86\sqlite3.dll">
|
<Content Include="x86\sqlite3.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" />
|
<package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" />
|
||||||
<package id="SharpCompress" version="0.14.0" targetFramework="net462" />
|
<package id="SharpCompress" version="0.14.0" targetFramework="net462" />
|
||||||
<package id="SimpleInjector" version="4.0.8" targetFramework="net462" />
|
<package id="SimpleInjector" version="4.0.8" targetFramework="net462" />
|
||||||
<package id="SkiaSharp" version="1.58.0" targetFramework="net462" />
|
<package id="SkiaSharp" version="1.58.1" targetFramework="net462" />
|
||||||
<package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net462" />
|
<package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net462" />
|
||||||
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net462" />
|
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net462" />
|
||||||
</packages>
|
</packages>
|
|
@ -1 +0,0 @@
|
||||||
20e469be83c5d41bb44d085c36550780e788a8ef
|
|
|
@ -1,3 +1,3 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("3.2.30.3")]
|
[assembly: AssemblyVersion("3.2.30.4")]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user