Compare commits

..

65 Commits

Author SHA1 Message Date
Joshua M. Boniface 595a68b822 Bump version for 10.3.7 2019-07-24 10:48:35 -04:00
Anthony Lavado c5d9480313 Merge pull request #1552 from cvium/fix_livetv_v2
Disable buffering in HttpClient as it causes big requests to timeout
2019-07-22 02:28:38 -04:00
Claus Vium dadfc09c01 Add HttpCompletionOption.ResponseHeadersRead to the buffering option to avoid potentially having 2 copies in memory 2019-07-20 13:36:59 +02:00
Claus Vium 886c88576c Use HttpCompletionOption.ResponseHeadersRead and resort to Content-Length header for non-buffered content 2019-07-19 23:22:30 +02:00
Joshua M. Boniface cf2f5b2026 Merge pull request #1538 from joshuaboniface/epg
Try to fix XmlTvListingsProvider
2019-07-14 17:09:00 -04:00
Joshua M. Boniface 135c16c721 Merge pull request #1537 from joshuaboniface/contenttype
Properly set content type
2019-07-14 17:08:54 -04:00
Bond_009 5d9fa06675 Cleanup 2019-07-13 17:18:39 -04:00
Bond_009 b294b802a8 Try to fix XmlTvListingsProvider 2019-07-13 17:18:27 -04:00
Bond_009 7bb504d491 Create a new HttpMethod from the function name 2019-07-13 17:12:06 -04:00
Bond_009 b1bd062709 Properly set content type 2019-07-13 17:12:06 -04:00
Joshua M. Boniface 4f17ed961e Merge pull request #1514 from Bond-009/httpclient2
Fix issues with HttpClientManager
2019-07-06 17:01:35 -04:00
Bond_009 5fc4ad6c4e Address comments 2019-07-06 20:04:45 +02:00
Bond_009 b117b364f2 Remove duplicate code 2019-07-06 20:04:45 +02:00
Bond_009 3603c64fa6 Use HttpResponseHeaders instead of a dictionary 2019-07-06 20:04:45 +02:00
Bond_009 d405a400aa Fixes issues with HttpClientManager 2019-07-06 20:04:42 +02:00
Joshua M. Boniface 54c6f02ebb Merge pull request #1455 from ferferga/release-10.3.z
Vacuum databases at startup
2019-07-06 13:57:18 -04:00
Joshua M. Boniface b3f9d04501 Bump version for 10.3.6 2019-07-06 13:34:38 -04:00
Erwin de Haan acf52b9b55 Cleanup extra spaces. 2019-07-04 20:55:49 +02:00
Erwin de Haan 7587fe56d8 Moved VACUUM down to the end of the list. 2019-07-04 20:54:57 +02:00
Anthony Lavado ab34a95142 Merge pull request #1473 from DrPandemic/apply-deprecation-WebRequest-on-10.3.z
Apply deprecation web request on 10.3.z
2019-07-04 14:51:24 -04:00
Andrew Rabert 0ee40cb636 Merge pull request #1495 from joshuaboniface/better-restart-script
Add nicer restart script
2019-06-29 19:48:54 -04:00
Joshua M. Boniface 62105c249f Use which to find the service binary path 2019-06-28 11:15:08 -04:00
Joshua M. Boniface a629f209b9 Make message wording more consistent 2019-06-28 11:06:55 -04:00
Joshua M. Boniface c08c0272b5 Add nicer restart script
The old restart script was buggy, as reported in #1320. This updated
script seems to work far more reliably and conforms to the existing
jellyfin-sudoers packages sudo configuration.
2019-06-27 18:05:03 -04:00
dkanada c52e8a2027 Merge pull request #1394 from joern-h/bugfix-issue1347
Check if an item is a child of an EnabledFolder
2019-06-26 01:19:34 -07:00
Jörn 1fd8164756 fix issue #1347 introduced in pr #930 2019-06-26 01:13:54 -07:00
dkanada 3c16c34386 Merge pull request #1485 from DrPandemic/fix-skia-segfault
Fix skia segfault
2019-06-25 12:44:29 -05:00
DrPandemic 394d96246b Check path before opening image 2019-06-24 20:13:07 -04:00
Anthony Lavado 084854d71d Merge pull request #1478 from cvium/fix_tvdb_again
Wait for the async authentication to finish when the JTW token expires
2019-06-22 02:08:46 -04:00
Claus Vium c2ab0ad641 Wait for the async authentication to finish when the JTW token expires 2019-06-21 19:08:04 +02:00
Claus Vium 7eb94e9674 Update MediaBrowser.Common/Net/IHttpClient.cs
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-06-18 22:21:07 -04:00
Bond-009 0a5550b13d Remove more unused stuff 2019-06-18 22:20:34 -04:00
Bond-009 067200be83 Remove usage of depricated 'WebRequest'
Ref: https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest?view=netframework-4.7.2
2019-06-17 19:35:05 -04:00
ferferga b136f14084 Vacuum databases at startup 2019-06-10 11:31:38 +02:00
Joshua M. Boniface d5fe82314e Bump version for 10.3.5 2019-06-09 21:47:45 -04:00
Joshua M. Boniface 06834fefef Merge pull request #1447 from joshuaboniface/implement-invalidauth
Implement InvalidAuthProvider
2019-06-09 15:36:25 -04:00
Joshua M. Boniface 2946ae1009 Revert "Don't set a default reset provider"
This reverts commit c230d49d7c.

This reenables an edge case where an admin might want to reset, with
the default auth provider, the password of an externally-provided
user so they could "unlock" the account while it was failing. There
might be minor security implications to this, but the malicious
actor would need FS access to do it (as they would with any password
resets) so it's probably best to keep it as-is.

Removing this in the first place was due to a misunderstanding
anyways so no harm.
2019-06-09 15:29:43 -04:00
Joshua M. Boniface 4b8f735cb8 Remove superfluous conditional
This wasn't needed to prevent updating the policy on-disk from my
tests and can be removed as suggested by @Bond-009
2019-06-09 13:57:49 -04:00
Joshua M. Boniface c230d49d7c Don't set a default reset provider 2019-06-09 13:46:53 -04:00
Joshua M. Boniface 20e2cb2d86 Use SecurityException for auth failure 2019-06-09 13:45:51 -04:00
Joshua M. Boniface b70083f3b3 Apply suggestions from code review
Co-Authored-By: Claus Vium <cvium@users.noreply.github.com>
Co-Authored-By: Bond-009 <bond.009@outlook.com>
2019-06-09 13:41:14 -04:00
Joshua M. Boniface 74ef389879 Add nicer log message and comment 2019-06-09 11:07:35 -04:00
Joshua M. Boniface d78a55adb4 Implement InvalidAuthProvider
Implements the InvalidAuthProvider, which acts as a fallback if a
configured authentication provider, e.g. LDAP, is unavailable due
to a load failure or removal. Until the user or the authentication
plugin is corrected, this will cause users with the missing provider
to be locked out, while throwing errors in the logs about the issue.

Fixes #1445 part 2
2019-06-08 22:54:31 -04:00
Andrew Rabert 6f99ed3955 Merge pull request #1443 from jellyfin/qemu
Docker - Update arm* Dockerfiles for latest multiarch
2019-06-07 18:41:35 -04:00
Andrew Rabert 247a5e12ab Docker - Update arm* Dockerfiles for latest multiarch
Relates to this change https://github.com/multiarch/qemu-user-static/commit/7bdafb96ee59fde53d07a291f9446d2b8957d372
2019-06-07 00:00:14 -04:00
Joshua M. Boniface 855911333a Bump version for 10.3.4 2019-06-06 22:45:37 -04:00
Anthony Lavado 127bfc7d3b Merge pull request #1437 from pjeanjean/master
Fix issue #1436: media folders appear empty unless user has all libraries access
2019-06-06 17:21:20 -04:00
pjeanjean 7919dd81da Skip user permission checking for UserRootFolder
Fix #1436
UserRootFolders are used to represent virtual folders that exist outside
of libraries. As such, it doesn't make sense to check if a user has the
right to access their library (named `Media Folders`).
2019-06-06 08:30:56 +02:00
Anthony Lavado e1da046960 Merge pull request #1426 from jellyfin/cvium-fix-tvdb-refresh
Fix inverted comparison in the tvdb token refresh logic
2019-05-31 21:27:33 -04:00
Claus Vium a756026962 Fix inverted comparison in the tvdb token refresh logic 2019-05-31 07:24:52 +02:00
Anthony Lavado 75260a960b Merge pull request #1406 from DrPandemic/fix-pin-update
Format the PIN when updating it
2019-05-31 00:58:53 -04:00
DrPandemic 69ee49bee6 Format correctly the PIN when updating it 2019-05-25 13:46:55 -04:00
Joshua M. Boniface 1bf3a26a61 Bump version for 10.3.3 2019-05-17 23:12:21 -04:00
Joshua M. Boniface c5760b3a40 Merge pull request #1380 from bugfixin/dlna-photo-thumb-fix
Improve Photo rendering in DLNA
2019-05-17 22:55:43 -04:00
Joshua M. Boniface 4de8bf3295 Merge pull request #1338 from cvium/fix_extras
Enforce a specific folder structure for Extras to avoid misidentification
2019-05-17 09:00:22 -04:00
bugfixin 87c8f19f19 Move DLNA thumbnail element to after larger image elements 2019-05-16 18:00:38 +00:00
Claus Vium 0ef52c739e Review changes
Untested
2019-05-16 07:27:38 +02:00
Joshua M. Boniface 0a9a6b949c Merge pull request #1372 from DrPandemic/fix-pin
Fix broken pin in 10.3.z
2019-05-14 21:54:28 -04:00
DrPandemic c22068d6b1 Fix pin bug introduced in 10.3.z.
The issue is that the new easyPassword format prepends the hash
function. This PR extract the hash from "$SHA1$_hash_".
2019-05-11 19:53:34 -04:00
Joshua M. Boniface bbc1a86b57 Merge pull request #1306 from oddstr13/pr-sudoless-build-1
Move artifact chown inside docker to avoid sudo
2019-05-10 09:25:41 -04:00
Bond-009 cd83d80f2b Merge pull request #1294 from DrPandemic/fix-download-non-ascii
Fix non-ascii filename downloads
2019-05-09 17:30:19 +02:00
Jean-Samuel Aubry-Guzzi 12721eb7dd Fix non-ascii filename downloads
Follow https://tools.ietf.org/html/rfc5987#section-3.2.2 to encode
non-ascii filenames in HTTP Content-Disposition header.
2019-05-07 19:43:04 -04:00
Claus Vium b8a09339cd Enforce extras folder structure according to Emby's wiki 2019-05-02 08:14:00 +02:00
Odd Stråbø 3634d367c1 Move artifact chown inside docker to avoid sudo 2019-05-01 20:32:15 +02:00
Claus Vium c1daea0ec7 Change owner and parent id of extras to the main media item 2019-05-01 07:47:22 +02:00
52 changed files with 612 additions and 709 deletions
+3
View File
@@ -24,6 +24,9 @@
- [Lynxy](https://github.com/Lynxy) - [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng) - [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff) - [ploughpuff](https://github.com/ploughpuff)
- [pjeanjean](https://github.com/pjeanjean)
- [DrPandemic](https://github.com/drpandemic)
- [joern-h](https://github.com/joern-h)
# Emby Contributors # Emby Contributors
+1 -1
View File
@@ -21,7 +21,7 @@ RUN apt-get update \
COPY --from=ffmpeg / / COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.3.2 ARG JELLYFIN_WEB_VERSION=10.3.7
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \ && rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
+3 -7
View File
@@ -3,11 +3,6 @@
ARG DOTNET_VERSION=3.0 ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
RUN tar -xzvf qemu-arm-static.tar.gz
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
@@ -21,8 +16,9 @@ RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm /jellyfin" build_jellyfin Jellyfin.Server Release linux-arm /jellyfin"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7 FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
COPY --from=qemu_extract qemu-arm-static /usr/bin COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
@@ -30,7 +26,7 @@ RUN apt-get update \
&& chmod 777 /cache /config /media && chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.3.2 ARG JELLYFIN_WEB_VERSION=10.3.7
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \ && rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
+3 -8
View File
@@ -3,12 +3,6 @@
ARG DOTNET_VERSION=3.0 ARG DOTNET_VERSION=3.0
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM alpine as qemu_extract
COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
RUN tar -xzvf qemu-aarch64-static.tar.gz
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
@@ -22,8 +16,9 @@ RUN bash -c "source deployment/common.build.sh && \
build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin" build_jellyfin Jellyfin.Server Release linux-arm64 /jellyfin"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8 FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
COPY --from=qemu_extract qemu-aarch64-static /usr/bin COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
@@ -31,7 +26,7 @@ RUN apt-get update \
&& chmod 777 /cache /config /media && chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
ARG JELLYFIN_WEB_VERSION=10.3.2 ARG JELLYFIN_WEB_VERSION=10.3.7
RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ RUN curl -L https://github.com/jellyfin/jellyfin-web/archive/v${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& rm -rf /jellyfin/jellyfin-web \ && rm -rf /jellyfin/jellyfin-web \
&& mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web && mv jellyfin-web-${JELLYFIN_WEB_VERSION} /jellyfin/jellyfin-web
+3 -2
View File
@@ -920,8 +920,6 @@ namespace Emby.Dlna.Didl
} }
} }
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{ {
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG"); AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
@@ -930,6 +928,9 @@ namespace Emby.Dlna.Didl
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG"); AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN"); AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
} }
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
} }
private void AddEmbeddedImageAsCover(string name, XmlWriter writer) private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
+11 -12
View File
@@ -34,16 +34,13 @@ namespace Emby.Dlna.PlayTo
{ {
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
using (var response = await PostSoapDataAsync(NormalizeServiceUrl(baseUrl, service.ControlUrl), "\"" + service.ServiceType + "#" + command + "\"", postData, header, logRequest, cancellationToken) var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync(url, '\"' + service.ServiceType + '#' + command + '\"', postData, header, logRequest, cancellationToken)
.ConfigureAwait(false)) .ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{ {
using (var stream = response.Content) return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
{
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
}
}
} }
} }
@@ -121,15 +118,18 @@ namespace Emby.Dlna.PlayTo
} }
} }
private Task<HttpResponseInfo> PostSoapDataAsync(string url, private Task<HttpResponseInfo> PostSoapDataAsync(
string url,
string soapAction, string soapAction,
string postData, string postData,
string header, string header,
bool logRequest, bool logRequest,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (!soapAction.StartsWith("\"")) if (soapAction[0] != '\"')
soapAction = "\"" + soapAction + "\""; {
soapAction = '\"' + soapAction + '\"';
}
var options = new HttpRequestOptions var options = new HttpRequestOptions
{ {
@@ -155,7 +155,6 @@ namespace Emby.Dlna.PlayTo
} }
options.RequestContentType = "text/xml"; options.RequestContentType = "text/xml";
options.AppendCharsetToMimeType = true;
options.RequestContent = postData; options.RequestContent = postData;
return _httpClient.Post(options); return _httpClient.Post(options);
+12 -12
View File
@@ -6,6 +6,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -429,7 +430,7 @@ namespace Emby.Server.Implementations
/// Gets the current application user agent /// Gets the current application user agent
/// </summary> /// </summary>
/// <value>The application user agent.</value> /// <value>The application user agent.</value>
public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; public string ApplicationUserAgent => Name.Replace(' ','-') + '/' + ApplicationVersion;
/// <summary> /// <summary>
/// Gets the email address for use within a comment section of a user agent field. /// Gets the email address for use within a comment section of a user agent field.
@@ -689,11 +690,6 @@ namespace Emby.Server.Implementations
await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false);
} }
protected virtual IHttpClient CreateHttpClient()
{
return new HttpClientManager.HttpClientManager(ApplicationPaths, LoggerFactory, FileSystemManager, () => ApplicationUserAgent);
}
public static IStreamHelper StreamHelper { get; set; } public static IStreamHelper StreamHelper { get; set; }
/// <summary> /// <summary>
@@ -719,7 +715,11 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(FileSystemManager); serviceCollection.AddSingleton(FileSystemManager);
serviceCollection.AddSingleton<TvDbClientManager>(); serviceCollection.AddSingleton<TvDbClientManager>();
HttpClient = CreateHttpClient(); HttpClient = new HttpClientManager.HttpClientManager(
ApplicationPaths,
LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(),
FileSystemManager,
() => ApplicationUserAgent);
serviceCollection.AddSingleton(HttpClient); serviceCollection.AddSingleton(HttpClient);
serviceCollection.AddSingleton(NetworkManager); serviceCollection.AddSingleton(NetworkManager);
@@ -1560,12 +1560,12 @@ namespace Emby.Server.Implementations
LogErrorResponseBody = false, LogErrorResponseBody = false,
LogErrors = false, LogErrors = false,
LogRequest = false, LogRequest = false,
TimeoutMs = 10000,
BufferContent = false, BufferContent = false,
CancellationToken = cancellationToken CancellationToken = cancellationToken
}).ConfigureAwait(false)) }).ConfigureAwait(false))
{ {
return GetWanApiUrl(response.ReadToEnd().Trim()); string res = await response.ReadToEndAsync().ConfigureAwait(false);
return GetWanApiUrl(res.Trim());
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -1717,15 +1717,15 @@ namespace Emby.Server.Implementations
LogErrorResponseBody = false, LogErrorResponseBody = false,
LogErrors = LogPing, LogErrors = LogPing,
LogRequest = LogPing, LogRequest = LogPing,
TimeoutMs = 5000,
BufferContent = false, BufferContent = false,
CancellationToken = cancellationToken CancellationToken = cancellationToken
}, "POST").ConfigureAwait(false)) }, HttpMethod.Post).ConfigureAwait(false))
{ {
using (var reader = new StreamReader(response.Content)) using (var reader = new StreamReader(response.Content))
{ {
var result = reader.ReadToEnd(); var result = await reader.ReadToEndAsync().ConfigureAwait(false);
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase); var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
@@ -223,6 +223,8 @@ namespace Emby.Server.Implementations.Data
"pragma temp_store = file" "pragma temp_store = file"
}); });
} }
// Configuration and pragmas can affect VACUUM so it needs to be last.
queries.Add("VACUUM");
db.ExecuteAll(string.Join(";", queries)); db.ExecuteAll(string.Join(";", queries));
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
@@ -1,18 +0,0 @@
using System;
using System.Net.Http;
namespace Emby.Server.Implementations.HttpClientManager
{
/// <summary>
/// Class HttpClientInfo
/// </summary>
public class HttpClientInfo
{
/// <summary>
/// Gets or sets the last timeout.
/// </summary>
/// <value>The last timeout.</value>
public DateTime LastTimeout { get; set; }
public HttpClient HttpClient { get; set; }
}
}
@@ -1,12 +1,9 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Cache; using System.Net.Http;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@@ -24,30 +21,24 @@ namespace Emby.Server.Implementations.HttpClientManager
/// </summary> /// </summary>
public class HttpClientManager : IHttpClient public class HttpClientManager : IHttpClient
{ {
/// <summary>
/// When one request to a host times out, we'll ban all other requests for this period of time, to prevent scans from stalling
/// </summary>
private const int TimeoutSeconds = 30;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger; private readonly ILogger _logger;
/// <summary>
/// The _app paths
/// </summary>
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly Func<string> _defaultUserAgentFn; private readonly Func<string> _defaultUserAgentFn;
/// <summary>
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
/// DON'T dispose it after use.
/// </summary>
/// <value>The HTTP clients.</value>
private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HttpClientManager" /> class. /// Initializes a new instance of the <see cref="HttpClientManager" /> class.
/// </summary> /// </summary>
public HttpClientManager( public HttpClientManager(
IApplicationPaths appPaths, IApplicationPaths appPaths,
ILoggerFactory loggerFactory, ILogger<HttpClientManager> logger,
IFileSystem fileSystem, IFileSystem fileSystem,
Func<string> defaultUserAgentFn) Func<string> defaultUserAgentFn)
{ {
@@ -55,46 +46,33 @@ namespace Emby.Server.Implementations.HttpClientManager
{ {
throw new ArgumentNullException(nameof(appPaths)); throw new ArgumentNullException(nameof(appPaths));
} }
if (loggerFactory == null)
if (logger == null)
{ {
throw new ArgumentNullException(nameof(loggerFactory)); throw new ArgumentNullException(nameof(logger));
} }
_logger = loggerFactory.CreateLogger("HttpClient"); _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_appPaths = appPaths; _appPaths = appPaths;
_defaultUserAgentFn = defaultUserAgentFn; _defaultUserAgentFn = defaultUserAgentFn;
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager.Expect100Continue = false;
} }
/// <summary> /// <summary>
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests. /// Gets the correct http client for the given url.
/// DON'T dispose it after use.
/// </summary> /// </summary>
/// <value>The HTTP clients.</value> /// <param name="url">The url.</param>
private readonly ConcurrentDictionary<string, HttpClientInfo> _httpClients = new ConcurrentDictionary<string, HttpClientInfo>();
/// <summary>
/// Gets
/// </summary>
/// <param name="host">The host.</param>
/// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param>
/// <returns>HttpClient.</returns> /// <returns>HttpClient.</returns>
/// <exception cref="ArgumentNullException">host</exception> private HttpClient GetHttpClient(string url)
private HttpClientInfo GetHttpClient(string host, bool enableHttpCompression)
{ {
if (string.IsNullOrEmpty(host)) var key = GetHostFromUrl(url);
{
throw new ArgumentNullException(nameof(host));
}
var key = host + enableHttpCompression;
if (!_httpClients.TryGetValue(key, out var client)) if (!_httpClients.TryGetValue(key, out var client))
{ {
client = new HttpClientInfo(); client = new HttpClient()
{
BaseAddress = new Uri(url)
};
_httpClients.TryAdd(key, client); _httpClients.TryAdd(key, client);
} }
@@ -102,119 +80,84 @@ namespace Emby.Server.Implementations.HttpClientManager
return client; return client;
} }
private WebRequest GetRequest(HttpRequestOptions options, string method) private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method)
{ {
string url = options.Url; string url = options.Url;
var uriAddress = new Uri(url); var uriAddress = new Uri(url);
string userInfo = uriAddress.UserInfo; string userInfo = uriAddress.UserInfo;
if (!string.IsNullOrWhiteSpace(userInfo)) if (!string.IsNullOrWhiteSpace(userInfo))
{ {
_logger.LogInformation("Found userInfo in url: {0} ... url: {1}", userInfo, url); _logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
url = url.Replace(userInfo + "@", string.Empty); url = url.Replace(userInfo + '@', string.Empty);
} }
var request = WebRequest.Create(url); var request = new HttpRequestMessage(method, url);
if (request is HttpWebRequest httpWebRequest) AddRequestHeaders(request, options);
switch (options.DecompressionMethod)
{ {
AddRequestHeaders(httpWebRequest, options); case CompressionMethod.Deflate | CompressionMethod.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
if (options.EnableHttpCompression) break;
{ case CompressionMethod.Deflate:
httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate; request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
if (options.DecompressionMethod.HasValue break;
&& options.DecompressionMethod.Value == CompressionMethod.Gzip) case CompressionMethod.Gzip:
{ request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip; break;
} default:
} break;
else
{
httpWebRequest.AutomaticDecompression = DecompressionMethods.None;
}
httpWebRequest.KeepAlive = options.EnableKeepAlive;
if (!string.IsNullOrEmpty(options.Host))
{
httpWebRequest.Host = options.Host;
}
if (!string.IsNullOrEmpty(options.Referer))
{
httpWebRequest.Referer = options.Referer;
}
} }
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); if (options.EnableKeepAlive)
{
request.Headers.Add(HeaderNames.Connection, "Keep-Alive");
}
request.Method = method; //request.Headers.Add(HeaderNames.CacheControl, "no-cache");
request.Timeout = options.TimeoutMs;
/*
if (!string.IsNullOrWhiteSpace(userInfo)) if (!string.IsNullOrWhiteSpace(userInfo))
{ {
var parts = userInfo.Split(':'); var parts = userInfo.Split(':');
if (parts.Length == 2) if (parts.Length == 2)
{ {
request.Credentials = GetCredential(url, parts[0], parts[1]); request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]);
// TODO: .net core ??
request.PreAuthenticate = true;
} }
} }
*/
return request; return request;
} }
private static CredentialCache GetCredential(string url, string username, string password) private void AddRequestHeaders(HttpRequestMessage request, HttpRequestOptions options)
{
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
var credentialCache = new CredentialCache();
credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
return credentialCache;
}
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
{ {
var hasUserAgent = false; var hasUserAgent = false;
foreach (var header in options.RequestHeaders) foreach (var header in options.RequestHeaders)
{ {
if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
{ {
request.Accept = header.Value;
}
else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
{
SetUserAgent(request, header.Value);
hasUserAgent = true; hasUserAgent = true;
} }
else
{ request.Headers.Add(header.Key, header.Value);
request.Headers.Set(header.Key, header.Value);
}
} }
if (!hasUserAgent && options.EnableDefaultUserAgent) if (!hasUserAgent && options.EnableDefaultUserAgent)
{ {
SetUserAgent(request, _defaultUserAgentFn()); request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn());
} }
} }
private static void SetUserAgent(HttpWebRequest request, string userAgent)
{
request.UserAgent = userAgent;
}
/// <summary> /// <summary>
/// Gets the response internal. /// Gets the response internal.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns>Task{HttpResponseInfo}.</returns> /// <returns>Task{HttpResponseInfo}.</returns>
public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options) public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
{ => SendAsync(options, HttpMethod.Get);
return SendAsync(options, "GET");
}
/// <summary> /// <summary>
/// Performs a GET request and returns the resulting stream /// Performs a GET request and returns the resulting stream
@@ -233,9 +176,16 @@ namespace Emby.Server.Implementations.HttpClientManager
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param> /// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns> /// <returns>Task{HttpResponseInfo}.</returns>
/// <exception cref="HttpException"> public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
/// </exception> => SendAsync(options, new HttpMethod(httpMethod));
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
/// <summary>
/// send as an asynchronous operation.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod)
{ {
if (options.CacheMode == CacheMode.None) if (options.CacheMode == CacheMode.None)
{ {
@@ -294,186 +244,89 @@ namespace Emby.Server.Implementations.HttpClientManager
} }
} }
private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, string httpMethod) private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod)
{ {
ValidateParams(options); ValidateParams(options);
options.CancellationToken.ThrowIfCancellationRequested(); options.CancellationToken.ThrowIfCancellationRequested();
var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); var client = GetHttpClient(options.Url);
if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) var httpWebRequest = GetRequestMessage(options, httpMethod);
if (options.RequestContentBytes != null
|| !string.IsNullOrEmpty(options.RequestContent)
|| httpMethod == HttpMethod.Post)
{ {
throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) if (options.RequestContentBytes != null)
{ {
IsTimedOut = true httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes);
};
}
var httpWebRequest = GetRequest(options, httpMethod);
if (options.RequestContentBytes != null ||
!string.IsNullOrEmpty(options.RequestContent) ||
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
{
try
{
var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
var contentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
if (options.AppendCharsetToMimeType)
{
contentType = contentType.TrimEnd(';') + "; charset=\"utf-8\"";
}
httpWebRequest.ContentType = contentType;
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
} }
catch (Exception ex) else if (options.RequestContent != null)
{ {
throw new HttpException(ex.Message) { IsTimedOut = true }; httpWebRequest.Content = new StringContent(
options.RequestContent,
null,
options.RequestContentType);
}
else
{
httpWebRequest.Content = new ByteArrayContent(Array.Empty<byte>());
} }
}
if (options.ResourcePool != null)
{
await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
}
if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds)
{
options.ResourcePool?.Release();
throw new HttpException($"Connection to {options.Url} timed out") { IsTimedOut = true };
} }
if (options.LogRequest) if (options.LogRequest)
{ {
if (options.LogRequestAsDebug) _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToString(), options.Url);
{
_logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url);
}
else
{
_logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url);
}
} }
try options.CancellationToken.ThrowIfCancellationRequested();
if (!options.BufferContent)
{ {
var response = await client.SendAsync(httpWebRequest, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false);
await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
options.CancellationToken.ThrowIfCancellationRequested(); options.CancellationToken.ThrowIfCancellationRequested();
if (!options.BufferContent) var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
return new HttpResponseInfo(response.Headers, response.Content.Headers)
{ {
var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false); Content = stream,
StatusCode = response.StatusCode,
ContentType = response.Content.Headers.ContentType?.MediaType,
ContentLength = response.Content.Headers.ContentLength,
ResponseUrl = response.Content.Headers.ContentLocation?.ToString()
};
}
var httpResponse = (HttpWebResponse)response; using (var response = await client.SendAsync(httpWebRequest, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false))
{
await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
EnsureSuccessStatusCode(client, httpResponse, options); options.CancellationToken.ThrowIfCancellationRequested();
options.CancellationToken.ThrowIfCancellationRequested(); using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse);
}
using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false))
{ {
var httpResponse = (HttpWebResponse)response; var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
memoryStream.Position = 0;
EnsureSuccessStatusCode(client, httpResponse, options); return new HttpResponseInfo(response.Headers, response.Content.Headers)
options.CancellationToken.ThrowIfCancellationRequested();
using (var stream = httpResponse.GetResponseStream())
{ {
var memoryStream = new MemoryStream(); Content = memoryStream,
await stream.CopyToAsync(memoryStream).ConfigureAwait(false); StatusCode = response.StatusCode,
ContentType = response.Content.Headers.ContentType?.MediaType,
memoryStream.Position = 0; ContentLength = memoryStream.Length,
ResponseUrl = response.Content.Headers.ContentLocation?.ToString()
return GetResponseInfo(httpResponse, memoryStream, memoryStream.Length, null); };
}
} }
} }
catch (OperationCanceledException ex)
{
throw GetCancellationException(options, client, options.CancellationToken, ex);
}
catch (Exception ex)
{
throw GetException(ex, options, client);
}
finally
{
options.ResourcePool?.Release();
}
}
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable)
{
var responseInfo = new HttpResponseInfo(disposable)
{
Content = content,
StatusCode = httpResponse.StatusCode,
ContentType = httpResponse.ContentType,
ContentLength = contentLength,
ResponseUrl = httpResponse.ResponseUri.ToString()
};
if (httpResponse.Headers != null)
{
SetHeaders(httpResponse.Headers, responseInfo);
}
return responseInfo;
}
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength)
{
var responseInfo = new HttpResponseInfo
{
TempFilePath = tempFile,
StatusCode = httpResponse.StatusCode,
ContentType = httpResponse.ContentType,
ContentLength = contentLength
};
if (httpResponse.Headers != null)
{
SetHeaders(httpResponse.Headers, responseInfo);
}
return responseInfo;
}
private static void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo)
{
foreach (var key in headers.AllKeys)
{
responseInfo.Headers[key] = headers[key];
}
} }
public Task<HttpResponseInfo> Post(HttpRequestOptions options) public Task<HttpResponseInfo> Post(HttpRequestOptions options)
{ => SendAsync(options, HttpMethod.Post);
return SendAsync(options, "POST");
}
/// <summary>
/// Performs a POST request
/// </summary>
/// <param name="options">The options.</param>
/// <param name="postData">Params to add to the POST data.</param>
/// <returns>stream on success, null on failure</returns>
public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData)
{
options.SetPostData(postData);
var response = await Post(options).ConfigureAwait(false);
return response.Content;
}
/// <summary> /// <summary>
/// Downloads the contents of a given url into a temporary location /// Downloads the contents of a given url into a temporary location
@@ -483,7 +336,6 @@ namespace Emby.Server.Implementations.HttpClientManager
public async Task<string> GetTempFile(HttpRequestOptions options) public async Task<string> GetTempFile(HttpRequestOptions options)
{ {
var response = await GetTempFileResponse(options).ConfigureAwait(false); var response = await GetTempFileResponse(options).ConfigureAwait(false);
return response.TempFilePath; return response.TempFilePath;
} }
@@ -502,44 +354,28 @@ namespace Emby.Server.Implementations.HttpClientManager
options.CancellationToken.ThrowIfCancellationRequested(); options.CancellationToken.ThrowIfCancellationRequested();
var httpWebRequest = GetRequest(options, "GET"); var httpWebRequest = GetRequestMessage(options, HttpMethod.Get);
if (options.ResourcePool != null)
{
await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
}
options.Progress.Report(0); options.Progress.Report(0);
if (options.LogRequest) if (options.LogRequest)
{ {
if (options.LogRequestAsDebug) _logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
{
_logger.LogDebug("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
}
else
{
_logger.LogInformation("HttpClientManager.GetTempFileResponse url: {0}", options.Url);
}
} }
var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); var client = GetHttpClient(options.Url);
try try
{ {
options.CancellationToken.ThrowIfCancellationRequested(); options.CancellationToken.ThrowIfCancellationRequested();
using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) using (var response = (await client.SendAsync(httpWebRequest, options.CancellationToken).ConfigureAwait(false)))
{ {
var httpResponse = (HttpWebResponse)response; await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
EnsureSuccessStatusCode(client, httpResponse, options);
options.CancellationToken.ThrowIfCancellationRequested(); options.CancellationToken.ThrowIfCancellationRequested();
var contentLength = GetContentLength(httpResponse); using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var stream = httpResponse.GetResponseStream())
using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{ {
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
@@ -547,35 +383,29 @@ namespace Emby.Server.Implementations.HttpClientManager
options.Progress.Report(100); options.Progress.Report(100);
return GetResponseInfo(httpResponse, tempFile, contentLength); var responseInfo = new HttpResponseInfo(response.Headers, response.Content.Headers)
{
TempFilePath = tempFile,
StatusCode = response.StatusCode,
ContentType = response.Content.Headers.ContentType?.MediaType,
ContentLength = response.Content.Headers.ContentLength
};
return responseInfo;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
DeleteTempFile(tempFile); if (File.Exists(tempFile))
throw GetException(ex, options, client); {
} File.Delete(tempFile);
finally }
{
options.ResourcePool?.Release(); throw GetException(ex, options);
} }
} }
private static long? GetContentLength(HttpWebResponse response) private Exception GetException(Exception ex, HttpRequestOptions options)
{
var length = response.ContentLength;
if (length == 0)
{
return null;
}
return length;
}
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private Exception GetException(Exception ex, HttpRequestOptions options, HttpClientInfo client)
{ {
if (ex is HttpException) if (ex is HttpException)
{ {
@@ -589,7 +419,7 @@ namespace Emby.Server.Implementations.HttpClientManager
{ {
if (options.LogErrors) if (options.LogErrors)
{ {
_logger.LogError(webException, "Error {status} getting response from {url}", webException.Status, options.Url); _logger.LogError(webException, "Error {Status} getting response from {Url}", webException.Status, options.Url);
} }
var exception = new HttpException(webException.Message, webException); var exception = new HttpException(webException.Message, webException);
@@ -599,11 +429,6 @@ namespace Emby.Server.Implementations.HttpClientManager
if (response != null) if (response != null)
{ {
exception.StatusCode = response.StatusCode; exception.StatusCode = response.StatusCode;
if ((int)response.StatusCode == 429)
{
client.LastTimeout = DateTime.UtcNow;
}
} }
} }
@@ -624,29 +449,17 @@ namespace Emby.Server.Implementations.HttpClientManager
if (operationCanceledException != null) if (operationCanceledException != null)
{ {
return GetCancellationException(options, client, options.CancellationToken, operationCanceledException); return GetCancellationException(options, options.CancellationToken, operationCanceledException);
} }
if (options.LogErrors) if (options.LogErrors)
{ {
_logger.LogError(ex, "Error getting response from {url}", options.Url); _logger.LogError(ex, "Error getting response from {Url}", options.Url);
} }
return ex; return ex;
} }
private void DeleteTempFile(string file)
{
try
{
_fileSystem.DeleteFile(file);
}
catch (IOException)
{
// Might not have been created at all. No need to worry.
}
}
private void ValidateParams(HttpRequestOptions options) private void ValidateParams(HttpRequestOptions options)
{ {
if (string.IsNullOrEmpty(options.Url)) if (string.IsNullOrEmpty(options.Url))
@@ -682,11 +495,10 @@ namespace Emby.Server.Implementations.HttpClientManager
/// Throws the cancellation exception. /// Throws the cancellation exception.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="exception">The exception.</param> /// <param name="exception">The exception.</param>
/// <returns>Exception.</returns> /// <returns>Exception.</returns>
private Exception GetCancellationException(HttpRequestOptions options, HttpClientInfo client, CancellationToken cancellationToken, OperationCanceledException exception) private Exception GetCancellationException(HttpRequestOptions options, CancellationToken cancellationToken, OperationCanceledException exception)
{ {
// If the HttpClient's timeout is reached, it will cancel the Task internally // If the HttpClient's timeout is reached, it will cancel the Task internally
if (!cancellationToken.IsCancellationRequested) if (!cancellationToken.IsCancellationRequested)
@@ -698,8 +510,6 @@ namespace Emby.Server.Implementations.HttpClientManager
_logger.LogError(msg); _logger.LogError(msg);
} }
client.LastTimeout = DateTime.UtcNow;
// Throw an HttpException so that the caller doesn't think it was cancelled by user code // Throw an HttpException so that the caller doesn't think it was cancelled by user code
return new HttpException(msg, exception) return new HttpException(msg, exception)
{ {
@@ -710,91 +520,20 @@ namespace Emby.Server.Implementations.HttpClientManager
return exception; return exception;
} }
private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options) private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options)
{ {
var statusCode = response.StatusCode; if (response.IsSuccessStatusCode)
var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299;
if (isSuccessful)
{ {
return; return;
} }
if (options.LogErrorResponseBody) var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
{ _logger.LogError("HTTP request failed with message: {Message}", msg);
try
{
using (var stream = response.GetResponseStream())
{
if (stream != null)
{
using (var reader = new StreamReader(stream))
{
var msg = reader.ReadToEnd();
_logger.LogError(msg); throw new HttpException(response.ReasonPhrase)
}
}
}
}
catch
{
}
}
throw new HttpException(response.StatusDescription)
{ {
StatusCode = response.StatusCode StatusCode = response.StatusCode
}; };
} }
private static Task<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout)
{
var taskCompletion = new TaskCompletionSource<WebResponse>();
var asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null);
ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true);
var callback = new TaskCallback { taskCompletion = taskCompletion };
asyncTask.ContinueWith(callback.OnSuccess, TaskContinuationOptions.NotOnFaulted);
// Handle errors
asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted);
return taskCompletion.Task;
}
private static void TimeoutCallback(object state, bool timedOut)
{
if (timedOut && state != null)
{
var request = (WebRequest)state;
request.Abort();
}
}
private class TaskCallback
{
public TaskCompletionSource<WebResponse> taskCompletion;
public void OnSuccess(Task<WebResponse> task)
{
taskCompletion.TrySetResult(task.Result);
}
public void OnError(Task<WebResponse> task)
{
if (task.Exception == null)
{
taskCompletion.TrySetException(Enumerable.Empty<Exception>());
}
else
{
taskCompletion.TrySetException(task.Exception);
}
}
}
} }
} }
@@ -165,6 +165,34 @@ namespace Emby.Server.Implementations.Library
return user.Password; return user.Password;
} }
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
{
ConvertPasswordFormat(user);
if (newPassword != null)
{
newPasswordHash = string.Format("$SHA1${0}", GetHashedString(user, newPassword));
}
if (string.IsNullOrWhiteSpace(newPasswordHash))
{
throw new ArgumentNullException(nameof(newPasswordHash));
}
user.EasyPassword = newPasswordHash;
}
public string GetEasyPasswordHash(User user)
{
// This should be removed in the future. This was added to let user login after
// Jellyfin 10.3.3 failed to save a well formatted PIN.
ConvertPasswordFormat(user);
return string.IsNullOrEmpty(user.EasyPassword)
? null
: (new PasswordHash(user.EasyPassword)).Hash;
}
public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
{ {
passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Net;
namespace Emby.Server.Implementations.Library
{
public class InvalidAuthProvider : IAuthenticationProvider
{
public string Name => "InvalidOrMissingAuthenticationProvider";
public bool IsEnabled => true;
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
{
throw new SecurityException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
}
public Task<bool> HasPassword(User user)
{
return Task.FromResult(true);
}
public Task ChangePassword(User user, string newPassword)
{
return Task.CompletedTask;
}
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
{
// Nothing here
}
public string GetPasswordHash(User user)
{
return string.Empty;
}
public string GetEasyPasswordHash(User user)
{
return string.Empty;
}
}
}
@@ -79,6 +79,8 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] _authenticationProviders; private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider; private DefaultAuthenticationProvider _defaultAuthenticationProvider;
private InvalidAuthProvider _invalidAuthProvider;
private IPasswordResetProvider[] _passwordResetProviders; private IPasswordResetProvider[] _passwordResetProviders;
private DefaultPasswordResetProvider _defaultPasswordResetProvider; private DefaultPasswordResetProvider _defaultPasswordResetProvider;
@@ -141,6 +143,8 @@ namespace Emby.Server.Implementations.Library
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
_passwordResetProviders = passwordResetProviders.ToArray(); _passwordResetProviders = passwordResetProviders.ToArray();
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First(); _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
@@ -307,8 +311,7 @@ namespace Emby.Server.Implementations.Library
user = Users user = Users
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
if (hasNewUserPolicy != null)
{ {
var policy = hasNewUserPolicy.GetNewUserPolicy(); var policy = hasNewUserPolicy.GetNewUserPolicy();
UpdateUserPolicy(user, policy, true); UpdateUserPolicy(user, policy, true);
@@ -400,7 +403,9 @@ namespace Emby.Server.Implementations.Library
if (providers.Length == 0) if (providers.Length == 0)
{ {
providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
_logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user.Name, user.Policy.AuthenticationProviderId);
providers = new IAuthenticationProvider[] { _invalidAuthProvider };
} }
return providers; return providers;
@@ -471,7 +476,7 @@ namespace Emby.Server.Implementations.Library
if (password == null) if (password == null)
{ {
// legacy // legacy
success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); success = string.Equals(GetAuthenticationProvider(user).GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
} }
else else
{ {
@@ -497,11 +502,11 @@ namespace Emby.Server.Implementations.Library
if (password == null) if (password == null)
{ {
// legacy // legacy
success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase);
} }
else else
{ {
success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase);
} }
} }
} }
@@ -546,13 +551,6 @@ namespace Emby.Server.Implementations.Library
} }
} }
private string GetLocalPasswordHash(User user)
{
return string.IsNullOrEmpty(user.EasyPassword)
? null
: user.EasyPassword;
}
/// <summary> /// <summary>
/// Loads the users from the repository /// Loads the users from the repository
/// </summary> /// </summary>
@@ -596,7 +594,7 @@ namespace Emby.Server.Implementations.Library
} }
bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetLocalPasswordHash(user)); bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user));
bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
hasConfiguredEasyPassword : hasConfiguredEasyPassword :
@@ -884,17 +882,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));
} }
if (newPassword != null) GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash);
{
newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword);
}
if (string.IsNullOrWhiteSpace(newPasswordHash))
{
throw new ArgumentNullException(nameof(newPasswordHash));
}
user.EasyPassword = newPasswordHash;
UpdateUser(user); UpdateUser(user);
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
UserAgent = "Emby/3.0", UserAgent = "Emby/3.0",
// Shouldn't matter but may cause issues // Shouldn't matter but may cause issues
EnableHttpCompression = false DecompressionMethod = CompressionMethod.None
}; };
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
@@ -96,8 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/schedules", Url = ApiUrl + "/schedules",
UserAgent = UserAgent, UserAgent = UserAgent,
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
// The data can be large so give it some extra time
TimeoutMs = 60000,
LogErrorResponseBody = true, LogErrorResponseBody = true,
RequestContent = requestString RequestContent = requestString
}; };
@@ -115,9 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/programs", Url = ApiUrl + "/programs",
UserAgent = UserAgent, UserAgent = UserAgent,
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
LogErrorResponseBody = true, LogErrorResponseBody = true
// The data can be large so give it some extra time
TimeoutMs = 60000
}; };
httpOptions.RequestHeaders["token"] = token; httpOptions.RequestHeaders["token"] = token;
@@ -483,8 +479,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
RequestContent = imageIdString, RequestContent = imageIdString,
LogErrorResponseBody = true, LogErrorResponseBody = true,
// The data can be large so give it some extra time
TimeoutMs = 60000
}; };
try try
@@ -633,15 +627,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo) ListingsProviderInfo providerInfo)
{ {
// Schedules direct requires that the client support compression and will return a 400 response without it // Schedules direct requires that the client support compression and will return a 400 response without it
options.EnableHttpCompression = true; options.DecompressionMethod = CompressionMethod.Deflate;
// On windows 7 under .net core, this header is not getting added
#if NETSTANDARD2_0
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
}
#endif
try try
{ {
@@ -671,15 +657,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo) ListingsProviderInfo providerInfo)
{ {
// Schedules direct requires that the client support compression and will return a 400 response without it // Schedules direct requires that the client support compression and will return a 400 response without it
options.EnableHttpCompression = true; options.DecompressionMethod = CompressionMethod.Deflate;
// On windows 7 under .net core, this header is not getting added
#if NETSTANDARD2_0
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
}
#endif
try try
{ {
@@ -871,8 +849,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
UserAgent = UserAgent, UserAgent = UserAgent,
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
LogErrorResponseBody = true, LogErrorResponseBody = true,
// The data can be large so give it some extra time
TimeoutMs = 60000
}; };
httpOptions.RequestHeaders["token"] = token; httpOptions.RequestHeaders["token"] = token;
@@ -2,14 +2,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.XmlTv.Classes; using Emby.XmlTv.Classes;
using Emby.XmlTv.Entities; using Emby.XmlTv.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
@@ -27,7 +28,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IZipClient _zipClient; private readonly IZipClient _zipClient;
public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem, IZipClient zipClient) public XmlTvListingsProvider(
IServerConfigurationManager config,
IHttpClient httpClient,
ILogger logger,
IFileSystem fileSystem,
IZipClient zipClient)
{ {
_config = config; _config = config;
_httpClient = httpClient; _httpClient = httpClient;
@@ -52,7 +58,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private async Task<string> GetXml(string path, CancellationToken cancellationToken) private async Task<string> GetXml(string path, CancellationToken cancellationToken)
{ {
_logger.LogInformation("xmltv path: {path}", path); _logger.LogInformation("xmltv path: {Path}", path);
if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{ {
@@ -66,26 +72,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return UnzipIfNeeded(path, cacheFile); return UnzipIfNeeded(path, cacheFile);
} }
_logger.LogInformation("Downloading xmltv listings from {path}", path); _logger.LogInformation("Downloading xmltv listings from {Path}", path);
string tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = path,
Progress = new SimpleProgress<double>(),
DecompressionMethod = CompressionMethod.Gzip,
// It's going to come back gzipped regardless of this value
// So we need to make sure the decompression method is set to gzip
EnableHttpCompression = true,
UserAgent = "Emby/3.0"
}).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
File.Copy(tempFile, cacheFile, true); using (var res = await _httpClient.SendAsync(
new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = path,
DecompressionMethod = CompressionMethod.Gzip,
},
HttpMethod.Get).ConfigureAwait(false))
using (var stream = res.Content)
using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
{
if (res.ContentHeaders.ContentEncoding.Contains("gzip"))
{
using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
{
await gzStream.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
else
{
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
return UnzipIfNeeded(path, cacheFile); return UnzipIfNeeded(path, cacheFile);
} }
@@ -103,7 +116,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error extracting from gz file {file}", file); _logger.LogError(ex, "Error extracting from gz file {File}", file);
} }
try try
@@ -113,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error extracting from zip file {file}", file); _logger.LogError(ex, "Error extracting from zip file {File}", file);
} }
} }
@@ -161,20 +174,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
throw new ArgumentNullException(nameof(channelId)); throw new ArgumentNullException(nameof(channelId));
} }
/* _logger.LogDebug("Getting xmltv programs for channel {Id}", channelId);
if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false))
{
var length = endDateUtc - startDateUtc;
if (length.TotalDays > 1)
{
endDateUtc = startDateUtc.AddDays(1);
}
}*/
_logger.LogDebug("Getting xmltv programs for channel {id}", channelId);
string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Opening XmlTvReader for {path}", path); _logger.LogDebug("Opening XmlTvReader for {Path}", path);
var reader = new XmlTvReader(path, GetLanguage(info)); var reader = new XmlTvReader(path, GetLanguage(info));
return reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken) return reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken)
@@ -267,7 +270,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
// In theory this should never be called because there is always only one lineup // In theory this should never be called because there is always only one lineup
string path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false); string path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false);
_logger.LogDebug("Opening XmlTvReader for {path}", path); _logger.LogDebug("Opening XmlTvReader for {Path}", path);
var reader = new XmlTvReader(path, GetLanguage(info)); var reader = new XmlTvReader(path, GetLanguage(info));
IEnumerable<XmlTvChannel> results = reader.GetChannels(); IEnumerable<XmlTvChannel> results = reader.GetChannels();
@@ -279,7 +282,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
// In theory this should never be called because there is always only one lineup // In theory this should never be called because there is always only one lineup
string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); string path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Opening XmlTvReader for {path}", path); _logger.LogDebug("Opening XmlTvReader for {Path}", path);
var reader = new XmlTvReader(path, GetLanguage(info)); var reader = new XmlTvReader(path, GetLanguage(info));
var results = reader.GetChannels(); var results = reader.GetChannels();
@@ -138,7 +138,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
Url = string.Format("{0}/discover.json", GetApiUrl(info)), Url = string.Format("{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds),
BufferContent = false BufferContent = false
}, "GET").ConfigureAwait(false)) }, "GET").ConfigureAwait(false))
@@ -191,7 +190,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
Url = string.Format("{0}/tuners.html", GetApiUrl(info)), Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false BufferContent = false
})) }))
{ {
@@ -47,13 +47,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
CancellationToken = CancellationToken.None, CancellationToken = CancellationToken.None,
BufferContent = false, BufferContent = false,
// Increase a little bit DecompressionMethod = CompressionMethod.None,
TimeoutMs = 30000,
EnableHttpCompression = false,
LogResponse = true,
LogResponseHeaders = true
}; };
foreach (var header in mediaSource.RequiredHttpHeaders) foreach (var header in mediaSource.RequiredHttpHeaders)
+5
View File
@@ -185,6 +185,11 @@ namespace Jellyfin.Drawing.Skia
public ImageDimensions GetImageSize(string path) public ImageDimensions GetImageSize(string path)
{ {
if (!File.Exists(path))
{
throw new FileNotFoundException("File not found", path);
}
using (var s = new SKFileStream(path)) using (var s = new SKFileStream(path))
using (var codec = SKCodec.Create(s)) using (var codec = SKCodec.Create(s))
{ {
+12
View File
@@ -118,8 +118,20 @@ namespace Jellyfin.Server
SQLitePCL.Batteries_V2.Init(); SQLitePCL.Batteries_V2.Init();
// Increase the max http request limit
// The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
// Disable the "Expect: 100-Continue" header by default
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager.Expect100Continue = false;
// CA5359: Do Not Disable Certificate Validation
#pragma warning disable CA5359
// Allow all https requests // Allow all https requests
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
#pragma warning restore CA5359
var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths); var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths);
+12 -1
View File
@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Api.Movies; using MediaBrowser.Api.Movies;
@@ -828,7 +830,16 @@ namespace MediaBrowser.Api.Library
var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
if (!string.IsNullOrWhiteSpace(filename)) if (!string.IsNullOrWhiteSpace(filename))
{ {
headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\""; // Kestrel doesn't support non-ASCII characters in headers
if (Regex.IsMatch(filename, "[^[:ascii:]]"))
{
// Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
}
else
{
headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\"";
}
} }
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+11 -1
View File
@@ -224,7 +224,17 @@ namespace MediaBrowser.Api.UserLibrary
request.IncludeItemTypes = "Playlist"; request.IncludeItemTypes = "Playlist";
} }
if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)) bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id);
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
{
if (user.Policy.EnabledFolders.Contains(collectionFolder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
{
isInEnabledFolder = true;
}
}
if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder)
{ {
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
return new QueryResult<BaseItem> return new QueryResult<BaseItem>
+17 -37
View File
@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@@ -17,7 +16,7 @@ namespace MediaBrowser.Common.Net
/// <value>The URL.</value> /// <value>The URL.</value>
public string Url { get; set; } public string Url { get; set; }
public CompressionMethod? DecompressionMethod { get; set; } public CompressionMethod DecompressionMethod { get; set; }
/// <summary> /// <summary>
/// Gets or sets the accept header. /// Gets or sets the accept header.
@@ -28,18 +27,13 @@ namespace MediaBrowser.Common.Net
get => GetHeaderValue(HeaderNames.Accept); get => GetHeaderValue(HeaderNames.Accept);
set => RequestHeaders[HeaderNames.Accept] = value; set => RequestHeaders[HeaderNames.Accept] = value;
} }
/// <summary> /// <summary>
/// Gets or sets the cancellation token. /// Gets or sets the cancellation token.
/// </summary> /// </summary>
/// <value>The cancellation token.</value> /// <value>The cancellation token.</value>
public CancellationToken CancellationToken { get; set; } public CancellationToken CancellationToken { get; set; }
/// <summary>
/// Gets or sets the resource pool.
/// </summary>
/// <value>The resource pool.</value>
public SemaphoreSlim ResourcePool { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user agent. /// Gets or sets the user agent.
/// </summary> /// </summary>
@@ -54,13 +48,21 @@ namespace MediaBrowser.Common.Net
/// Gets or sets the referrer. /// Gets or sets the referrer.
/// </summary> /// </summary>
/// <value>The referrer.</value> /// <value>The referrer.</value>
public string Referer { get; set; } public string Referer
{
get => GetHeaderValue(HeaderNames.Referer);
set => RequestHeaders[HeaderNames.Referer] = value;
}
/// <summary> /// <summary>
/// Gets or sets the host. /// Gets or sets the host.
/// </summary> /// </summary>
/// <value>The host.</value> /// <value>The host.</value>
public string Host { get; set; } public string Host
{
get => GetHeaderValue(HeaderNames.Host);
set => RequestHeaders[HeaderNames.Host] = value;
}
/// <summary> /// <summary>
/// Gets or sets the progress. /// Gets or sets the progress.
@@ -68,12 +70,6 @@ namespace MediaBrowser.Common.Net
/// <value>The progress.</value> /// <value>The progress.</value>
public IProgress<double> Progress { get; set; } public IProgress<double> Progress { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [enable HTTP compression].
/// </summary>
/// <value><c>true</c> if [enable HTTP compression]; otherwise, <c>false</c>.</value>
public bool EnableHttpCompression { get; set; }
public Dictionary<string, string> RequestHeaders { get; private set; } public Dictionary<string, string> RequestHeaders { get; private set; }
public string RequestContentType { get; set; } public string RequestContentType { get; set; }
@@ -86,8 +82,6 @@ namespace MediaBrowser.Common.Net
public bool LogRequest { get; set; } public bool LogRequest { get; set; }
public bool LogRequestAsDebug { get; set; } public bool LogRequestAsDebug { get; set; }
public bool LogErrors { get; set; } public bool LogErrors { get; set; }
public bool LogResponse { get; set; }
public bool LogResponseHeaders { get; set; }
public bool LogErrorResponseBody { get; set; } public bool LogErrorResponseBody { get; set; }
public bool EnableKeepAlive { get; set; } public bool EnableKeepAlive { get; set; }
@@ -95,12 +89,8 @@ namespace MediaBrowser.Common.Net
public CacheMode CacheMode { get; set; } public CacheMode CacheMode { get; set; }
public TimeSpan CacheLength { get; set; } public TimeSpan CacheLength { get; set; }
public int TimeoutMs { get; set; }
public bool EnableDefaultUserAgent { get; set; } public bool EnableDefaultUserAgent { get; set; }
public bool AppendCharsetToMimeType { get; set; }
public string DownloadFilePath { get; set; }
private string GetHeaderValue(string name) private string GetHeaderValue(string name)
{ {
RequestHeaders.TryGetValue(name, out var value); RequestHeaders.TryGetValue(name, out var value);
@@ -113,24 +103,12 @@ namespace MediaBrowser.Common.Net
/// </summary> /// </summary>
public HttpRequestOptions() public HttpRequestOptions()
{ {
EnableHttpCompression = true;
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
LogRequest = true; LogRequest = true;
LogErrors = true; LogErrors = true;
CacheMode = CacheMode.None; CacheMode = CacheMode.None;
DecompressionMethod = CompressionMethod.Deflate;
TimeoutMs = 20000;
}
public void SetPostData(IDictionary<string, string> values)
{
var strings = values.Keys.Select(key => string.Format("{0}={1}", key, values[key]));
var postContent = string.Join("&", strings.ToArray());
RequestContent = postContent;
RequestContentType = "application/x-www-form-urlencoded";
} }
} }
@@ -140,9 +118,11 @@ namespace MediaBrowser.Common.Net
Unconditional = 1 Unconditional = 1
} }
[Flags]
public enum CompressionMethod public enum CompressionMethod
{ {
Deflate, None = 0b00000001,
Gzip Deflate = 0b00000010,
Gzip = 0b00000100
} }
} }
+15 -13
View File
@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Http.Headers;
namespace MediaBrowser.Common.Net namespace MediaBrowser.Common.Net
{ {
@@ -50,26 +50,28 @@ namespace MediaBrowser.Common.Net
/// Gets or sets the headers. /// Gets or sets the headers.
/// </summary> /// </summary>
/// <value>The headers.</value> /// <value>The headers.</value>
public Dictionary<string, string> Headers { get; set; } public HttpResponseHeaders Headers { get; set; }
private readonly IDisposable _disposable; /// <summary>
/// Gets or sets the content headers.
/// </summary>
/// <value>The content headers.</value>
public HttpContentHeaders ContentHeaders { get; set; }
public HttpResponseInfo(IDisposable disposable)
{
_disposable = disposable;
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public HttpResponseInfo() public HttpResponseInfo()
{ {
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader)
{
Headers = headers;
ContentHeaders = contentHeader;
} }
public void Dispose() public void Dispose()
{ {
if (_disposable != null) // Only IDisposable for backwards compatibility
{
_disposable.Dispose();
}
} }
} }
} }
+11
View File
@@ -1,5 +1,6 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Net.Http;
namespace MediaBrowser.Common.Net namespace MediaBrowser.Common.Net
{ {
@@ -23,6 +24,8 @@ namespace MediaBrowser.Common.Net
Task<Stream> Get(HttpRequestOptions options); Task<Stream> Get(HttpRequestOptions options);
/// <summary> /// <summary>
/// Warning: Deprecated function,
/// use 'Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead
/// Sends the asynchronous. /// Sends the asynchronous.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
@@ -30,6 +33,14 @@ namespace MediaBrowser.Common.Net
/// <returns>Task{HttpResponseInfo}.</returns> /// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod); Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod);
/// <summary>
/// Sends the asynchronous.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);
/// <summary> /// <summary>
/// Posts the specified options. /// Posts the specified options.
/// </summary> /// </summary>
@@ -11,6 +11,9 @@ namespace MediaBrowser.Controller.Authentication
Task<ProviderAuthenticationResult> Authenticate(string username, string password); Task<ProviderAuthenticationResult> Authenticate(string username, string password);
Task<bool> HasPassword(User user); Task<bool> HasPassword(User user);
Task ChangePassword(User user, string newPassword); Task ChangePassword(User user, string newPassword);
void ChangeEasyPassword(User user, string newPassword, string newPasswordHash);
string GetPasswordHash(User user);
string GetEasyPasswordHash(User user);
} }
public interface IRequiresResolvedUser public interface IRequiresResolvedUser
+62 -29
View File
@@ -78,10 +78,25 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// The trailer folder name /// The trailer folder name
/// </summary> /// </summary>
public static string TrailerFolderName = "trailers"; public const string TrailerFolderName = "trailers";
public static string ThemeSongsFolderName = "theme-music"; public const string ThemeSongsFolderName = "theme-music";
public static string ThemeSongFilename = "theme"; public const string ThemeSongFilename = "theme";
public static string ThemeVideosFolderName = "backdrops"; public const string ThemeVideosFolderName = "backdrops";
public const string ExtrasFolderName = "extras";
public const string BehindTheScenesFolderName = "behind the scenes";
public const string DeletedScenesFolderName = "deleted scenes";
public const string InterviewFolderName = "interviews";
public const string SceneFolderName = "scenes";
public const string SampleFolderName = "samples";
public static readonly string[] AllExtrasTypesFolderNames = {
ExtrasFolderName,
BehindTheScenesFolderName,
DeletedScenesFolderName,
InterviewFolderName,
SceneFolderName,
SampleFolderName
};
[IgnoreDataMember] [IgnoreDataMember]
public Guid[] ThemeSongIds { get; set; } public Guid[] ThemeSongIds { get; set; }
@@ -1276,16 +1291,15 @@ namespace MediaBrowser.Controller.Entities
.Select(item => .Select(item =>
{ {
// Try to retrieve it from the db. If we don't find it, use the resolved version // Try to retrieve it from the db. If we don't find it, use the resolved version
var dbItem = LibraryManager.GetItemById(item.Id) as Video;
if (dbItem != null) if (LibraryManager.GetItemById(item.Id) is Video dbItem)
{ {
item = dbItem; item = dbItem;
} }
else else
{ {
// item is new // item is new
item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; item.ExtraType = Model.Entities.ExtraType.ThemeVideo;
} }
return item; return item;
@@ -1296,33 +1310,38 @@ namespace MediaBrowser.Controller.Entities
protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{ {
var files = fileSystemChildren.Where(i => i.IsDirectory) var extras = new List<Video>();
.SelectMany(i => FileSystem.GetFiles(i.FullName));
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) var folders = fileSystemChildren.Where(i => i.IsDirectory).ToArray();
.OfType<Video>() foreach (var extraFolderName in AllExtrasTypesFolderNames)
.Select(item => {
{ var files = folders
// Try to retrieve it from the db. If we don't find it, use the resolved version .Where(i => string.Equals(i.Name, extraFolderName, StringComparison.OrdinalIgnoreCase))
var dbItem = LibraryManager.GetItemById(item.Id) as Video; .SelectMany(i => FileSystem.GetFiles(i.FullName));
if (dbItem != null) extras.AddRange(LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Video>()
.Select(item =>
{ {
item = dbItem; // Try to retrieve it from the db. If we don't find it, use the resolved version
} if (LibraryManager.GetItemById(item.Id) is Video dbItem)
else {
{ item = dbItem;
// item is new }
item.ExtraType = MediaBrowser.Model.Entities.ExtraType.Clip;
}
return item; // Use some hackery to get the extra type based on foldername
Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
item.ExtraType = extraType;
// Sort them so that the list can be easily compared for changes return item;
}).OrderBy(i => i.Path).ToArray();
// Sort them so that the list can be easily compared for changes
}).OrderBy(i => i.Path));
}
return extras.ToArray();
} }
public Task RefreshMetadata(CancellationToken cancellationToken) public Task RefreshMetadata(CancellationToken cancellationToken)
{ {
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken); return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
@@ -1481,7 +1500,13 @@ namespace MediaBrowser.Controller.Entities
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{ {
var newExtras = LoadExtras(fileSystemChildren, options.DirectoryService).Concat(LoadThemeVideos(fileSystemChildren, options.DirectoryService)).Concat(LoadThemeSongs(fileSystemChildren, options.DirectoryService)); var extras = LoadExtras(fileSystemChildren, options.DirectoryService);
var themeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
var themeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
var newExtras = new BaseItem[extras.Length + themeVideos.Length + themeSongs.Length];
extras.CopyTo(newExtras, 0);
themeVideos.CopyTo(newExtras, extras.Length);
themeSongs.CopyTo(newExtras, extras.Length + themeVideos.Length);
var newExtraIds = newExtras.Select(i => i.Id).ToArray(); var newExtraIds = newExtras.Select(i => i.Id).ToArray();
@@ -1493,7 +1518,15 @@ namespace MediaBrowser.Controller.Entities
var tasks = newExtras.Select(i => var tasks = newExtras.Select(i =>
{ {
return RefreshMetadataForOwnedItem(i, true, new MetadataRefreshOptions(options), cancellationToken); var subOptions = new MetadataRefreshOptions(options);
if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
{
i.OwnerId = ownerId;
i.ParentId = Guid.Empty;
subOptions.ForceSave = true;
}
return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
}); });
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -24,24 +24,28 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{ {
_cache = memoryCache; _cache = memoryCache;
_tvDbClient = new TvDbClient(); _tvDbClient = new TvDbClient();
_tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey);
_tokenCreatedAt = DateTime.Now;
} }
public TvDbClient TvDbClient private TvDbClient TvDbClient
{ {
get get
{ {
if (string.IsNullOrEmpty(_tvDbClient.Authentication.Token))
{
_tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult();
_tokenCreatedAt = DateTime.Now;
}
// Refresh if necessary // Refresh if necessary
if (_tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20))) if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20)))
{ {
try try
{ {
_tvDbClient.Authentication.RefreshTokenAsync(); _tvDbClient.Authentication.RefreshTokenAsync().GetAwaiter().GetResult();
} }
catch catch
{ {
_tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey); _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult();
} }
_tokenCreatedAt = DateTime.Now; _tokenCreatedAt = DateTime.Now;
+1 -4
View File
@@ -57,10 +57,9 @@ namespace Mono.Nat.Upnp
req.Url = ss; req.Url = ss;
req.EnableKeepAlive = false; req.EnableKeepAlive = false;
req.RequestContentType = "text/xml"; req.RequestContentType = "text/xml";
req.AppendCharsetToMimeType = true;
req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\"");
string bodyString = "<s:Envelope " req.RequestContent = "<s:Envelope "
+ "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<s:Body>"
@@ -70,8 +69,6 @@ namespace Mono.Nat.Upnp
+ "</u:" + upnpMethod + ">" + "</u:" + upnpMethod + ">"
+ "</s:Body>" + "</s:Body>"
+ "</s:Envelope>\r\n\r\n"; + "</s:Envelope>\r\n\r\n";
req.RequestContentBytes = System.Text.Encoding.UTF8.GetBytes(bodyString);
return req; return req;
} }
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.3.2")] [assembly: AssemblyVersion("10.3.7")]
[assembly: AssemblyFileVersion("10.3.2")] [assembly: AssemblyFileVersion("10.3.7")]
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
# We just wrap `build` so this is really it # We just wrap `build` so this is really it
name: "jellyfin" name: "jellyfin"
version: "10.3.2" version: "10.3.7"
packages: packages:
- debian-package-x64 - debian-package-x64
- debian-package-armhf - debian-package-armhf
@@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm mkdir -p ${ARTIFACT_DIR}/rpm
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
-3
View File
@@ -72,9 +72,6 @@ fi
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the RPMs and copy out to ${package_temporary_dir} # Build the RPMs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the RPMs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" \
|| sudo chown -R "${current_user}" "${package_temporary_dir}"
# Move the RPMs to the output directory # Move the RPMs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/rpm/* "${output_dir}" mv "${package_temporary_dir}"/rpm/* "${output_dir}"
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarm64
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/ mv /jellyfin_* ${ARTIFACT_DIR}/deb/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -30,13 +30,12 @@ case $ARCH in
;; ;;
esac esac
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir} # Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory # Move the DEBs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}"
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/ mv /jellyfin_* ${ARTIFACT_DIR}/deb/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -30,13 +30,12 @@ case $ARCH in
;; ;;
esac esac
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir} # Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory # Move the DEBs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}"
@@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/ mv /jellyfin_* ${ARTIFACT_DIR}/deb/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -19,13 +19,12 @@ else
docker_sudo="" docker_sudo=""
fi fi
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the DEBs and copy out to ${package_temporary_dir} # Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory # Move the DEBs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}"
@@ -1,20 +1,36 @@
#!/bin/bash #!/bin/bash
NAME=jellyfin # restart.sh - Jellyfin server restart script
# Part of the Jellyfin project (https://github.com/jellyfin)
#
# This script restarts the Jellyfin daemon on Linux when using
# the Restart button on the admin dashboard. It supports the
# systemctl, service, and traditional /etc/init.d (sysv) restart
# methods, chosen automatically by which one is found first (in
# that order).
#
# This script is used by the Debian/Ubuntu/Fedora/CentOS packages.
restart_cmds=( get_service_command() {
"systemctl restart ${NAME}" for command in systemctl service; do
"service ${NAME} restart" if which $command &>/dev/null; then
"/etc/init.d/${NAME} restart" echo $command && return
"s6-svc -t /var/run/s6/services/${NAME}" fi
) done
echo "sysv"
}
for restart_cmd in "${restart_cmds[@]}"; do cmd="$( get_service_command )"
cmd=$(echo "$restart_cmd" | awk '{print $1}') echo "Detected service control platform '$cmd'; using it to restart Jellyfin..."
cmd_loc=$(command -v ${cmd}) case $cmd in
if [[ -n "$cmd_loc" ]]; then 'systemctl')
restart_cmd=$(echo "$restart_cmd" | sed -e "s%${cmd}%${cmd_loc}%") echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now
echo "sleep 2; sudo $restart_cmd > /dev/null 2>&1" | at now > /dev/null 2>&1 ;;
exit 0 'service')
fi echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now
done ;;
'sysv')
echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now
;;
esac
exit 0
@@ -1,3 +1,33 @@
jellyfin (10.3.7-1) unstable; urgency=medium
* New upstream version 10.3.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.7
-- Jellyfin Packaging Team <packaging@jellyfin.org> Wed, 24 Jul 2019 10:48:28 -0400
jellyfin (10.3.6-1) unstable; urgency=medium
* New upstream version 10.3.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.6
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 06 Jul 2019 13:34:19 -0400
jellyfin (10.3.5-1) unstable; urgency=medium
* New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 09 Jun 2019 21:47:35 -0400
jellyfin (10.3.4-1) unstable; urgency=medium
* New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 06 Jun 2019 22:45:31 -0400
jellyfin (10.3.3-1) unstable; urgency=medium
* New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 17 May 2019 23:12:08 -0400
jellyfin (10.3.2-1) unstable; urgency=medium jellyfin (10.3.2-1) unstable; urgency=medium
* New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2 * New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2
@@ -18,3 +18,4 @@ rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm mkdir -p ${ARTIFACT_DIR}/rpm
mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -23,13 +23,12 @@ fi
./create_tarball.sh ./create_tarball.sh
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the RPMs and copy out to ${package_temporary_dir} # Build the RPMs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the RPMs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" \
|| sudo chown -R "${current_user}" "${package_temporary_dir}"
# Move the RPMs to the output directory # Move the RPMs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/rpm/* "${output_dir}" mv "${package_temporary_dir}"/rpm/* "${output_dir}"
@@ -7,7 +7,7 @@
%endif %endif
Name: jellyfin Name: jellyfin
Version: 10.3.2 Version: 10.3.7
Release: 1%{?dist} Release: 1%{?dist}
Summary: The Free Software Media Browser Summary: The Free Software Media Browser
License: GPLv2 License: GPLv2
@@ -140,6 +140,16 @@ fi
%systemd_postun_with_restart jellyfin.service %systemd_postun_with_restart jellyfin.service
%changelog %changelog
* Wed Jul 24 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.7
* Sat Jul 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.6
* Sun Jun 09 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.5
* Thu Jun 06 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.4
* Fri May 17 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.3
* Tue Apr 30 2019 Jellyfin Packaging Team <packaging@jellyfin.org> * Tue Apr 30 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2 - New upstream version 10.3.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.3.2
* Sat Apr 20 2019 Jellyfin Packaging Team <packaging@jellyfin.org> * Sat Apr 20 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
@@ -1,6 +1,36 @@
#!/bin/sh #!/bin/bash
NAME=jellyfin # restart.sh - Jellyfin server restart script
restart_cmd="/usr/bin/systemctl restart ${NAME}" # Part of the Jellyfin project (https://github.com/jellyfin)
echo "sleep 2; sudo $restart_cmd > /dev/null 2>&1" | at now > /dev/null 2>&1 #
# This script restarts the Jellyfin daemon on Linux when using
# the Restart button on the admin dashboard. It supports the
# systemctl, service, and traditional /etc/init.d (sysv) restart
# methods, chosen automatically by which one is found first (in
# that order).
#
# This script is used by the Debian/Ubuntu/Fedora/CentOS packages.
get_service_command() {
for command in systemctl service; do
if which $command &>/dev/null; then
echo $command && return
fi
done
echo "sysv"
}
cmd="$( get_service_command )"
echo "Detected service control platform '$cmd'; using it to restart Jellyfin..."
case $cmd in
'systemctl')
echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now
;;
'service')
echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now
;;
'sysv')
echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now
;;
esac
exit 0 exit 0
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarm64
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/ mv /jellyfin_* ${ARTIFACT_DIR}/deb/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -30,13 +30,12 @@ case $ARCH in
;; ;;
esac esac
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir} # Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory # Move the DEBs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}"
@@ -18,3 +18,4 @@ dpkg-buildpackage -us -uc -aarmhf
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/ mv /jellyfin_* ${ARTIFACT_DIR}/deb/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -30,13 +30,12 @@ case $ARCH in
;; ;;
esac esac
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE} ${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
# Build the DEBs and copy out to ${package_temporary_dir} # Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory # Move the DEBs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}"
@@ -17,3 +17,4 @@ dpkg-buildpackage -us -uc
# Move the artifacts out # Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/deb mkdir -p ${ARTIFACT_DIR}/deb
mv /jellyfin_* ${ARTIFACT_DIR}/deb/ mv /jellyfin_* ${ARTIFACT_DIR}/deb/
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+2 -3
View File
@@ -19,13 +19,12 @@ else
docker_sudo="" docker_sudo=""
fi fi
# Prepare temporary package dir
mkdir -p "${package_temporary_dir}"
# Set up the build environment Docker image # Set up the build environment Docker image
${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile ${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
# Build the DEBs and copy out to ${package_temporary_dir} # Build the DEBs and copy out to ${package_temporary_dir}
${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
# Correct ownership on the DEBs (as current user, then as root if that fails)
chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
|| sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
# Move the DEBs to the output directory # Move the DEBs to the output directory
mkdir -p "${output_dir}" mkdir -p "${output_dir}"
mv "${package_temporary_dir}"/deb/* "${output_dir}" mv "${package_temporary_dir}"/deb/* "${output_dir}"