Merge remote-tracking branch 'upstream/master' into dynamic-cors
This commit is contained in:
commit
8a08111adc
|
@ -64,28 +64,28 @@ jobs:
|
||||||
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
|
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
|
||||||
zipAfterPublish: false
|
zipAfterPublish: false
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@1
|
||||||
displayName: 'Publish Artifact Naming'
|
displayName: 'Publish Artifact Naming'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||||
artifactName: 'Jellyfin.Naming'
|
artifactName: 'Jellyfin.Naming'
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@1
|
||||||
displayName: 'Publish Artifact Controller'
|
displayName: 'Publish Artifact Controller'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||||
artifactName: 'Jellyfin.Controller'
|
artifactName: 'Jellyfin.Controller'
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@1
|
||||||
displayName: 'Publish Artifact Model'
|
displayName: 'Publish Artifact Model'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||||
artifactName: 'Jellyfin.Model'
|
artifactName: 'Jellyfin.Model'
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@1
|
||||||
displayName: 'Publish Artifact Common'
|
displayName: 'Publish Artifact Common'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -74,7 +74,6 @@ jobs:
|
||||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
||||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
displayName: 'Run ReportGenerator'
|
displayName: 'Run ReportGenerator'
|
||||||
enabled: false
|
|
||||||
inputs:
|
inputs:
|
||||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||||
|
@ -84,10 +83,16 @@ jobs:
|
||||||
- task: PublishCodeCoverageResults@1
|
- task: PublishCodeCoverageResults@1
|
||||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
displayName: 'Publish Code Coverage'
|
displayName: 'Publish Code Coverage'
|
||||||
enabled: false
|
|
||||||
inputs:
|
inputs:
|
||||||
codeCoverageTool: "cobertura"
|
codeCoverageTool: "cobertura"
|
||||||
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||||
pathToSources: $(Build.SourcesDirectory)
|
pathToSources: $(Build.SourcesDirectory)
|
||||||
failIfCoverageEmpty: true
|
failIfCoverageEmpty: true
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish OpenAPI Artifact'
|
||||||
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||||
|
inputs:
|
||||||
|
targetPath: "tests/Jellyfin.Api.Tests/bin/Release/netcoreapp3.1/openapi.json"
|
||||||
|
artifactName: 'OpenAPI Spec'
|
||||||
|
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
|
@ -17,7 +17,7 @@
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"test",
|
"test",
|
||||||
"${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
|
"${workspaceFolder}/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj"
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
- [Larvitar](https://github.com/Larvitar)
|
- [Larvitar](https://github.com/Larvitar)
|
||||||
- [LeoVerto](https://github.com/LeoVerto)
|
- [LeoVerto](https://github.com/LeoVerto)
|
||||||
- [Liggy](https://github.com/Liggy)
|
- [Liggy](https://github.com/Liggy)
|
||||||
|
- [lmaonator](https://github.com/lmaonator)
|
||||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
||||||
- [loli10K](https://github.com/loli10K)
|
- [loli10K](https://github.com/loli10K)
|
||||||
- [lostmypillow](https://github.com/lostmypillow)
|
- [lostmypillow](https://github.com/lostmypillow)
|
||||||
|
@ -78,6 +79,7 @@
|
||||||
- [nvllsvm](https://github.com/nvllsvm)
|
- [nvllsvm](https://github.com/nvllsvm)
|
||||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||||
- [oddstr13](https://github.com/oddstr13)
|
- [oddstr13](https://github.com/oddstr13)
|
||||||
|
- [orryverducci](https://github.com/orryverducci)
|
||||||
- [petermcneil](https://github.com/petermcneil)
|
- [petermcneil](https://github.com/petermcneil)
|
||||||
- [Phlogi](https://github.com/Phlogi)
|
- [Phlogi](https://github.com/Phlogi)
|
||||||
- [pjeanjean](https://github.com/pjeanjean)
|
- [pjeanjean](https://github.com/pjeanjean)
|
||||||
|
|
|
@ -14,7 +14,7 @@ COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||||
|
|
|
@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||||
FROM arm64v8/debian:buster-slim
|
FROM arm64v8/debian:buster-slim
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -18,8 +18,8 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
IDlnaManager dlna,
|
IDlnaManager dlna,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ILogger<ConnectionManagerService> logger,
|
ILogger<ConnectionManagerService> logger,
|
||||||
IHttpClient httpClient)
|
IHttpClientFactory httpClientFactory)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
_dlna = dlna;
|
_dlna = dlna;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -41,7 +41,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
ILogger<ContentDirectoryService> logger,
|
ILogger<ContentDirectoryService> logger,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClient,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IUserViewManager userViewManager,
|
IUserViewManager userViewManager,
|
||||||
|
|
|
@ -1363,7 +1363,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
|
||||||
|
|
||||||
return new ServerItem(_libraryManager.GetUserRootFolder());
|
return new ServerItem(_libraryManager.GetUserRootFolder());
|
||||||
}
|
}
|
||||||
|
|
|
@ -948,7 +948,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error adding xml value: {value}", name);
|
_logger.LogError(ex, "Error adding xml value: {Value}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -960,7 +960,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error adding xml value: {value}", value);
|
_logger.LogError(ex, "Error adding xml value: {Value}", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
@ -20,13 +21,13 @@ namespace Emby.Dlna.Eventing
|
||||||
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
|
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
public DlnaEventManager(ILogger logger, IHttpClient httpClient)
|
public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,24 +168,17 @@ namespace Emby.Dlna.Eventing
|
||||||
|
|
||||||
builder.Append("</e:propertyset>");
|
builder.Append("</e:propertyset>");
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
|
||||||
{
|
options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
|
||||||
RequestContent = builder.ToString(),
|
options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
|
||||||
RequestContentType = "text/xml",
|
options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
|
||||||
Url = subscription.CallbackUrl,
|
options.Headers.TryAddWithoutValidation("SID", subscription.Id);
|
||||||
BufferContent = false
|
options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture));
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders.Add("NT", subscription.NotificationType);
|
|
||||||
options.RequestHeaders.Add("NTS", "upnp:propchange");
|
|
||||||
options.RequestHeaders.Add("SID", subscription.Id);
|
|
||||||
options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -36,7 +37,7 @@ namespace Emby.Dlna.Main
|
||||||
private readonly ILogger<DlnaEntryPoint> _logger;
|
private readonly ILogger<DlnaEntryPoint> _logger;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IDlnaManager _dlnaManager;
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
@ -61,7 +62,7 @@ namespace Emby.Dlna.Main
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IDlnaManager dlnaManager,
|
IDlnaManager dlnaManager,
|
||||||
|
@ -79,7 +80,7 @@ namespace Emby.Dlna.Main
|
||||||
_config = config;
|
_config = config;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
|
@ -101,7 +102,7 @@ namespace Emby.Dlna.Main
|
||||||
config,
|
config,
|
||||||
userManager,
|
userManager,
|
||||||
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
|
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
|
||||||
httpClient,
|
httpClientFactory,
|
||||||
localizationManager,
|
localizationManager,
|
||||||
mediaSourceManager,
|
mediaSourceManager,
|
||||||
userViewManager,
|
userViewManager,
|
||||||
|
@ -112,11 +113,11 @@ namespace Emby.Dlna.Main
|
||||||
dlnaManager,
|
dlnaManager,
|
||||||
config,
|
config,
|
||||||
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
|
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
|
||||||
httpClient);
|
httpClientFactory);
|
||||||
|
|
||||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
|
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
|
||||||
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
|
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
|
||||||
httpClient,
|
httpClientFactory,
|
||||||
config);
|
config);
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
@ -364,7 +365,7 @@ namespace Emby.Dlna.Main
|
||||||
_appHost,
|
_appHost,
|
||||||
_imageProcessor,
|
_imageProcessor,
|
||||||
_deviceDiscovery,
|
_deviceDiscovery,
|
||||||
_httpClient,
|
_httpClientFactory,
|
||||||
_config,
|
_config,
|
||||||
_userDataManager,
|
_userDataManager,
|
||||||
_localization,
|
_localization,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -14,9 +14,9 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
|
|
||||||
public MediaReceiverRegistrarService(
|
public MediaReceiverRegistrarService(
|
||||||
ILogger<MediaReceiverRegistrarService> logger,
|
ILogger<MediaReceiverRegistrarService> logger,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IServerConfigurationManager config)
|
IServerConfigurationManager config)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -21,7 +22,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
@ -34,10 +35,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
private int _connectFailureCount;
|
private int _connectFailureCount;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger)
|
public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger)
|
||||||
{
|
{
|
||||||
Properties = deviceProperties;
|
Properties = deviceProperties;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +237,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
_logger.LogDebug("Setting mute");
|
_logger.LogDebug("Setting mute");
|
||||||
var value = mute ? 1 : 0;
|
var value = mute ? 1 : 0;
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
IsMuted = mute;
|
IsMuted = mute;
|
||||||
|
@ -271,7 +272,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
// Remote control will perform better
|
// Remote control will perform better
|
||||||
Volume = value;
|
Volume = value;
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +293,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
}
|
}
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
|
@ -326,7 +327,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await Task.Delay(50).ConfigureAwait(false);
|
await Task.Delay(50).ConfigureAwait(false);
|
||||||
|
@ -368,7 +369,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SsdpHttpClient(_httpClient).SendCommandAsync(
|
return new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -397,7 +398,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
|
@ -415,7 +416,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
TransportState = TransportState.Paused;
|
TransportState = TransportState.Paused;
|
||||||
|
@ -542,7 +543,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -592,7 +593,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -625,7 +626,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -667,7 +668,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -734,7 +735,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -912,7 +913,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||||
|
|
||||||
var httpClient = new SsdpHttpClient(_httpClient);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
|
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -940,7 +941,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||||
|
|
||||||
var httpClient = new SsdpHttpClient(_httpClient);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -969,9 +970,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
return baseUrl + url;
|
return baseUrl + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
|
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var ssdpHttpClient = new SsdpHttpClient(httpClient);
|
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
||||||
|
|
||||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -1079,7 +1080,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Device(deviceProperties, httpClient, logger);
|
return new Device(deviceProperties, httpClientFactory, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DeviceIcon CreateIcon(XElement element)
|
private static DeviceIcon CreateIcon(XElement element)
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
|
@ -33,7 +34,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
private readonly IDlnaManager _dlnaManager;
|
private readonly IDlnaManager _dlnaManager;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
@ -46,7 +47,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
|
@ -56,7 +57,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_config = config;
|
_config = config;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
@ -174,7 +175,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (controller == null)
|
if (controller == null)
|
||||||
{
|
{
|
||||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _logger, cancellationToken).ConfigureAwait(false);
|
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
string deviceName = device.Properties.Name;
|
string deviceName = device.Properties.Name;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -20,11 +22,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
public SsdpHttpClient(IHttpClient httpClient)
|
public SsdpHttpClient(IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XDocument> SendCommandAsync(
|
public async Task<XDocument> SendCommandAsync(
|
||||||
|
@ -36,21 +38,19 @@ namespace Emby.Dlna.PlayTo
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
||||||
using (var response = await PostSoapDataAsync(
|
using var response = await PostSoapDataAsync(
|
||||||
url,
|
url,
|
||||||
$"\"{service.ServiceType}#{command}\"",
|
$"\"{service.ServiceType}#{command}\"",
|
||||||
postData,
|
postData,
|
||||||
header,
|
header,
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false))
|
.ConfigureAwait(false);
|
||||||
using (var stream = response.Content)
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||||
{
|
|
||||||
return XDocument.Parse(
|
return XDocument.Parse(
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
LoadOptions.PreserveWhitespace);
|
LoadOptions.PreserveWhitespace);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
||||||
{
|
{
|
||||||
|
@ -76,49 +76,32 @@ namespace Emby.Dlna.PlayTo
|
||||||
int eventport,
|
int eventport,
|
||||||
int timeOut = 3600)
|
int timeOut = 3600)
|
||||||
{
|
{
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
|
||||||
{
|
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||||
Url = url,
|
options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture));
|
||||||
UserAgent = USERAGENT,
|
options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">");
|
||||||
LogErrorResponseBody = true,
|
options.Headers.TryAddWithoutValidation("NT", "upnp:event");
|
||||||
BufferContent = false,
|
options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture));
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
|
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
|
||||||
options.RequestHeaders["NT"] = "upnp:event";
|
.ConfigureAwait(false);
|
||||||
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
|
|
||||||
|
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
|
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
{
|
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||||
Url = url,
|
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||||
UserAgent = USERAGENT,
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
LogErrorResponseBody = true,
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
BufferContent = false,
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||||
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
using (var stream = response.Content)
|
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
|
||||||
{
|
|
||||||
return XDocument.Parse(
|
return XDocument.Parse(
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
LoadOptions.PreserveWhitespace);
|
LoadOptions.PreserveWhitespace);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private Task<HttpResponseInfo> PostSoapDataAsync(
|
private Task<HttpResponseMessage> PostSoapDataAsync(
|
||||||
string url,
|
string url,
|
||||||
string soapAction,
|
string soapAction,
|
||||||
string postData,
|
string postData,
|
||||||
|
@ -130,29 +113,20 @@ namespace Emby.Dlna.PlayTo
|
||||||
soapAction = $"\"{soapAction}\"";
|
soapAction = $"\"{soapAction}\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
{
|
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||||
Url = url,
|
options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
|
||||||
UserAgent = USERAGENT,
|
options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
|
||||||
LogErrorResponseBody = true,
|
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||||
BufferContent = false,
|
|
||||||
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["SOAPAction"] = soapAction;
|
|
||||||
options.RequestHeaders["Pragma"] = "no-cache";
|
|
||||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(header))
|
if (!string.IsNullOrEmpty(header))
|
||||||
{
|
{
|
||||||
options.RequestHeaders["contentFeatures.dlna.org"] = header;
|
options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.RequestContentType = "text/xml";
|
options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
|
||||||
options.RequestContent = postData;
|
|
||||||
|
|
||||||
return _httpClient.Post(options);
|
return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,21 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using Emby.Dlna.Eventing;
|
using Emby.Dlna.Eventing;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.Service
|
namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public class BaseService : IDlnaEventManager
|
public class BaseService : IDlnaEventManager
|
||||||
{
|
{
|
||||||
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
|
protected BaseService(ILogger<BaseService> logger, IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
HttpClient = httpClient;
|
EventManager = new DlnaEventManager(logger, httpClientFactory);
|
||||||
|
|
||||||
EventManager = new DlnaEventManager(logger, HttpClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IDlnaEventManager EventManager { get; }
|
protected IDlnaEventManager EventManager { get; }
|
||||||
|
|
||||||
protected IHttpClient HttpClient { get; }
|
|
||||||
|
|
||||||
protected ILogger Logger { get; }
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
||||||
|
|
|
@ -136,8 +136,8 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
|
|
|
@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error loading configuration file: {path}", path);
|
Logger.LogError(ex, "Error loading configuration file: {Path}", path);
|
||||||
|
|
||||||
return Activator.CreateInstance(configurationType);
|
return Activator.CreateInstance(configurationType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ using Jellyfin.Api.Helpers;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Events;
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Common.Json;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
|
@ -122,8 +123,8 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
private IMediaEncoder _mediaEncoder;
|
private IMediaEncoder _mediaEncoder;
|
||||||
private ISessionManager _sessionManager;
|
private ISessionManager _sessionManager;
|
||||||
|
private IHttpClientFactory _httpClientFactory;
|
||||||
private IWebSocketManager _webSocketManager;
|
private IWebSocketManager _webSocketManager;
|
||||||
private IHttpClient _httpClient;
|
|
||||||
|
|
||||||
private string[] _urlPrefixes;
|
private string[] _urlPrefixes;
|
||||||
|
|
||||||
|
@ -279,6 +280,10 @@ namespace Emby.Server.Implementations
|
||||||
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
||||||
};
|
};
|
||||||
Certificate = GetCertificate(CertificateInfo);
|
Certificate = GetCertificate(CertificateInfo);
|
||||||
|
|
||||||
|
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||||
|
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||||
|
ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExpandVirtualPath(string path)
|
public string ExpandVirtualPath(string path)
|
||||||
|
@ -308,16 +313,16 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
|
public Version ApplicationVersion { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
public string ApplicationVersionString { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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(' ', '-') + "/" + ApplicationVersionString;
|
public string ApplicationUserAgent { get; }
|
||||||
|
|
||||||
/// <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.
|
||||||
|
@ -522,8 +527,6 @@ namespace Emby.Server.Implementations
|
||||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
ServiceCollection.AddSingleton(_fileSystemManager);
|
||||||
ServiceCollection.AddSingleton<TvdbClientManager>();
|
ServiceCollection.AddSingleton<TvdbClientManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
|
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(_networkManager);
|
ServiceCollection.AddSingleton(_networkManager);
|
||||||
|
|
||||||
ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
|
ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
|
||||||
|
@ -650,8 +653,8 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
_mediaEncoder = Resolve<IMediaEncoder>();
|
_mediaEncoder = Resolve<IMediaEncoder>();
|
||||||
_sessionManager = Resolve<ISessionManager>();
|
_sessionManager = Resolve<ISessionManager>();
|
||||||
|
_httpClientFactory = Resolve<IHttpClientFactory>();
|
||||||
_webSocketManager = Resolve<IWebSocketManager>();
|
_webSocketManager = Resolve<IWebSocketManager>();
|
||||||
_httpClient = Resolve<IHttpClient>();
|
|
||||||
|
|
||||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||||
|
|
||||||
|
@ -1296,26 +1299,18 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.SendAsync(
|
using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
|
||||||
new HttpRequestOptions
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
Url = apiUrl,
|
|
||||||
LogErrorResponseBody = false,
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
BufferContent = false,
|
var result = await System.Text.Json.JsonSerializer.DeserializeAsync<string>(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false);
|
||||||
CancellationToken = cancellationToken
|
|
||||||
}, HttpMethod.Post).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
using (var reader = new StreamReader(response.Content))
|
|
||||||
{
|
|
||||||
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);
|
||||||
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
|
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled");
|
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled");
|
||||||
|
@ -1401,7 +1396,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
foreach (var assembly in assemblies)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Found API endpoints in plugin {name}", assembly.FullName);
|
Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName);
|
||||||
yield return assembly;
|
yield return assembly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -890,7 +890,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error writing to channel cache file: {path}", path);
|
_logger.LogError(ex, "Error writing to channel cache file: {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
|
// Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
|
||||||
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {itemName}", item.Name);
|
_logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,10 +32,10 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.7" />
|
||||||
<PackageReference Include="Mono.Nat" Version="2.0.2" />
|
<PackageReference Include="Mono.Nat" Version="2.0.2" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
|
||||||
|
|
|
@ -1,335 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Common;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Extensions;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpClientManager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class HttpClientManager.
|
|
||||||
/// </summary>
|
|
||||||
public class HttpClientManager : IHttpClient
|
|
||||||
{
|
|
||||||
private readonly ILogger<HttpClientManager> _logger;
|
|
||||||
private readonly IApplicationPaths _appPaths;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IApplicationHost _appHost;
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public HttpClientManager(
|
|
||||||
IApplicationPaths appPaths,
|
|
||||||
ILogger<HttpClientManager> logger,
|
|
||||||
IFileSystem fileSystem,
|
|
||||||
IApplicationHost appHost)
|
|
||||||
{
|
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
|
|
||||||
_appHost = appHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the correct http client for the given url.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url">The url.</param>
|
|
||||||
/// <returns>HttpClient.</returns>
|
|
||||||
private HttpClient GetHttpClient(string url)
|
|
||||||
{
|
|
||||||
var key = GetHostFromUrl(url);
|
|
||||||
|
|
||||||
if (!_httpClients.TryGetValue(key, out var client))
|
|
||||||
{
|
|
||||||
client = new HttpClient()
|
|
||||||
{
|
|
||||||
BaseAddress = new Uri(url)
|
|
||||||
};
|
|
||||||
|
|
||||||
_httpClients.TryAdd(key, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method)
|
|
||||||
{
|
|
||||||
string url = options.Url;
|
|
||||||
var uriAddress = new Uri(url);
|
|
||||||
string userInfo = uriAddress.UserInfo;
|
|
||||||
if (!string.IsNullOrWhiteSpace(userInfo))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
|
|
||||||
url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage(method, url);
|
|
||||||
|
|
||||||
foreach (var header in options.RequestHeaders)
|
|
||||||
{
|
|
||||||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.EnableDefaultUserAgent
|
|
||||||
&& !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
|
|
||||||
{
|
|
||||||
request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (options.DecompressionMethod)
|
|
||||||
{
|
|
||||||
case CompressionMethods.Deflate | CompressionMethods.Gzip:
|
|
||||||
request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
|
|
||||||
break;
|
|
||||||
case CompressionMethods.Deflate:
|
|
||||||
request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
|
|
||||||
break;
|
|
||||||
case CompressionMethods.Gzip:
|
|
||||||
request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.EnableKeepAlive)
|
|
||||||
{
|
|
||||||
request.Headers.Add(HeaderNames.Connection, "Keep-Alive");
|
|
||||||
}
|
|
||||||
|
|
||||||
// request.Headers.Add(HeaderNames.CacheControl, "no-cache");
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (!string.IsNullOrWhiteSpace(userInfo))
|
|
||||||
{
|
|
||||||
var parts = userInfo.Split(':');
|
|
||||||
if (parts.Length == 2)
|
|
||||||
{
|
|
||||||
request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the response internal.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options">The options.</param>
|
|
||||||
/// <returns>Task{HttpResponseInfo}.</returns>
|
|
||||||
public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
|
|
||||||
=> SendAsync(options, HttpMethod.Get);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs a GET request and returns the resulting stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options">The options.</param>
|
|
||||||
/// <returns>Task{Stream}.</returns>
|
|
||||||
public async Task<Stream> Get(HttpRequestOptions options)
|
|
||||||
{
|
|
||||||
var response = await GetResponse(options).ConfigureAwait(false);
|
|
||||||
return response.Content;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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 Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
|
|
||||||
=> SendAsync(options, new HttpMethod(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)
|
|
||||||
{
|
|
||||||
return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = options.Url;
|
|
||||||
var urlHash = url.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
|
|
||||||
|
|
||||||
var response = GetCachedResponse(responseCachePath, options.CacheLength, url);
|
|
||||||
if (response != null)
|
|
||||||
{
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
await CacheResponse(response, responseCachePath).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
|
|
||||||
{
|
|
||||||
if (File.Exists(responseCachePath)
|
|
||||||
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
|
||||||
|
|
||||||
return new HttpResponseInfo
|
|
||||||
{
|
|
||||||
ResponseUrl = url,
|
|
||||||
Content = stream,
|
|
||||||
StatusCode = HttpStatusCode.OK,
|
|
||||||
ContentLength = stream.Length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath));
|
|
||||||
|
|
||||||
using (var fileStream = new FileStream(
|
|
||||||
responseCachePath,
|
|
||||||
FileMode.Create,
|
|
||||||
FileAccess.Write,
|
|
||||||
FileShare.None,
|
|
||||||
IODefaults.FileStreamBufferSize,
|
|
||||||
true))
|
|
||||||
{
|
|
||||||
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
|
|
||||||
|
|
||||||
response.Content.Position = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod)
|
|
||||||
{
|
|
||||||
ValidateParams(options);
|
|
||||||
|
|
||||||
options.CancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var client = GetHttpClient(options.Url);
|
|
||||||
|
|
||||||
var httpWebRequest = GetRequestMessage(options, httpMethod);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(options.RequestContent)
|
|
||||||
|| httpMethod == HttpMethod.Post)
|
|
||||||
{
|
|
||||||
if (options.RequestContent != null)
|
|
||||||
{
|
|
||||||
httpWebRequest.Content = new StringContent(
|
|
||||||
options.RequestContent,
|
|
||||||
null,
|
|
||||||
options.RequestContentType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
httpWebRequest.Content = new ByteArrayContent(Array.Empty<byte>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.CancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var response = await client.SendAsync(
|
|
||||||
httpWebRequest,
|
|
||||||
options.BufferContent || options.CacheMode == CacheMode.Unconditional ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead,
|
|
||||||
options.CancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
|
|
||||||
|
|
||||||
options.CancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
|
||||||
return new HttpResponseInfo(response.Headers, response.Content.Headers)
|
|
||||||
{
|
|
||||||
Content = stream,
|
|
||||||
StatusCode = response.StatusCode,
|
|
||||||
ContentType = response.Content.Headers.ContentType?.MediaType,
|
|
||||||
ContentLength = response.Content.Headers.ContentLength,
|
|
||||||
ResponseUrl = response.Content.Headers.ContentLocation?.ToString()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<HttpResponseInfo> Post(HttpRequestOptions options)
|
|
||||||
=> SendAsync(options, HttpMethod.Post);
|
|
||||||
|
|
||||||
private void ValidateParams(HttpRequestOptions options)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(options.Url))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the host from URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url">The URL.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
private static string GetHostFromUrl(string url)
|
|
||||||
{
|
|
||||||
var index = url.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (index != -1)
|
|
||||||
{
|
|
||||||
url = url.Substring(index + 3);
|
|
||||||
var host = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(host))
|
|
||||||
{
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options)
|
|
||||||
{
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.LogErrorResponseBody)
|
|
||||||
{
|
|
||||||
string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
|
||||||
_logger.LogError("HTTP request failed with message: {Message}", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new HttpException(response.ReasonPhrase)
|
|
||||||
{
|
|
||||||
StatusCode = response.StatusCode
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
|
_logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -160,11 +160,11 @@ namespace Emby.Server.Implementations.IO
|
||||||
// For now swallow and log.
|
// For now swallow and log.
|
||||||
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
|
||||||
// Should we remove it from it's parent?
|
// Should we remove it from it's parent?
|
||||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
_logger.LogError(ex, "Error refreshing {Name}", item.Name);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error refreshing {name}", item.Name);
|
_logger.LogError(ex, "Error refreshing {Name}", item.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
|
@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
|
_logger.LogError(ex, "Error in ReportFileSystemChanged for {Path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -398,30 +398,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void SetReadOnly(string path, bool isReadOnly)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = GetExtendedFileSystemInfo(path);
|
|
||||||
|
|
||||||
if (info.Exists && info.IsReadOnly != isReadOnly)
|
|
||||||
{
|
|
||||||
if (isReadOnly)
|
|
||||||
{
|
|
||||||
File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var attributes = File.GetAttributes(path);
|
|
||||||
attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
|
|
||||||
File.SetAttributes(path, attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
|
||||||
{
|
{
|
||||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||||
|
@ -707,14 +683,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
|
return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void SetExecutable(string path)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.Id == OperatingSystemId.Darwin)
|
|
||||||
{
|
|
||||||
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RunProcess(string path, string args, string workingDirectory)
|
private static void RunProcess(string path, string args, string workingDirectory)
|
||||||
{
|
{
|
||||||
using (var process = Process.Start(new ProcessStartInfo
|
using (var process = Process.Start(new ProcessStartInfo
|
||||||
|
|
|
@ -11,8 +11,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
{
|
{
|
||||||
public class StreamHelper : IStreamHelper
|
public class StreamHelper : IStreamHelper
|
||||||
{
|
{
|
||||||
private const int StreamCopyToBufferSize = 81920;
|
|
||||||
|
|
||||||
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
|
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
|
@ -83,37 +81,9 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int totalBytesRead = 0;
|
|
||||||
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
|
||||||
{
|
|
||||||
var bytesToWrite = bytesRead;
|
|
||||||
|
|
||||||
if (bytesToWrite > 0)
|
|
||||||
{
|
|
||||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
totalBytesRead += bytesRead;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalBytesRead;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<byte>.Shared.Return(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
|
|
|
@ -16,13 +16,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
public class DirectRecorder : IRecorder
|
public class DirectRecorder : IRecorder
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper)
|
public DirectRecorder(ILogger logger, IHttpClientFactory httpClientFactory, IStreamHelper streamHelper)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_streamHelper = streamHelper;
|
_streamHelper = streamHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,10 +52,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
||||||
|
|
||||||
// The media source is infinite so we need to handle stopping ourselves
|
// The media source is infinite so we need to handle stopping ourselves
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
using var durationToken = new CancellationTokenSource(duration);
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||||
|
|
||||||
await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
|
await directStreamProvider.CopyToAsync(output, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||||
|
@ -63,26 +63,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var httpRequestOptions = new HttpRequestOptions
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.GetAsync(mediaSource.Path, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
Url = mediaSource.Path,
|
|
||||||
BufferContent = false,
|
|
||||||
|
|
||||||
// Some remote urls will expect a user agent to be supplied
|
|
||||||
UserAgent = "Emby/3.0",
|
|
||||||
|
|
||||||
// Shouldn't matter but may cause issues
|
|
||||||
DecompressionMethod = CompressionMethods.None
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Opened recording stream from tuner provider");
|
_logger.LogInformation("Opened recording stream from tuner provider");
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
{
|
|
||||||
onStarted();
|
onStarted();
|
||||||
|
|
||||||
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
_logger.LogInformation("Copying recording stream to file {0}", targetFile);
|
||||||
|
@ -91,9 +80,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
var durationToken = new CancellationTokenSource(duration);
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
||||||
|
|
||||||
await _streamHelper.CopyUntilCancelled(response.Content, output, 81920, cancellationToken).ConfigureAwait(false);
|
await _streamHelper.CopyUntilCancelled(
|
||||||
}
|
await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
|
||||||
}
|
output,
|
||||||
|
IODefaults.CopyToBufferSize,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -48,7 +49,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger<EmbyTV> _logger;
|
private readonly ILogger<EmbyTV> _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
ILogger<EmbyTV> logger,
|
ILogger<EmbyTV> logger,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ILiveTvManager liveTvManager,
|
ILiveTvManager liveTvManager,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
|
@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -604,11 +605,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@ -808,11 +804,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
|
|
||||||
{
|
|
||||||
return _activeRecordings.Values.Where(i => i.Timer.Status == RecordingStatus.InProgress && !i.CancellationTokenSource.IsCancellationRequested);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(path))
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
@ -1015,16 +1006,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
throw new Exception("Tuner not found.");
|
throw new Exception("Tuner not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
|
|
||||||
{
|
|
||||||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
|
||||||
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
|
||||||
|
|
||||||
mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
|
|
||||||
|
|
||||||
return mediaSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(channelId))
|
if (string.IsNullOrWhiteSpace(channelId))
|
||||||
|
@ -1654,10 +1635,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||||
{
|
{
|
||||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
|
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
return new DirectRecorder(_logger, _httpClientFactory, _streamHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSuccessfulRecording(TimerInfo timer, string path)
|
private void OnSuccessfulRecording(TimerInfo timer, string path)
|
||||||
|
|
|
@ -8,12 +8,9 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
@ -26,26 +23,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
private readonly IJsonSerializer _json;
|
||||||
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
private Stream _logFileStream;
|
private Stream _logFileStream;
|
||||||
private string _targetPath;
|
private string _targetPath;
|
||||||
private Process _process;
|
private Process _process;
|
||||||
private readonly IJsonSerializer _json;
|
|
||||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
|
|
||||||
public EncodedRecorder(
|
public EncodedRecorder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json)
|
||||||
IServerConfigurationManager config)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_config = config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CopySubtitles => false;
|
private static bool CopySubtitles => false;
|
||||||
|
@ -58,19 +53,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// The media source is infinite so we need to handle stopping ourselves
|
// The media source is infinite so we need to handle stopping ourselves
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
using var durationToken = new CancellationTokenSource(duration);
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||||
|
|
||||||
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false);
|
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncodingOptions GetEncodingOptions()
|
|
||||||
{
|
|
||||||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_targetPath = targetFile;
|
_targetPath = targetFile;
|
||||||
|
@ -108,7 +98,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
StartInfo = processStartInfo,
|
StartInfo = processStartInfo,
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
};
|
};
|
||||||
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
|
||||||
|
|
||||||
_process.Start();
|
_process.Start();
|
||||||
|
|
||||||
|
@ -221,20 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetOutputSizeParam()
|
protected string GetOutputSizeParam()
|
||||||
{
|
=> "-vf \"yadif=0:-1:0\"";
|
||||||
var filters = new List<string>();
|
|
||||||
|
|
||||||
filters.Add("yadif=0:-1:0");
|
|
||||||
|
|
||||||
var output = string.Empty;
|
|
||||||
|
|
||||||
if (filters.Count > 0)
|
|
||||||
{
|
|
||||||
output += string.Format(CultureInfo.InvariantCulture, " -vf \"{0}\"", string.Join(",", filters.ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Stop()
|
private void Stop()
|
||||||
{
|
{
|
||||||
|
@ -291,7 +268,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnFfMpegProcessExited(Process process, string inputFile)
|
private void OnFfMpegProcessExited(Process process)
|
||||||
{
|
{
|
||||||
using (process)
|
using (process)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,8 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Mime;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
|
@ -24,23 +26,23 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
{
|
{
|
||||||
public class SchedulesDirect : IListingsProvider
|
public class SchedulesDirect : IListingsProvider
|
||||||
{
|
{
|
||||||
|
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
||||||
|
|
||||||
private readonly ILogger<SchedulesDirect> _logger;
|
private readonly ILogger<SchedulesDirect> _logger;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
|
|
||||||
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
|
|
||||||
|
|
||||||
public SchedulesDirect(
|
public SchedulesDirect(
|
||||||
ILogger<SchedulesDirect> logger,
|
ILogger<SchedulesDirect> logger,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IApplicationHost appHost)
|
IApplicationHost appHost)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
while (start <= end)
|
while (start <= end)
|
||||||
{
|
{
|
||||||
dates.Add(start.ToString("yyyy-MM-dd"));
|
dates.Add(start.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
|
||||||
start = start.AddDays(1);
|
start = start.AddDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,38 +104,23 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
var requestString = _jsonSerializer.SerializeToString(requestList);
|
var requestString = _jsonSerializer.SerializeToString(requestList);
|
||||||
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
|
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
|
||||||
|
|
||||||
var httpOptions = new HttpRequestOptions()
|
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
|
||||||
{
|
options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||||
Url = ApiUrl + "/schedules",
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
UserAgent = UserAgent,
|
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
CancellationToken = cancellationToken,
|
await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
LogErrorResponseBody = true,
|
var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(responseStream).ConfigureAwait(false);
|
||||||
RequestContent = requestString
|
|
||||||
};
|
|
||||||
|
|
||||||
httpOptions.RequestHeaders["token"] = token;
|
|
||||||
|
|
||||||
using (var response = await Post(httpOptions, true, info).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(response.Content).ConfigureAwait(false);
|
|
||||||
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
|
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
|
||||||
|
|
||||||
httpOptions = new HttpRequestOptions()
|
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
|
||||||
{
|
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
|
||||||
Url = ApiUrl + "/programs",
|
|
||||||
UserAgent = UserAgent,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
LogErrorResponseBody = true
|
|
||||||
};
|
|
||||||
|
|
||||||
httpOptions.RequestHeaders["token"] = token;
|
|
||||||
|
|
||||||
var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
|
var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
|
||||||
httpOptions.RequestContent = "[\"" + string.Join("\", \"", programsID) + "\"]";
|
programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||||
|
|
||||||
using (var innerResponse = await Post(httpOptions, true, info).ConfigureAwait(false))
|
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponse.Content).ConfigureAwait(false);
|
var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream).ConfigureAwait(false);
|
||||||
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
|
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
|
||||||
|
|
||||||
var programIdsWithImages =
|
var programIdsWithImages =
|
||||||
|
@ -190,8 +177,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
return programsInfo;
|
return programsInfo;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetSizeOrder(ScheduleDirect.ImageData image)
|
private static int GetSizeOrder(ScheduleDirect.ImageData image)
|
||||||
{
|
{
|
||||||
|
@ -367,13 +352,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(details.originalAirDate))
|
if (!string.IsNullOrWhiteSpace(details.originalAirDate))
|
||||||
{
|
{
|
||||||
info.OriginalAirDate = DateTime.Parse(details.originalAirDate);
|
info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
|
||||||
info.ProductionYear = info.OriginalAirDate.Value.Year;
|
info.ProductionYear = info.OriginalAirDate.Value.Year;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.movie != null)
|
if (details.movie != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out int year))
|
if (!string.IsNullOrEmpty(details.movie.year)
|
||||||
|
&& int.TryParse(details.movie.year, out int year))
|
||||||
{
|
{
|
||||||
info.ProductionYear = year;
|
info.ProductionYear = year;
|
||||||
}
|
}
|
||||||
|
@ -482,22 +468,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
imageIdString = imageIdString.TrimEnd(',') + "]";
|
imageIdString = imageIdString.TrimEnd(',') + "]";
|
||||||
|
|
||||||
var httpOptions = new HttpRequestOptions()
|
using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs");
|
||||||
{
|
message.Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||||
Url = ApiUrl + "/metadata/programs",
|
|
||||||
UserAgent = UserAgent,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
RequestContent = imageIdString,
|
|
||||||
LogErrorResponseBody = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false))
|
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
await using var response = await innerResponse2.Content.ReadAsStreamAsync();
|
||||||
return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(
|
return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(
|
||||||
innerResponse2.Content).ConfigureAwait(false);
|
response).ConfigureAwait(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -518,22 +497,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
return lineups;
|
return lineups;
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new HttpRequestOptions()
|
using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/headends?country=" + country + "&postalcode=" + location);
|
||||||
{
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
Url = ApiUrl + "/headends?country=" + country + "&postalcode=" + location,
|
|
||||||
UserAgent = UserAgent,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
LogErrorResponseBody = true
|
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["token"] = token;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var httpResponse = await Get(options, false, info).ConfigureAwait(false))
|
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
|
||||||
using (Stream responce = httpResponse.Content)
|
await using var response = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
{
|
|
||||||
var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(responce).ConfigureAwait(false);
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(response).ConfigureAwait(false);
|
||||||
|
|
||||||
if (root != null)
|
if (root != null)
|
||||||
{
|
{
|
||||||
|
@ -554,7 +526,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
_logger.LogInformation("No lineups available");
|
_logger.LogInformation("No lineups available");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting headends");
|
_logger.LogError(ex, "Error getting headends");
|
||||||
|
@ -587,7 +558,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NameValuePair savedToken = null;
|
NameValuePair savedToken;
|
||||||
if (!_tokens.TryGetValue(username, out savedToken))
|
if (!_tokens.TryGetValue(username, out savedToken))
|
||||||
{
|
{
|
||||||
savedToken = new NameValuePair();
|
savedToken = new NameValuePair();
|
||||||
|
@ -633,16 +604,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseInfo> Post(HttpRequestOptions options,
|
private async Task<HttpResponseMessage> Send(
|
||||||
|
HttpRequestMessage options,
|
||||||
bool enableRetry,
|
bool enableRetry,
|
||||||
ListingsProviderInfo providerInfo)
|
ListingsProviderInfo providerInfo,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
|
||||||
{
|
{
|
||||||
// Schedules direct requires that the client support compression and will return a 400 response without it
|
|
||||||
options.DecompressionMethod = CompressionMethods.Deflate;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _httpClient.Post(options).ConfigureAwait(false);
|
return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
@ -659,57 +630,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
|
options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false));
|
||||||
return await Post(options, false, providerInfo).ConfigureAwait(false);
|
return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseInfo> Get(HttpRequestOptions options,
|
private async Task<string> GetTokenInternal(
|
||||||
bool enableRetry,
|
string username,
|
||||||
ListingsProviderInfo providerInfo)
|
string password,
|
||||||
{
|
|
||||||
// Schedules direct requires that the client support compression and will return a 400 response without it
|
|
||||||
options.DecompressionMethod = CompressionMethods.Deflate;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
|
||||||
{
|
|
||||||
_tokens.Clear();
|
|
||||||
|
|
||||||
if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
|
|
||||||
{
|
|
||||||
enableRetry = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enableRetry)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
|
|
||||||
return await Get(options, false, providerInfo).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> GetTokenInternal(string username, string password,
|
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var httpOptions = new HttpRequestOptions()
|
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
|
||||||
{
|
options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||||
Url = ApiUrl + "/token",
|
|
||||||
UserAgent = UserAgent,
|
|
||||||
RequestContent = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}",
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
LogErrorResponseBody = true
|
|
||||||
};
|
|
||||||
// _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
|
|
||||||
// httpOptions.RequestContent);
|
|
||||||
|
|
||||||
using (var response = await Post(httpOptions, false, null).ConfigureAwait(false))
|
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(response.Content).ConfigureAwait(false);
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false);
|
||||||
if (root.message == "OK")
|
if (root.message == "OK")
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
|
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
|
||||||
|
@ -718,7 +653,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
|
throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
|
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -736,20 +670,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
_logger.LogInformation("Adding new LineUp ");
|
_logger.LogInformation("Adding new LineUp ");
|
||||||
|
|
||||||
var httpOptions = new HttpRequestOptions()
|
using var options = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId);
|
||||||
{
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
Url = ApiUrl + "/lineups/" + info.ListingsId,
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
UserAgent = UserAgent,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
LogErrorResponseBody = true,
|
|
||||||
BufferContent = false
|
|
||||||
};
|
|
||||||
|
|
||||||
httpOptions.RequestHeaders["token"] = token;
|
|
||||||
|
|
||||||
using (await _httpClient.SendAsync(httpOptions, HttpMethod.Put).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken)
|
private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken)
|
||||||
|
@ -768,26 +691,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
_logger.LogInformation("Headends on account ");
|
_logger.LogInformation("Headends on account ");
|
||||||
|
|
||||||
var options = new HttpRequestOptions()
|
using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups");
|
||||||
{
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
Url = ApiUrl + "/lineups",
|
|
||||||
UserAgent = UserAgent,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
LogErrorResponseBody = true
|
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["token"] = token;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var httpResponse = await Get(options, false, null).ConfigureAwait(false))
|
using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
|
||||||
using (var response = httpResponse.Content)
|
await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
{
|
using var response = httpResponse.Content;
|
||||||
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(response).ConfigureAwait(false);
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(stream).ConfigureAwait(false);
|
||||||
|
|
||||||
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
|
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
// Apparently we're supposed to swallow this
|
// Apparently we're supposed to swallow this
|
||||||
|
@ -851,22 +766,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
throw new Exception("token required");
|
throw new Exception("token required");
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpOptions = new HttpRequestOptions()
|
using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId);
|
||||||
{
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
Url = ApiUrl + "/lineups/" + listingsId,
|
|
||||||
UserAgent = UserAgent,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
LogErrorResponseBody = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
httpOptions.RequestHeaders["token"] = token;
|
|
||||||
|
|
||||||
var list = new List<ChannelInfo>();
|
var list = new List<ChannelInfo>();
|
||||||
|
|
||||||
using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false))
|
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
using (var response = httpResponse.Content)
|
await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
{
|
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false);
|
||||||
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(response).ConfigureAwait(false);
|
|
||||||
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
|
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
|
||||||
_logger.LogInformation("Mapping Stations to Channel");
|
_logger.LogInformation("Mapping Stations to Channel");
|
||||||
|
|
||||||
|
@ -879,10 +786,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
|
var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
|
||||||
if (station == null)
|
if (station == null)
|
||||||
{
|
{
|
||||||
station = new ScheduleDirect.Station
|
station = new ScheduleDirect.Station { stationID = map.stationID };
|
||||||
{
|
|
||||||
stationID = map.stationID
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelInfo = new ChannelInfo
|
var channelInfo = new ChannelInfo
|
||||||
|
@ -900,7 +804,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
list.Add(channelInfo);
|
list.Add(channelInfo);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,20 +25,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
public class XmlTvListingsProvider : IListingsProvider
|
public class XmlTvListingsProvider : IListingsProvider
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IZipClient _zipClient;
|
private readonly IZipClient _zipClient;
|
||||||
|
|
||||||
public XmlTvListingsProvider(
|
public XmlTvListingsProvider(
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
ILogger<XmlTvListingsProvider> logger,
|
ILogger<XmlTvListingsProvider> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IZipClient zipClient)
|
IZipClient zipClient)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_zipClient = zipClient;
|
_zipClient = zipClient;
|
||||||
|
@ -78,29 +78,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||||
|
|
||||||
using (var res = await _httpClient.SendAsync(
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false);
|
||||||
new HttpRequestOptions
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
{
|
await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
Url = path,
|
|
||||||
DecompressionMethod = CompressionMethods.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, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return UnzipIfNeeded(path, cacheFile);
|
return UnzipIfNeeded(path, cacheFile);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
|
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ILogger<HdHomerunHost> logger,
|
ILogger<HdHomerunHost> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ISocketFactory socketFactory,
|
ISocketFactory socketFactory,
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
|
@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache)
|
||||||
: base(config, logger, fileSystem, memoryCache)
|
: base(config, logger, fileSystem, memoryCache)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_socketFactory = socketFactory;
|
_socketFactory = socketFactory;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
|
@ -71,15 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
Url = model.LineupURL,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
BufferContent = false
|
|
||||||
};
|
|
||||||
|
|
||||||
using var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
|
|
||||||
await using var stream = response.Content;
|
|
||||||
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken)
|
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken)
|
||||||
.ConfigureAwait(false) ?? new List<Channels>();
|
.ConfigureAwait(false) ?? new List<Channels>();
|
||||||
|
|
||||||
|
@ -133,14 +126,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var response = await _httpClient.SendAsync(
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
new HttpRequestOptions
|
.GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||||
{
|
.ConfigureAwait(false);
|
||||||
Url = string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)),
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
BufferContent = false
|
|
||||||
}, HttpMethod.Get).ConfigureAwait(false);
|
|
||||||
await using var stream = response.Content;
|
|
||||||
var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken)
|
var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -183,17 +172,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
new HttpRequestOptions()
|
.GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||||
{
|
.ConfigureAwait(false);
|
||||||
Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)),
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
CancellationToken = cancellationToken,
|
using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
|
||||||
BufferContent = false
|
|
||||||
},
|
|
||||||
HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
using (var stream = response.Content)
|
|
||||||
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
|
|
||||||
{
|
|
||||||
var tuners = new List<LiveTvTunerInfo>();
|
var tuners = new List<LiveTvTunerInfo>();
|
||||||
while (!sr.EndOfStream)
|
while (!sr.EndOfStream)
|
||||||
{
|
{
|
||||||
|
@ -225,7 +208,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
return tuners;
|
return tuners;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static string StripXML(string source)
|
private static string StripXML(string source)
|
||||||
{
|
{
|
||||||
|
@ -634,7 +616,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
info,
|
info,
|
||||||
streamId,
|
streamId,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
_httpClient,
|
_httpClientFactory,
|
||||||
Logger,
|
Logger,
|
||||||
Config,
|
Config,
|
||||||
_appHost,
|
_appHost,
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
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 MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
|
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
@ -37,14 +38,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
ILogger<M3UTunerHost> logger,
|
ILogger<M3UTunerHost> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
IStreamHelper streamHelper,
|
IStreamHelper streamHelper,
|
||||||
IMemoryCache memoryCache)
|
IMemoryCache memoryCache)
|
||||||
: base(config, logger, fileSystem, memoryCache)
|
: base(config, logger, fileSystem, memoryCache)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
var channelIdPrefix = GetFullChannelIdPrefix(info);
|
var channelIdPrefix = GetFullChannelIdPrefix(info);
|
||||||
|
|
||||||
return await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
return await new M3uParser(Logger, _httpClientFactory, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||||
|
@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper);
|
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
public async Task Validate(TunerHostInfo info)
|
public async Task Validate(TunerHostInfo info)
|
||||||
{
|
{
|
||||||
using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
|
using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -19,13 +20,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
public class M3uParser
|
public class M3uParser
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost)
|
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,13 +52,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return _httpClient.Get(new HttpRequestOptions
|
return _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.GetStreamAsync(url);
|
||||||
Url = url,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
// Some data providers will require a user agent
|
|
||||||
UserAgent = _appHost.ApplicationUserAgent
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult((Stream)File.OpenRead(url));
|
return Task.FromResult((Stream)File.OpenRead(url));
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
{
|
{
|
||||||
public class SharedHttpStream : LiveStream, IDirectStreamProvider
|
public class SharedHttpStream : LiveStream, IDirectStreamProvider
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
|
||||||
public SharedHttpStream(
|
public SharedHttpStream(
|
||||||
|
@ -29,14 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
TunerHostInfo tunerHostInfo,
|
TunerHostInfo tunerHostInfo,
|
||||||
string originalStreamId,
|
string originalStreamId,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IConfigurationManager configurationManager,
|
IConfigurationManager configurationManager,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
IStreamHelper streamHelper)
|
IStreamHelper streamHelper)
|
||||||
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
OriginalStreamId = originalStreamId;
|
OriginalStreamId = originalStreamId;
|
||||||
EnableStreamSharing = true;
|
EnableStreamSharing = true;
|
||||||
|
@ -55,25 +55,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
var typeName = GetType().Name;
|
var typeName = GetType().Name;
|
||||||
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
|
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
|
||||||
|
|
||||||
var httpRequestOptions = new HttpRequestOptions
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
|
||||||
Url = url,
|
.ConfigureAwait(false);
|
||||||
CancellationToken = CancellationToken.None,
|
|
||||||
BufferContent = false,
|
|
||||||
DecompressionMethod = CompressionMethods.None
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var header in mediaSource.RequiredHttpHeaders)
|
|
||||||
{
|
|
||||||
httpRequestOptions.RequestHeaders[header.Key] = header.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var extension = "ts";
|
var extension = "ts";
|
||||||
var requiresRemux = false;
|
var requiresRemux = false;
|
||||||
|
|
||||||
var contentType = response.ContentType ?? string.Empty;
|
var contentType = response.Content.Headers.ContentType.ToString();
|
||||||
if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
|
if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
requiresRemux = true;
|
requiresRemux = true;
|
||||||
|
@ -132,17 +121,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.Run(async () =>
|
return Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
|
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
|
||||||
using (response)
|
using var message = response;
|
||||||
using (var stream = response.Content)
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||||
{
|
|
||||||
await StreamHelper.CopyToAsync(
|
await StreamHelper.CopyToAsync(
|
||||||
stream,
|
stream,
|
||||||
fileStream,
|
fileStream,
|
||||||
|
@ -150,7 +138,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
() => Resolve(openTaskCompletionSource),
|
() => Resolve(openTaskCompletionSource),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (OperationCanceledException ex)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath);
|
Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath);
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
"ScheduledTaskFailedWithName": "{0} mislykkes",
|
"ScheduledTaskFailedWithName": "{0} mislykkes",
|
||||||
"ScheduledTaskStartedWithName": "{0} startet",
|
"ScheduledTaskStartedWithName": "{0} startet",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
|
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
|
||||||
"Shows": "Programmer",
|
"Shows": "Program",
|
||||||
"Songs": "Sanger",
|
"Songs": "Sanger",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
|
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
|
||||||
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
|
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
"UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
|
"UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
|
||||||
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
|
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
|
||||||
"UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
|
"UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} har startet avspilling {1}",
|
"UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
|
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
|
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
|
||||||
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
|
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
|
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
|
||||||
"Artists": "Artistar",
|
"Artists": "Artistar",
|
||||||
"Application": "Program",
|
"Application": "Program",
|
||||||
"AppDeviceValues": "App: {0}, Einheit: {1}",
|
"AppDeviceValues": "App: {0}, Eining: {1}",
|
||||||
"Albums": "Album",
|
"Albums": "Album",
|
||||||
"NotificationOptionServerRestartRequired": "Tenaren krev omstart",
|
"NotificationOptionServerRestartRequired": "Tenaren krev omstart",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
|
"NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"NotificationOptionPluginInstalled": "Tilleggsprogram installert",
|
"NotificationOptionPluginInstalled": "Tilleggsprogram installert",
|
||||||
"NotificationOptionPluginError": "Tilleggsprogram feila",
|
"NotificationOptionPluginError": "Tilleggsprogram feila",
|
||||||
"NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
|
"NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
|
||||||
"NotificationOptionInstallationFailed": "Installasjonen feila",
|
"NotificationOptionInstallationFailed": "Installasjonsfeil",
|
||||||
"NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
|
"NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
|
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
|
||||||
"NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
|
"NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
|
||||||
|
@ -56,5 +56,62 @@
|
||||||
"MusicVideos": "Musikkvideoar",
|
"MusicVideos": "Musikkvideoar",
|
||||||
"Music": "Musikk",
|
"Music": "Musikk",
|
||||||
"Movies": "Filmar",
|
"Movies": "Filmar",
|
||||||
"MixedContent": "Blanda innhald"
|
"MixedContent": "Blanda innhald",
|
||||||
|
"Sync": "Synkronisera",
|
||||||
|
"TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.",
|
||||||
|
"TaskDownloadMissingSubtitles": "Last ned manglande undertekstar",
|
||||||
|
"TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.",
|
||||||
|
"TaskRefreshChannels": "Oppdater kanalar",
|
||||||
|
"TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.",
|
||||||
|
"TaskCleanTranscode": "Reins transkodemappe",
|
||||||
|
"TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.",
|
||||||
|
"TaskUpdatePlugins": "Oppdaterer programtillegg",
|
||||||
|
"TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.",
|
||||||
|
"TaskRefreshPeople": "Oppdater personar",
|
||||||
|
"TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.",
|
||||||
|
"TaskCleanLogs": "Reins loggmappe",
|
||||||
|
"TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.",
|
||||||
|
"TaskRefreshLibrary": "Skann mediebibliotek",
|
||||||
|
"TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.",
|
||||||
|
"TaskRefreshChapterImages": "Trekk ut kapittelbilete",
|
||||||
|
"TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.",
|
||||||
|
"TaskCleanCache": "Rens mappe for hurtiglager",
|
||||||
|
"TasksChannelsCategory": "Internettkanalar",
|
||||||
|
"TasksApplicationCategory": "Applikasjon",
|
||||||
|
"TasksLibraryCategory": "Bibliotek",
|
||||||
|
"TasksMaintenanceCategory": "Vedlikehald",
|
||||||
|
"VersionNumber": "Versjon {0}",
|
||||||
|
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
|
||||||
|
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
|
||||||
|
"UserStoppedPlayingItemWithValues": "{0} har fullført avspeling {1} på {2}",
|
||||||
|
"UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}",
|
||||||
|
"UserPolicyUpdatedWithName": "Brukarreglar har blitt oppdatert for {0}",
|
||||||
|
"UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
|
||||||
|
"UserOnlineFromDevice": "{0} er direktekopla frå {1}",
|
||||||
|
"UserOfflineFromDevice": "{0} har kopla frå {1}",
|
||||||
|
"UserLockedOutWithName": "Brukar {0} har blitt utestengd",
|
||||||
|
"UserDownloadingItemWithValues": "{0} lastar ned {1}",
|
||||||
|
"UserDeletedWithName": "Brukar {0} er sletta",
|
||||||
|
"UserCreatedWithName": "Brukar {0} er oppretta",
|
||||||
|
"User": "Brukar",
|
||||||
|
"TvShows": "TV-seriar",
|
||||||
|
"System": "System",
|
||||||
|
"SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}",
|
||||||
|
"StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.",
|
||||||
|
"Songs": "Songar",
|
||||||
|
"Shows": "Program",
|
||||||
|
"ServerNameNeedsToBeRestarted": "{0} må omstartast",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} starta",
|
||||||
|
"ScheduledTaskFailedWithName": "{0} feila",
|
||||||
|
"ProviderValue": "Leverandør: {0}",
|
||||||
|
"PluginUpdatedWithName": "{0} blei oppdatert",
|
||||||
|
"PluginUninstalledWithName": "{0} blei avinstallert",
|
||||||
|
"PluginInstalledWithName": "{0} blei installert",
|
||||||
|
"Plugin": "Programtillegg",
|
||||||
|
"Playlists": "Speleliste",
|
||||||
|
"Photos": "Foto",
|
||||||
|
"NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa",
|
||||||
|
"NotificationOptionVideoPlayback": "Videoavspeling starta",
|
||||||
|
"NotificationOptionUserLockedOut": "Brukar er utestengd",
|
||||||
|
"NotificationOptionTaskFailed": "Planlagt oppgåve feila"
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
|
"MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
|
||||||
"MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
|
"MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
|
||||||
"MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
|
"MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
|
||||||
"Inherit": "மரபரிமையாகப் பெறு",
|
"Inherit": "மரபுரிமையாகப் பெறு",
|
||||||
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
|
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
|
||||||
"HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
|
"HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
|
||||||
"Folders": "கோப்புறைகள்",
|
"Folders": "கோப்புறைகள்",
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
|
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
|
||||||
"TaskRefreshChannels": "சேனல்களை புதுப்பி",
|
"TaskRefreshChannels": "சேனல்களை புதுப்பி",
|
||||||
"TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
|
"TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
|
||||||
"TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்",
|
"TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்",
|
||||||
"TasksChannelsCategory": "இணைய சேனல்கள்",
|
"TasksChannelsCategory": "இணைய சேனல்கள்",
|
||||||
"TasksApplicationCategory": "செயலி",
|
"TasksApplicationCategory": "செயலி",
|
||||||
"TasksLibraryCategory": "நூலகம்",
|
"TasksLibraryCategory": "நூலகம்",
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
"Sync": "ஒத்திசைவு",
|
"Sync": "ஒத்திசைவு",
|
||||||
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
|
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
|
||||||
"Songs": "பாடல்கள்",
|
"Songs": "பாடல்கள்",
|
||||||
"Shows": "தொடர்கள்",
|
"Shows": "நிகழ்ச்சிகள்",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
|
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
|
||||||
"ScheduledTaskStartedWithName": "{0} துவங்கியது",
|
"ScheduledTaskStartedWithName": "{0} துவங்கியது",
|
||||||
"ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
|
"ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
|
||||||
|
@ -67,20 +67,20 @@
|
||||||
"NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
|
"NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
|
"NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
|
"NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
|
||||||
"NameSeasonUnknown": "பருவம் அறியப்படாதவை",
|
"NameSeasonUnknown": "அறியப்படாத பருவம்",
|
||||||
"NameSeasonNumber": "பருவம் {0}",
|
"NameSeasonNumber": "பருவம் {0}",
|
||||||
"NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
|
"NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
|
||||||
"MusicVideos": "இசைப்படங்கள்",
|
"MusicVideos": "இசைப்படங்கள்",
|
||||||
"Music": "இசை",
|
"Music": "இசை",
|
||||||
"Movies": "திரைப்படங்கள்",
|
"Movies": "திரைப்படங்கள்",
|
||||||
"Latest": "புதியன",
|
"Latest": "புதியவை",
|
||||||
"LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
|
"LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
|
||||||
"LabelIpAddressValue": "ஐபி முகவரி: {0}",
|
"LabelIpAddressValue": "ஐபி முகவரி: {0}",
|
||||||
"ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
|
"ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
|
||||||
"ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
|
"ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
|
||||||
"HeaderNextUp": "அடுத்ததாக",
|
"HeaderNextUp": "அடுத்தது",
|
||||||
"HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
|
"HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
|
||||||
"HeaderFavoriteSongs": "பிடித்த பாட்டுகள்",
|
"HeaderFavoriteSongs": "பிடித்த பாடல்கள்",
|
||||||
"HeaderFavoriteShows": "பிடித்த தொடர்கள்",
|
"HeaderFavoriteShows": "பிடித்த தொடர்கள்",
|
||||||
"HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
|
"HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
|
||||||
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
|
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
|
||||||
|
@ -93,25 +93,25 @@
|
||||||
"Channels": "சேனல்கள்",
|
"Channels": "சேனல்கள்",
|
||||||
"Books": "புத்தகங்கள்",
|
"Books": "புத்தகங்கள்",
|
||||||
"AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
|
"AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
|
||||||
"Artists": "கலைஞர்",
|
"Artists": "கலைஞர்கள்",
|
||||||
"Application": "செயலி",
|
"Application": "செயலி",
|
||||||
"Albums": "ஆல்பங்கள்",
|
"Albums": "ஆல்பங்கள்",
|
||||||
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
|
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது",
|
"MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது",
|
||||||
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
|
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
|
||||||
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
|
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
|
||||||
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
|
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
|
"TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
|
||||||
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
|
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
|
||||||
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
|
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
|
||||||
"TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.",
|
"TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
|
||||||
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
|
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
|
||||||
"TaskCleanLogs": "பதிவு அடைவு சுத்தம் செய்யுங்கள்",
|
"TaskCleanLogs": "பதிவு அடைவை சுத்தம் செய்யுங்கள்",
|
||||||
"TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.",
|
"TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.",
|
||||||
"TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
|
"TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
|
||||||
"ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
|
"ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
|
||||||
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
|
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
|
||||||
"HomeVideos": "முகப்பு வீடியோக்கள்",
|
"HomeVideos": "முகப்பு வீடியோக்கள்",
|
||||||
"UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது",
|
"UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது",
|
||||||
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
|
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,10 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
{
|
{
|
||||||
|
@ -21,10 +21,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
/// Gets or sets the application paths.
|
/// Gets or sets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
private IApplicationPaths ApplicationPaths { get; set; }
|
private readonly IApplicationPaths _applicationPaths;
|
||||||
|
|
||||||
private readonly ILogger<DeleteCacheFileTask> _logger;
|
private readonly ILogger<DeleteCacheFileTask> _logger;
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
|
@ -37,20 +35,41 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ILocalizationManager localization)
|
ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
ApplicationPaths = appPaths;
|
_applicationPaths = appPaths;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => _localization.GetLocalizedString("TaskCleanCache");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => "DeleteCacheFiles";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsLogged => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run.
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[] {
|
return new[]
|
||||||
|
{
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||||
};
|
};
|
||||||
|
@ -68,7 +87,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.CachePath, minDateModified, progress);
|
DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -81,7 +100,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
|
DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +110,6 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the cache files from directory with a last write time less than a given date.
|
/// Deletes the cache files from directory with a last write time less than a given date.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -164,26 +182,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
_logger.LogError(ex, "Error deleting file {path}", path);
|
_logger.LogError(ex, "Error deleting file {path}", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => _localization.GetLocalizedString("TaskCleanCache");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Key => "DeleteCacheFiles";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsEnabled => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsLogged => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,27 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => "PluginUpdates";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsLogged => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run.
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -98,26 +119,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Key => "PluginUpdates";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsHidden => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsEnabled => true;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsLogged => true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
public class DailyTrigger : ITaskTrigger
|
public class DailyTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the time of day to trigger the task to run.
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the time of day to trigger the task to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The time of day.</value>
|
/// <value>The time of day.</value>
|
||||||
public TimeSpan TimeOfDay { get; set; }
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
@ -69,11 +74,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -11,6 +11,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class IntervalTrigger : ITaskTrigger
|
public class IntervalTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
|
private DateTime _lastStartDate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the interval.
|
/// Gets or sets the interval.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -28,8 +35,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
/// <value>The timer.</value>
|
/// <value>The timer.</value>
|
||||||
private Timer Timer { get; set; }
|
private Timer Timer { get; set; }
|
||||||
|
|
||||||
private DateTime _lastStartDate;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stars waiting for the trigger action.
|
/// Stars waiting for the trigger action.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -88,11 +93,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -12,6 +12,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StartupTrigger : ITaskTrigger
|
public class StartupTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
public int DelayMs { get; set; }
|
public int DelayMs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -48,20 +53,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnTriggered()
|
private void OnTriggered()
|
||||||
{
|
{
|
||||||
if (Triggered != null)
|
Triggered?.Invoke(this, EventArgs.Empty);
|
||||||
{
|
|
||||||
Triggered(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
public class WeeklyTrigger : ITaskTrigger
|
public class WeeklyTrigger : ITaskTrigger
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the time of day to trigger the task to run.
|
/// Occurs when [triggered].
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<EventArgs> Triggered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the time of day to trigger the task to run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The time of day.</value>
|
/// <value>The time of day.</value>
|
||||||
public TimeSpan TimeOfDay { get; set; }
|
public TimeSpan TimeOfDay { get; set; }
|
||||||
|
@ -95,20 +100,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [triggered].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<EventArgs> Triggered;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [triggered].
|
/// Called when [triggered].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnTriggered()
|
private void OnTriggered()
|
||||||
{
|
{
|
||||||
if (Triggered != null)
|
Triggered?.Invoke(this, EventArgs.Empty);
|
||||||
{
|
|
||||||
Triggered(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Updates
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger<InstallationManager> _logger;
|
private readonly ILogger<InstallationManager> _logger;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Updates
|
||||||
ILogger<InstallationManager> logger,
|
ILogger<InstallationManager> logger,
|
||||||
IApplicationHost appHost,
|
IApplicationHost appHost,
|
||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
|
@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Updates
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_applicationHost = appHost;
|
_applicationHost = appHost;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
@ -116,17 +116,10 @@ namespace Emby.Server.Implementations.Updates
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.SendAsync(
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
new HttpRequestOptions
|
.GetAsync(manifest, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
Url = manifest,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
CacheMode = CacheMode.Unconditional,
|
|
||||||
CacheLength = TimeSpan.FromMinutes(3)
|
|
||||||
},
|
|
||||||
HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
using (Stream stream = response.Content)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
|
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
|
||||||
|
@ -137,7 +130,6 @@ namespace Emby.Server.Implementations.Updates
|
||||||
return Array.Empty<PackageInfo>();
|
return Array.Empty<PackageInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (UriFormatException ex)
|
catch (UriFormatException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest);
|
_logger.LogError(ex, "The URL configured for the plugin repository manifest URL is not valid: {Manifest}", manifest);
|
||||||
|
@ -360,20 +352,13 @@ namespace Emby.Server.Implementations.Updates
|
||||||
// Always override the passed-in target (which is a file) and figure it out again
|
// Always override the passed-in target (which is a file) and figure it out again
|
||||||
string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name);
|
string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name);
|
||||||
|
|
||||||
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
|
.GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false);
|
||||||
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// CA5351: Do Not Use Broken Cryptographic Algorithms
|
// CA5351: Do Not Use Broken Cryptographic Algorithms
|
||||||
#pragma warning disable CA5351
|
#pragma warning disable CA5351
|
||||||
using (var res = await _httpClient.SendAsync(
|
using var md5 = MD5.Create();
|
||||||
new HttpRequestOptions
|
|
||||||
{
|
|
||||||
Url = package.SourceUrl,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
// We need it to be buffered for setting the position
|
|
||||||
BufferContent = true
|
|
||||||
},
|
|
||||||
HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
using (var stream = res.Content)
|
|
||||||
using (var md5 = MD5.Create())
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var hash = Hex.Encode(md5.ComputeHash(stream));
|
var hash = Hex.Encode(md5.ComputeHash(stream));
|
||||||
|
@ -394,7 +379,6 @@ namespace Emby.Server.Implementations.Updates
|
||||||
|
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
||||||
}
|
|
||||||
|
|
||||||
#pragma warning restore CA5351
|
#pragma warning restore CA5351
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
|
using MediaBrowser.Common.Json;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Jellyfin.Api
|
namespace Jellyfin.Api
|
||||||
|
@ -8,7 +9,10 @@ namespace Jellyfin.Api
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
[Produces(MediaTypeNames.Application.Json)]
|
[Produces(
|
||||||
|
MediaTypeNames.Application.Json,
|
||||||
|
JsonDefaults.CamelCaseMediaType,
|
||||||
|
JsonDefaults.PascalCaseMediaType)]
|
||||||
public class BaseJellyfinApiController : ControllerBase
|
public class BaseJellyfinApiController : ControllerBase
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Extensions;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Controllers
|
namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
|
@ -26,38 +27,20 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
private readonly ILogger<DashboardController> _logger;
|
private readonly ILogger<DashboardController> _logger;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IConfiguration _appConfig;
|
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
|
||||||
private readonly IResourceFileManager _resourceFileManager;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DashboardController"/> class.
|
/// Initializes a new instance of the <see cref="DashboardController"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
|
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
|
||||||
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
|
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
|
||||||
/// <param name="appConfig">Instance of <see cref="IConfiguration"/> interface.</param>
|
|
||||||
/// <param name="resourceFileManager">Instance of <see cref="IResourceFileManager"/> interface.</param>
|
|
||||||
/// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
|
|
||||||
public DashboardController(
|
public DashboardController(
|
||||||
ILogger<DashboardController> logger,
|
ILogger<DashboardController> logger,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost)
|
||||||
IConfiguration appConfig,
|
|
||||||
IResourceFileManager resourceFileManager,
|
|
||||||
IServerConfigurationManager serverConfigurationManager)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_appConfig = appConfig;
|
|
||||||
_resourceFileManager = resourceFileManager;
|
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path of the directory containing the static web interface content, or null if the server is not
|
|
||||||
/// hosting the web client.
|
|
||||||
/// </summary>
|
|
||||||
private string? WebClientUiPath => GetWebClientUiPath(_appConfig, _serverConfigurationManager);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the configuration pages.
|
/// Gets the configuration pages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -169,87 +152,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the robots.txt.
|
|
||||||
/// </summary>
|
|
||||||
/// <response code="200">Robots.txt returned.</response>
|
|
||||||
/// <returns>The robots.txt.</returns>
|
|
||||||
[HttpGet("robots.txt")]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
|
||||||
public ActionResult GetRobotsTxt()
|
|
||||||
{
|
|
||||||
return GetWebClientResource("robots.txt");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a resource from the web client.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="resourceName">The resource name.</param>
|
|
||||||
/// <response code="200">Web client returned.</response>
|
|
||||||
/// <response code="404">Server does not host a web client.</response>
|
|
||||||
/// <returns>The resource.</returns>
|
|
||||||
[HttpGet("web/{*resourceName}")]
|
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
||||||
public ActionResult GetWebClientResource([FromRoute] string resourceName)
|
|
||||||
{
|
|
||||||
if (!_appConfig.HostWebClient() || WebClientUiPath == null)
|
|
||||||
{
|
|
||||||
return NotFound("Server does not host a web client.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = resourceName;
|
|
||||||
var basePath = WebClientUiPath;
|
|
||||||
|
|
||||||
var requestPathAndQuery = Request.GetEncodedPathAndQuery();
|
|
||||||
// Bounce them to the startup wizard if it hasn't been completed yet
|
|
||||||
if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted
|
|
||||||
&& !requestPathAndQuery.Contains("wizard", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& requestPathAndQuery.Contains("index", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return Redirect("index.html?start=wizard#!/wizardstart.html");
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream = new FileStream(_resourceFileManager.GetResourcePath(basePath, path), FileMode.Open, FileAccess.Read);
|
|
||||||
return File(stream, MimeTypes.GetMimeType(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the favicon.
|
|
||||||
/// </summary>
|
|
||||||
/// <response code="200">Favicon.ico returned.</response>
|
|
||||||
/// <returns>The favicon.</returns>
|
|
||||||
[HttpGet("favicon.ico")]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
|
||||||
public ActionResult GetFavIcon()
|
|
||||||
{
|
|
||||||
return GetWebClientResource("favicon.ico");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path of the directory containing the static web interface content.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="appConfig">The app configuration.</param>
|
|
||||||
/// <param name="serverConfigManager">The server configuration manager.</param>
|
|
||||||
/// <returns>The directory path, or null if the server is not hosting the web client.</returns>
|
|
||||||
public static string? GetWebClientUiPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
|
|
||||||
{
|
|
||||||
if (!appConfig.HostWebClient())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
|
|
||||||
{
|
|
||||||
return serverConfigManager.Configuration.DashboardSourcePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverConfigManager.ApplicationPaths.WebPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin)
|
private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin)
|
||||||
{
|
{
|
||||||
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));
|
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));
|
||||||
|
|
|
@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
|
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
|
||||||
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
|
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
|
||||||
_displayPreferencesManager.SaveChanges(itemPreferences);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
|
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
|
||||||
|
@ -167,8 +166,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
itemPrefs.ViewType = viewType;
|
itemPrefs.ViewType = viewType;
|
||||||
}
|
}
|
||||||
|
|
||||||
_displayPreferencesManager.SaveChanges(existingDisplayPreferences);
|
_displayPreferencesManager.SaveChanges();
|
||||||
_displayPreferencesManager.SaveChanges(itemPrefs);
|
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna;
|
using Emby.Dlna;
|
||||||
using Emby.Dlna.Main;
|
using Emby.Dlna.Main;
|
||||||
|
@ -17,8 +18,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
[Route("Dlna")]
|
[Route("Dlna")]
|
||||||
public class DlnaServerController : BaseJellyfinApiController
|
public class DlnaServerController : BaseJellyfinApiController
|
||||||
{
|
{
|
||||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
|
||||||
|
|
||||||
private readonly IDlnaManager _dlnaManager;
|
private readonly IDlnaManager _dlnaManager;
|
||||||
private readonly IContentDirectory _contentDirectory;
|
private readonly IContentDirectory _contentDirectory;
|
||||||
private readonly IConnectionManager _connectionManager;
|
private readonly IConnectionManager _connectionManager;
|
||||||
|
@ -44,7 +43,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
|
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
|
||||||
[HttpGet("{serverId}/description")]
|
[HttpGet("{serverId}/description")]
|
||||||
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
|
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
|
||||||
[Produces(XMLContentType)]
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult GetDescriptionXml([FromRoute] string serverId)
|
public ActionResult GetDescriptionXml([FromRoute] string serverId)
|
||||||
{
|
{
|
||||||
|
@ -61,8 +60,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <response code="200">Dlna content directory returned.</response>
|
/// <response code="200">Dlna content directory returned.</response>
|
||||||
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
|
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
|
||||||
[HttpGet("{serverId}/ContentDirectory")]
|
[HttpGet("{serverId}/ContentDirectory")]
|
||||||
[HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
|
[HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
|
||||||
[Produces(XMLContentType)]
|
[HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
|
||||||
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||||
public ActionResult GetContentDirectory([FromRoute] string serverId)
|
public ActionResult GetContentDirectory([FromRoute] string serverId)
|
||||||
|
@ -76,8 +76,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="serverId">Server UUID.</param>
|
/// <param name="serverId">Server UUID.</param>
|
||||||
/// <returns>Dlna media receiver registrar xml.</returns>
|
/// <returns>Dlna media receiver registrar xml.</returns>
|
||||||
[HttpGet("{serverId}/MediaReceiverRegistrar")]
|
[HttpGet("{serverId}/MediaReceiverRegistrar")]
|
||||||
[HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
|
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
|
||||||
[Produces(XMLContentType)]
|
[HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
|
||||||
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||||
public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
|
public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
|
||||||
|
@ -91,8 +92,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="serverId">Server UUID.</param>
|
/// <param name="serverId">Server UUID.</param>
|
||||||
/// <returns>Dlna media receiver registrar xml.</returns>
|
/// <returns>Dlna media receiver registrar xml.</returns>
|
||||||
[HttpGet("{serverId}/ConnectionManager")]
|
[HttpGet("{serverId}/ConnectionManager")]
|
||||||
[HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
|
[HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
|
||||||
[Produces(XMLContentType)]
|
[HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
|
||||||
|
[Produces(MediaTypeNames.Text.Xml)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
|
||||||
public ActionResult GetConnectionManager([FromRoute] string serverId)
|
public ActionResult GetConnectionManager([FromRoute] string serverId)
|
||||||
|
|
|
@ -1354,15 +1354,20 @@ namespace Jellyfin.Api.Controllers
|
||||||
segmentFormat = "mpegts";
|
segmentFormat = "mpegts";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize > 128
|
||||||
|
? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
|
||||||
|
: "128";
|
||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
|
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
_encodingHelper.GetInputArgument(state, encodingOptions),
|
_encodingHelper.GetInputArgument(state, encodingOptions),
|
||||||
threads,
|
threads,
|
||||||
mapArgs,
|
mapArgs,
|
||||||
GetVideoArguments(state, encodingOptions, startNumber),
|
GetVideoArguments(state, encodingOptions, startNumber),
|
||||||
GetAudioArguments(state, encodingOptions),
|
GetAudioArguments(state, encodingOptions),
|
||||||
|
maxMuxingQueueSize,
|
||||||
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
|
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
|
||||||
segmentFormat,
|
segmentFormat,
|
||||||
startNumberParam,
|
startNumberParam,
|
||||||
|
|
|
@ -16,6 +16,7 @@ using Jellyfin.Api.Models.LiveTvDtos;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
|
@ -1069,7 +1070,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public async Task<ActionResult> GetSchedulesDirectCountries()
|
public async Task<ActionResult> GetSchedulesDirectCountries()
|
||||||
{
|
{
|
||||||
var client = _httpClientFactory.CreateClient();
|
var client = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||||
// https://json.schedulesdirect.org/20141201/available/countries
|
// https://json.schedulesdirect.org/20141201/available/countries
|
||||||
// Can't dispose the response as it's required up the call chain.
|
// Can't dispose the response as it's required up the call chain.
|
||||||
var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
|
var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
@ -244,7 +245,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
|
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
|
||||||
{
|
{
|
||||||
var httpClient = _httpClientFactory.CreateClient();
|
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||||
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
|
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
|
||||||
var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
|
var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
|
||||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||||
|
|
|
@ -11,6 +11,7 @@ using Jellyfin.Api.Extensions;
|
||||||
using Jellyfin.Api.Helpers;
|
using Jellyfin.Api.Helpers;
|
||||||
using Jellyfin.Api.Models.StreamingDtos;
|
using Jellyfin.Api.Models.StreamingDtos;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
@ -233,7 +234,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
.First();
|
.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = primaryVersion.LinkedAlternateVersions.ToList();
|
var alternateVersionsOfPrimary = primaryVersion.LinkedAlternateVersions.ToList();
|
||||||
|
|
||||||
foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
|
foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
|
||||||
{
|
{
|
||||||
|
@ -241,17 +242,20 @@ namespace Jellyfin.Api.Controllers
|
||||||
|
|
||||||
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
list.Add(new LinkedChild
|
if (!alternateVersionsOfPrimary.Any(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
alternateVersionsOfPrimary.Add(new LinkedChild
|
||||||
{
|
{
|
||||||
Path = item.Path,
|
Path = item.Path,
|
||||||
ItemId = item.Id
|
ItemId = item.Id
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var linkedItem in item.LinkedAlternateVersions)
|
foreach (var linkedItem in item.LinkedAlternateVersions)
|
||||||
{
|
{
|
||||||
if (!list.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase)))
|
if (!alternateVersionsOfPrimary.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
list.Add(linkedItem);
|
alternateVersionsOfPrimary.Add(linkedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +266,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
primaryVersion.LinkedAlternateVersions = list.ToArray();
|
primaryVersion.LinkedAlternateVersions = alternateVersionsOfPrimary.ToArray();
|
||||||
await primaryVersion.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await primaryVersion.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
@ -470,7 +474,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
var httpClient = _httpClientFactory.CreateClient();
|
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||||
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
|
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Models.StreamingDtos;
|
using Jellyfin.Api.Models.StreamingDtos;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
@ -138,7 +139,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
{
|
{
|
||||||
StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
|
StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
|
||||||
|
|
||||||
var httpClient = _httpClientFactory.CreateClient();
|
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||||
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false);
|
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
return new NoContentResult();
|
return new NoContentResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PhysicalFileResult(path, contentType);
|
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.7" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
|
||||||
|
|
||||||
namespace Jellyfin.Api
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Route prefixing for ASP.NET MVC.
|
|
||||||
/// </summary>
|
|
||||||
public static class MvcRoutePrefix
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Adds route prefixes to the MVC conventions.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="opts">The MVC options.</param>
|
|
||||||
/// <param name="prefixes">The list of prefixes.</param>
|
|
||||||
public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes)
|
|
||||||
{
|
|
||||||
opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RoutePrefixConvention : IApplicationModelConvention
|
|
||||||
{
|
|
||||||
private readonly AttributeRouteModel[] _routePrefixes;
|
|
||||||
|
|
||||||
public RoutePrefixConvention(IEnumerable<string> prefixes)
|
|
||||||
{
|
|
||||||
_routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Apply(ApplicationModel application)
|
|
||||||
{
|
|
||||||
foreach (var controller in application.Controllers)
|
|
||||||
{
|
|
||||||
if (controller.Selectors == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newSelectors = new List<SelectorModel>();
|
|
||||||
foreach (var selector in controller.Selectors)
|
|
||||||
{
|
|
||||||
newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector)
|
|
||||||
{
|
|
||||||
AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.Selectors.Clear();
|
|
||||||
newSelectors.ForEach(selector => controller.Selectors.Add(selector));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
@ -11,7 +9,7 @@ namespace Jellyfin.Data.Entities
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity referencing an activity log entry.
|
/// An entity referencing an activity log entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ActivityLog : IHasConcurrencyToken
|
public class ActivityLog : IHasConcurrencyToken
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ActivityLog"/> class.
|
/// Initializes a new instance of the <see cref="ActivityLog"/> class.
|
||||||
|
@ -32,13 +30,11 @@ namespace Jellyfin.Data.Entities
|
||||||
throw new ArgumentNullException(nameof(type));
|
throw new ArgumentNullException(nameof(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Name = name;
|
Name = name;
|
||||||
this.Type = type;
|
Type = type;
|
||||||
this.UserId = userId;
|
UserId = userId;
|
||||||
this.DateCreated = DateTime.UtcNow;
|
DateCreated = DateTime.UtcNow;
|
||||||
this.LogSeverity = LogLevel.Trace;
|
LogSeverity = LogLevel.Trace;
|
||||||
|
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -47,38 +43,21 @@ namespace Jellyfin.Data.Entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ActivityLog()
|
protected ActivityLog()
|
||||||
{
|
{
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Static create function (for use in LINQ queries, etc.)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <param name="userId">The user's id.</param>
|
|
||||||
/// <returns>The new <see cref="ActivityLog"/> instance.</returns>
|
|
||||||
public static ActivityLog Create(string name, string type, Guid userId)
|
|
||||||
{
|
|
||||||
return new ActivityLog(name, type, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
* Properties
|
|
||||||
*************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the identity of this instance.
|
/// Gets or sets the identity of this instance.
|
||||||
/// This is the key in the backing database.
|
/// This is the key in the backing database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Key]
|
|
||||||
[Required]
|
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int Id { get; protected set; }
|
public int Id { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets or sets the name.
|
||||||
/// Required, Max length = 512.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Required, Max length = 512.
|
||||||
|
/// </remarks>
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(512)]
|
[MaxLength(512)]
|
||||||
[StringLength(512)]
|
[StringLength(512)]
|
||||||
|
@ -86,24 +65,30 @@ namespace Jellyfin.Data.Entities
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the overview.
|
/// Gets or sets the overview.
|
||||||
/// Max length = 512.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Max length = 512.
|
||||||
|
/// </remarks>
|
||||||
[MaxLength(512)]
|
[MaxLength(512)]
|
||||||
[StringLength(512)]
|
[StringLength(512)]
|
||||||
public string Overview { get; set; }
|
public string Overview { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the short overview.
|
/// Gets or sets the short overview.
|
||||||
/// Max length = 512.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Max length = 512.
|
||||||
|
/// </remarks>
|
||||||
[MaxLength(512)]
|
[MaxLength(512)]
|
||||||
[StringLength(512)]
|
[StringLength(512)]
|
||||||
public string ShortOverview { get; set; }
|
public string ShortOverview { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type.
|
/// Gets or sets the type.
|
||||||
/// Required, Max length = 256.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Required, Max length = 256.
|
||||||
|
/// </remarks>
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(256)]
|
[MaxLength(256)]
|
||||||
[StringLength(256)]
|
[StringLength(256)]
|
||||||
|
@ -111,43 +96,42 @@ namespace Jellyfin.Data.Entities
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the user id.
|
/// Gets or sets the user id.
|
||||||
/// Required.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
/// <remarks>
|
||||||
|
/// Required.
|
||||||
|
/// </remarks>
|
||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the item id.
|
/// Gets or sets the item id.
|
||||||
/// Max length = 256.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Max length = 256.
|
||||||
|
/// </remarks>
|
||||||
[MaxLength(256)]
|
[MaxLength(256)]
|
||||||
[StringLength(256)]
|
[StringLength(256)]
|
||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the date created. This should be in UTC.
|
/// Gets or sets the date created. This should be in UTC.
|
||||||
/// Required.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
/// <remarks>
|
||||||
|
/// Required.
|
||||||
|
/// </remarks>
|
||||||
public DateTime DateCreated { get; set; }
|
public DateTime DateCreated { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
|
/// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
|
||||||
/// Required.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
/// <remarks>
|
||||||
|
/// Required.
|
||||||
|
/// </remarks>
|
||||||
public LogLevel LogSeverity { get; set; }
|
public LogLevel LogSeverity { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets or sets the row version.
|
|
||||||
/// Required, ConcurrencyToken.
|
|
||||||
/// </summary>
|
|
||||||
[ConcurrencyCheck]
|
[ConcurrencyCheck]
|
||||||
[Required]
|
|
||||||
public uint RowVersion { get; set; }
|
public uint RowVersion { get; set; }
|
||||||
|
|
||||||
partial void Init();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void OnSavingChanges()
|
public void OnSavingChanges()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
@ -13,11 +12,10 @@ namespace Jellyfin.Data.Entities
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity representing a group.
|
/// An entity representing a group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class Group : IHasPermissions, IHasConcurrencyToken
|
public class Group : IHasPermissions, IHasConcurrencyToken
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Group"/> class.
|
/// Initializes a new instance of the <see cref="Group"/> class.
|
||||||
/// Public constructor with required data.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the group.</param>
|
/// <param name="name">The name of the group.</param>
|
||||||
public Group(string name)
|
public Group(string name)
|
||||||
|
@ -31,33 +29,25 @@ namespace Jellyfin.Data.Entities
|
||||||
Id = Guid.NewGuid();
|
Id = Guid.NewGuid();
|
||||||
|
|
||||||
Permissions = new HashSet<Permission>();
|
Permissions = new HashSet<Permission>();
|
||||||
ProviderMappings = new HashSet<ProviderMapping>();
|
|
||||||
Preferences = new HashSet<Preference>();
|
Preferences = new HashSet<Preference>();
|
||||||
|
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Group"/> class.
|
/// Initializes a new instance of the <see cref="Group"/> class.
|
||||||
/// Default constructor. Protected due to required properties, but present because EF needs it.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Default constructor. Protected due to required properties, but present because EF needs it.
|
||||||
|
/// </remarks>
|
||||||
protected Group()
|
protected Group()
|
||||||
{
|
{
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
* Properties
|
|
||||||
*************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the id of this group.
|
/// Gets or sets the id of this group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Identity, Indexed, Required.
|
/// Identity, Indexed, Required.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Key]
|
|
||||||
[Required]
|
|
||||||
public Guid Id { get; protected set; }
|
public Guid Id { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -71,42 +61,19 @@ namespace Jellyfin.Data.Entities
|
||||||
[StringLength(255)]
|
[StringLength(255)]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets or sets the row version.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Required, Concurrency Token.
|
|
||||||
/// </remarks>
|
|
||||||
[ConcurrencyCheck]
|
[ConcurrencyCheck]
|
||||||
[Required]
|
|
||||||
public uint RowVersion { get; set; }
|
public uint RowVersion { get; set; }
|
||||||
|
|
||||||
public void OnSavingChanges()
|
/// <summary>
|
||||||
{
|
/// Gets or sets a collection containing the group's permissions.
|
||||||
RowVersion++;
|
/// </summary>
|
||||||
}
|
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
* Navigation properties
|
|
||||||
*************************************************************************/
|
|
||||||
|
|
||||||
[ForeignKey("Permission_GroupPermissions_Id")]
|
|
||||||
public virtual ICollection<Permission> Permissions { get; protected set; }
|
public virtual ICollection<Permission> Permissions { get; protected set; }
|
||||||
|
|
||||||
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
|
|
||||||
public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
|
|
||||||
|
|
||||||
[ForeignKey("Preference_Preferences_Id")]
|
|
||||||
public virtual ICollection<Preference> Preferences { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Static create function (for use in LINQ queries, etc.)
|
/// Gets or sets a collection containing the group's preferences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of this group.</param>
|
public virtual ICollection<Preference> Preferences { get; protected set; }
|
||||||
public static Group Create(string name)
|
|
||||||
{
|
|
||||||
return new Group(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool HasPermission(PermissionKind kind)
|
public bool HasPermission(PermissionKind kind)
|
||||||
|
@ -120,6 +87,10 @@ namespace Jellyfin.Data.Entities
|
||||||
Permissions.First(p => p.Kind == kind).Value = value;
|
Permissions.First(p => p.Kind == kind).Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void Init();
|
/// <inheritdoc />
|
||||||
|
public void OnSavingChanges()
|
||||||
|
{
|
||||||
|
RowVersion++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,65 @@
|
||||||
#pragma warning disable CS1591
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace Jellyfin.Data.Entities
|
namespace Jellyfin.Data.Entities
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entity representing an image.
|
||||||
|
/// </summary>
|
||||||
public class ImageInfo
|
public class ImageInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ImageInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
public ImageInfo(string path)
|
public ImageInfo(string path)
|
||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
LastModified = DateTime.UtcNow;
|
LastModified = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Key]
|
/// <summary>
|
||||||
[Required]
|
/// Initializes a new instance of the <see cref="ImageInfo"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Default constructor. Protected due to required properties, but present because EF needs it.
|
||||||
|
/// </remarks>
|
||||||
|
protected ImageInfo()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the id.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Identity, Indexed, Required.
|
||||||
|
/// </remarks>
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int Id { get; protected set; }
|
public int Id { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user id.
|
||||||
|
/// </summary>
|
||||||
public Guid? UserId { get; protected set; }
|
public Guid? UserId { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the path of the image.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Required.
|
||||||
|
/// </remarks>
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(512)]
|
[MaxLength(512)]
|
||||||
[StringLength(512)]
|
[StringLength(512)]
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
|
||||||
[Required]
|
/// <summary>
|
||||||
|
/// Gets or sets the date last modified.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Required.
|
||||||
|
/// </remarks>
|
||||||
public DateTime LastModified { get; set; }
|
public DateTime LastModified { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
#pragma warning disable CS1591
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
|
||||||
namespace Jellyfin.Data.Entities
|
namespace Jellyfin.Data.Entities
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entity that represents a user's display preferences for a specific item.
|
||||||
|
/// </summary>
|
||||||
public class ItemDisplayPreferences
|
public class ItemDisplayPreferences
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
@ -8,7 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity containing metadata for a book.
|
/// An entity containing metadata for a book.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BookMetadata : Metadata, IHasCompanies
|
public class BookMetadata : ItemMetadata, IHasCompanies
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BookMetadata"/> class.
|
/// Initializes a new instance of the <see cref="BookMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
|
@ -73,7 +73,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// Gets or sets the next item in the collection.
|
/// Gets or sets the next item in the collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// TODO check if this properly updated dependant and has the proper principal relationship
|
/// TODO check if this properly updated dependant and has the proper principal relationship.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual CollectionItem Next { get; set; }
|
public virtual CollectionItem Next { get; set; }
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// Gets or sets the previous item in the collection.
|
/// Gets or sets the previous item in the collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// TODO check if this properly updated dependant and has the proper principal relationship
|
/// TODO check if this properly updated dependant and has the proper principal relationship.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual CollectionItem Previous { get; set; }
|
public virtual CollectionItem Previous { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity holding metadata for a <see cref="Company"/>.
|
/// An entity holding metadata for a <see cref="Company"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CompanyMetadata : Metadata
|
public class CompanyMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
|
/// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity containing metadata for a custom item.
|
/// An entity containing metadata for a custom item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CustomItemMetadata : Metadata
|
public class CustomItemMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
|
/// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity containing metadata for an <see cref="Episode"/>.
|
/// An entity containing metadata for an <see cref="Episode"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EpisodeMetadata : Metadata
|
public class EpisodeMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
|
/// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// Initializes a new instance of the <see cref="Genre"/> class.
|
/// Initializes a new instance of the <see cref="Genre"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <param name="metadata">The metadata.</param>
|
/// <param name="itemMetadata">The metadata.</param>
|
||||||
public Genre(string name, Metadata metadata)
|
public Genre(string name, ItemMetadata itemMetadata)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrEmpty(name))
|
||||||
{
|
{
|
||||||
|
@ -24,12 +24,12 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
|
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
if (metadata == null)
|
if (itemMetadata == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(metadata));
|
throw new ArgumentNullException(nameof(itemMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Genres.Add(this);
|
itemMetadata.Genres.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
@ -9,14 +11,14 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An abstract class that holds metadata.
|
/// An abstract class that holds metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Metadata : IHasArtwork, IHasConcurrencyToken
|
public abstract class ItemMetadata : IHasArtwork, IHasConcurrencyToken
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Metadata"/> class.
|
/// Initializes a new instance of the <see cref="ItemMetadata"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="title">The title or name of the object.</param>
|
/// <param name="title">The title or name of the object.</param>
|
||||||
/// <param name="language">ISO-639-3 3-character language codes.</param>
|
/// <param name="language">ISO-639-3 3-character language codes.</param>
|
||||||
protected Metadata(string title, string language)
|
protected ItemMetadata(string title, string language)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(title))
|
if (string.IsNullOrEmpty(title))
|
||||||
{
|
{
|
||||||
|
@ -41,12 +43,12 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Metadata"/> class.
|
/// Initializes a new instance of the <see cref="ItemMetadata"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Default constructor. Protected due to being abstract.
|
/// Default constructor. Protected due to being abstract.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected Metadata()
|
protected ItemMetadata()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
|
@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
|
/// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="providerId">The provider id.</param>
|
/// <param name="providerId">The provider id.</param>
|
||||||
/// <param name="metadata">The metadata entity.</param>
|
/// <param name="itemMetadata">The metadata entity.</param>
|
||||||
public MetadataProviderId(string providerId, Metadata metadata)
|
public MetadataProviderId(string providerId, ItemMetadata itemMetadata)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(providerId))
|
if (string.IsNullOrEmpty(providerId))
|
||||||
{
|
{
|
||||||
|
@ -24,12 +24,12 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
|
|
||||||
ProviderId = providerId;
|
ProviderId = providerId;
|
||||||
|
|
||||||
if (metadata == null)
|
if (itemMetadata == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(metadata));
|
throw new ArgumentNullException(nameof(itemMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Sources.Add(this);
|
itemMetadata.Sources.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
@ -8,7 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity holding the metadata for a movie.
|
/// An entity holding the metadata for a movie.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MovieMetadata : Metadata, IHasCompanies
|
public class MovieMetadata : ItemMetadata, IHasCompanies
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MovieMetadata"/> class.
|
/// Initializes a new instance of the <see cref="MovieMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Jellyfin.Data.Entities.Libraries
|
namespace Jellyfin.Data.Entities.Libraries
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
@ -6,7 +8,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity holding the metadata for a music album.
|
/// An entity holding the metadata for a music album.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MusicAlbumMetadata : Metadata
|
public class MusicAlbumMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.
|
/// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
@ -16,17 +18,17 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// Initializes a new instance of the <see cref="PersonRole"/> class.
|
/// Initializes a new instance of the <see cref="PersonRole"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">The role type.</param>
|
/// <param name="type">The role type.</param>
|
||||||
/// <param name="metadata">The metadata.</param>
|
/// <param name="itemMetadata">The metadata.</param>
|
||||||
public PersonRole(PersonRoleType type, Metadata metadata)
|
public PersonRole(PersonRoleType type, ItemMetadata itemMetadata)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
|
||||||
if (metadata == null)
|
if (itemMetadata == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(metadata));
|
throw new ArgumentNullException(nameof(itemMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.PersonRoles.Add(this);
|
itemMetadata.PersonRoles.Add(this);
|
||||||
|
|
||||||
Sources = new HashSet<MetadataProviderId>();
|
Sources = new HashSet<MetadataProviderId>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity that holds metadata for a photo.
|
/// An entity that holds metadata for a photo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PhotoMetadata : Metadata
|
public class PhotoMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PhotoMetadata"/> class.
|
/// Initializes a new instance of the <see cref="PhotoMetadata"/> class.
|
||||||
|
|
|
@ -14,17 +14,17 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// Initializes a new instance of the <see cref="Rating"/> class.
|
/// Initializes a new instance of the <see cref="Rating"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value.</param>
|
/// <param name="value">The value.</param>
|
||||||
/// <param name="metadata">The metadata.</param>
|
/// <param name="itemMetadata">The metadata.</param>
|
||||||
public Rating(double value, Metadata metadata)
|
public Rating(double value, ItemMetadata itemMetadata)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
|
|
||||||
if (metadata == null)
|
if (itemMetadata == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(metadata));
|
throw new ArgumentNullException(nameof(itemMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata.Ratings.Add(this);
|
itemMetadata.Ratings.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity that holds metadata for seasons.
|
/// An entity that holds metadata for seasons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SeasonMetadata : Metadata
|
public class SeasonMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SeasonMetadata"/> class.
|
/// Initializes a new instance of the <see cref="SeasonMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
@ -9,7 +11,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity representing series metadata.
|
/// An entity representing series metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SeriesMetadata : Metadata, IHasCompanies
|
public class SeriesMetadata : ItemMetadata, IHasCompanies
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SeriesMetadata"/> class.
|
/// Initializes a new instance of the <see cref="SeriesMetadata"/> class.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CA2227
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity holding metadata for a track.
|
/// An entity holding metadata for a track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TrackMetadata : Metadata
|
public class TrackMetadata : ItemMetadata
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TrackMetadata"/> class.
|
/// Initializes a new instance of the <see cref="TrackMetadata"/> class.
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
@ -10,7 +8,7 @@ namespace Jellyfin.Data.Entities
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An entity representing whether the associated user has a specific permission.
|
/// An entity representing whether the associated user has a specific permission.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class Permission : IHasConcurrencyToken
|
public class Permission : IHasConcurrencyToken
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Permission"/> class.
|
/// Initializes a new instance of the <see cref="Permission"/> class.
|
||||||
|
@ -22,8 +20,6 @@ namespace Jellyfin.Data.Entities
|
||||||
{
|
{
|
||||||
Kind = kind;
|
Kind = kind;
|
||||||
Value = value;
|
Value = value;
|
||||||
|
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -32,21 +28,14 @@ namespace Jellyfin.Data.Entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Permission()
|
protected Permission()
|
||||||
{
|
{
|
||||||
Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
* Properties
|
|
||||||
*************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the id of this permission.
|
/// Gets or sets the id of this permission.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Identity, Indexed, Required.
|
/// Identity, Indexed, Required.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Key]
|
|
||||||
[Required]
|
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int Id { get; protected set; }
|
public int Id { get; protected set; }
|
||||||
|
|
||||||
|
@ -56,7 +45,6 @@ namespace Jellyfin.Data.Entities
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Required.
|
/// Required.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Required]
|
|
||||||
public PermissionKind Kind { get; protected set; }
|
public PermissionKind Kind { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -65,36 +53,16 @@ namespace Jellyfin.Data.Entities
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Required.
|
/// Required.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Required]
|
|
||||||
public bool Value { get; set; }
|
public bool Value { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets or sets the row version.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Required, ConcurrencyToken.
|
|
||||||
/// </remarks>
|
|
||||||
[ConcurrencyCheck]
|
[ConcurrencyCheck]
|
||||||
[Required]
|
|
||||||
public uint RowVersion { get; set; }
|
public uint RowVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Static create function (for use in LINQ queries, etc.)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="kind">The permission kind.</param>
|
|
||||||
/// <param name="value">The value of this permission.</param>
|
|
||||||
/// <returns>The newly created instance.</returns>
|
|
||||||
public static Permission Create(PermissionKind kind, bool value)
|
|
||||||
{
|
|
||||||
return new Permission(kind, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnSavingChanges()
|
public void OnSavingChanges()
|
||||||
{
|
{
|
||||||
RowVersion++;
|
RowVersion++;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void Init();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,18 +31,12 @@ namespace Jellyfin.Data.Entities
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************************
|
|
||||||
* Properties
|
|
||||||
*************************************************************************/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the id of this preference.
|
/// Gets or sets the id of this preference.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Identity, Indexed, Required.
|
/// Identity, Indexed, Required.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Key]
|
|
||||||
[Required]
|
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int Id { get; protected set; }
|
public int Id { get; protected set; }
|
||||||
|
|
||||||
|
@ -52,7 +46,6 @@ namespace Jellyfin.Data.Entities
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Required.
|
/// Required.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Required]
|
|
||||||
public PreferenceKind Kind { get; protected set; }
|
public PreferenceKind Kind { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -66,27 +59,10 @@ namespace Jellyfin.Data.Entities
|
||||||
[StringLength(65535)]
|
[StringLength(65535)]
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets or sets the row version.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Required, ConcurrencyToken.
|
|
||||||
/// </remarks>
|
|
||||||
[ConcurrencyCheck]
|
[ConcurrencyCheck]
|
||||||
[Required]
|
|
||||||
public uint RowVersion { get; set; }
|
public uint RowVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Static create function (for use in LINQ queries, etc.)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="kind">The preference kind.</param>
|
|
||||||
/// <param name="value">The value.</param>
|
|
||||||
/// <returns>The new instance.</returns>
|
|
||||||
public static Preference Create(PreferenceKind kind, string value)
|
|
||||||
{
|
|
||||||
return new Preference(kind, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnSavingChanges()
|
public void OnSavingChanges()
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user