mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-24 14:08:44 -04:00
Merge remote-tracking branch 'upstream/master' into NetworkPR2
This commit is contained in:
commit
ebe650afa9
157 changed files with 3014 additions and 6328 deletions
62
.ci/azure-pipelines-api-client.yml
Normal file
62
.ci/azure-pipelines-api-client.yml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
parameters:
|
||||||
|
- name: LinuxImage
|
||||||
|
type: string
|
||||||
|
default: "ubuntu-latest"
|
||||||
|
- name: GeneratorVersion
|
||||||
|
type: string
|
||||||
|
default: "5.0.0-beta2"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: GenerateApiClients
|
||||||
|
displayName: 'Generate Api Clients'
|
||||||
|
dependsOn: Test
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: 'Download OpenAPI Spec Artifact'
|
||||||
|
inputs:
|
||||||
|
source: 'current'
|
||||||
|
artifact: "OpenAPI Spec"
|
||||||
|
path: "$(System.ArtifactsDirectory)/openapispec"
|
||||||
|
runVersion: "latest"
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: 'Download OpenApi Generator'
|
||||||
|
inputs:
|
||||||
|
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
|
||||||
|
|
||||||
|
# Generate npm api client
|
||||||
|
# Unstable
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: 'Build unstable typescript axios client'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
inputs:
|
||||||
|
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory) $(Build.BuildNumber)"
|
||||||
|
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: 'Publish unstable typescript axios client'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
inputs:
|
||||||
|
command: publish
|
||||||
|
publishRegistry: useFeed
|
||||||
|
publishFeed: 'unstable@Local'
|
||||||
|
workingDir: ./apiclient/generated/typescript/axios
|
||||||
|
|
||||||
|
# Stable
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: 'Build stable typescript axios client'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
inputs:
|
||||||
|
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
|
||||||
|
|
||||||
|
- task: Npm@1
|
||||||
|
displayName: 'Publish stable typescript axios client'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
inputs:
|
||||||
|
command: publish
|
||||||
|
publishRegistry: useExternalRegistry
|
||||||
|
publishEndpoint: 'jellyfin-bot for NPM'
|
||||||
|
workingDir: ./apiclient/generated/typescript/axios
|
|
@ -63,7 +63,38 @@ jobs:
|
||||||
sshEndpoint: repository
|
sshEndpoint: repository
|
||||||
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
|
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
|
||||||
contents: '**'
|
contents: '**'
|
||||||
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
|
|
||||||
|
- job: OpenAPISpec
|
||||||
|
dependsOn: Test
|
||||||
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/master'),startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
|
||||||
|
displayName: 'Push OpenAPI Spec to repository'
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: 'Download OpenAPI Spec'
|
||||||
|
inputs:
|
||||||
|
source: 'current'
|
||||||
|
artifact: "OpenAPI Spec"
|
||||||
|
path: "$(System.ArtifactsDirectory)/openapispec"
|
||||||
|
runVersion: "latest"
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Create target directory on repository server'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
runOptions: 'inline'
|
||||||
|
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)'
|
||||||
|
|
||||||
|
- task: CopyFilesOverSSH@0
|
||||||
|
displayName: 'Upload artifacts to repository server'
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
sourceFolder: '$(System.ArtifactsDirectory)/openapispec'
|
||||||
|
contents: 'openapi.json'
|
||||||
|
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)'
|
||||||
|
|
||||||
- job: BuildDocker
|
- job: BuildDocker
|
||||||
displayName: 'Build Docker'
|
displayName: 'Build Docker'
|
||||||
|
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
inputs:
|
inputs:
|
||||||
command: "test"
|
command: "test"
|
||||||
projects: ${{ parameters.TestProjects }}
|
projects: ${{ parameters.TestProjects }}
|
||||||
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
|
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal'
|
||||||
publishTestResults: true
|
publishTestResults: true
|
||||||
testRunTitle: $(Agent.JobName)
|
testRunTitle: $(Agent.JobName)
|
||||||
workingDirectory: "$(Build.SourcesDirectory)"
|
workingDirectory: "$(Build.SourcesDirectory)"
|
||||||
|
|
|
@ -34,6 +34,12 @@ jobs:
|
||||||
Linux: 'ubuntu-latest'
|
Linux: 'ubuntu-latest'
|
||||||
Windows: 'windows-latest'
|
Windows: 'windows-latest'
|
||||||
macOS: 'macos-latest'
|
macOS: 'macos-latest'
|
||||||
|
|
||||||
|
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
||||||
|
- template: azure-pipelines-test.yml
|
||||||
|
parameters:
|
||||||
|
ImageNames:
|
||||||
|
Linux: 'ubuntu-latest'
|
||||||
|
|
||||||
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
|
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
|
||||||
- template: azure-pipelines-abi.yml
|
- template: azure-pipelines-abi.yml
|
||||||
|
@ -55,3 +61,6 @@ jobs:
|
||||||
|
|
||||||
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
||||||
- template: azure-pipelines-package.yml
|
- template: azure-pipelines-package.yml
|
||||||
|
|
||||||
|
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
||||||
|
- template: azure-pipelines-api-client.yml
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -276,3 +276,4 @@ BenchmarkDotNet.Artifacts
|
||||||
web/
|
web/
|
||||||
web-src.*
|
web-src.*
|
||||||
MediaBrowser.WebDashboard/jellyfin-web
|
MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
apiclient/generated
|
||||||
|
|
|
@ -137,6 +137,7 @@
|
||||||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||||
- [Pusta](https://github.com/pusta)
|
- [Pusta](https://github.com/pusta)
|
||||||
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
||||||
|
- [skyfrk](https://github.com/skyfrk)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
|
@ -811,7 +811,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
|
public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -823,17 +823,17 @@ namespace Emby.Dlna.PlayTo
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
|
if (name == SessionMessageType.Play)
|
||||||
{
|
{
|
||||||
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
if (name == SessionMessageType.PlayState)
|
||||||
{
|
{
|
||||||
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
if (name == SessionMessageType.GeneralCommand)
|
||||||
{
|
{
|
||||||
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,15 +209,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
SupportedCommands = new[]
|
SupportedCommands = new[]
|
||||||
{
|
{
|
||||||
GeneralCommandType.VolumeDown.ToString(),
|
GeneralCommandType.VolumeDown,
|
||||||
GeneralCommandType.VolumeUp.ToString(),
|
GeneralCommandType.VolumeUp,
|
||||||
GeneralCommandType.Mute.ToString(),
|
GeneralCommandType.Mute,
|
||||||
GeneralCommandType.Unmute.ToString(),
|
GeneralCommandType.Unmute,
|
||||||
GeneralCommandType.ToggleMute.ToString(),
|
GeneralCommandType.ToggleMute,
|
||||||
GeneralCommandType.SetVolume.ToString(),
|
GeneralCommandType.SetVolume,
|
||||||
GeneralCommandType.SetAudioStreamIndex.ToString(),
|
GeneralCommandType.SetAudioStreamIndex,
|
||||||
GeneralCommandType.SetSubtitleStreamIndex.ToString(),
|
GeneralCommandType.SetSubtitleStreamIndex,
|
||||||
GeneralCommandType.PlayMediaSource.ToString()
|
GeneralCommandType.PlayMediaSource
|
||||||
},
|
},
|
||||||
|
|
||||||
SupportsMediaControl = true
|
SupportsMediaControl = true
|
||||||
|
|
|
@ -101,6 +101,7 @@ using MediaBrowser.Model.Tasks;
|
||||||
using MediaBrowser.Providers.Chapters;
|
using MediaBrowser.Providers.Chapters;
|
||||||
using MediaBrowser.Providers.Manager;
|
using MediaBrowser.Providers.Manager;
|
||||||
using MediaBrowser.Providers.Plugins.TheTvdb;
|
using MediaBrowser.Providers.Plugins.TheTvdb;
|
||||||
|
using MediaBrowser.Providers.Plugins.Tmdb;
|
||||||
using MediaBrowser.Providers.Subtitles;
|
using MediaBrowser.Providers.Subtitles;
|
||||||
using MediaBrowser.XbmcMetadata.Providers;
|
using MediaBrowser.XbmcMetadata.Providers;
|
||||||
using Microsoft.AspNetCore.DataProtection.Repositories;
|
using Microsoft.AspNetCore.DataProtection.Repositories;
|
||||||
|
@ -549,6 +550,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
ServiceCollection.AddSingleton(_fileSystemManager);
|
||||||
ServiceCollection.AddSingleton<TvdbClientManager>();
|
ServiceCollection.AddSingleton<TvdbClientManager>();
|
||||||
|
ServiceCollection.AddSingleton<TmdbClientManager>();
|
||||||
|
|
||||||
ServiceCollection.AddSingleton(NetManager);
|
ServiceCollection.AddSingleton(NetManager);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
@ -105,7 +106,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_sessionManager.SendMessageToAdminSessions("RefreshProgress", dict, CancellationToken.None);
|
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_sessionManager.SendMessageToAdminSessions("RefreshProgress", collectionFolderDict, CancellationToken.None);
|
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -345,7 +346,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, "LibraryChanged", info, cancellationToken).ConfigureAwait(false);
|
await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
@ -46,25 +47,25 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false);
|
await SendMessage(SessionMessageType.SeriesTimerCreated, e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
|
await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false);
|
await SendMessage(SessionMessageType.SeriesTimerCancelled, e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e)
|
private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e)
|
||||||
{
|
{
|
||||||
await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false);
|
await SendMessage(SessionMessageType.TimerCancelled, e.Argument).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendMessage(string name, TimerEventInfo info)
|
private async Task SendMessage(SessionMessageType name, TimerEventInfo info)
|
||||||
{
|
{
|
||||||
var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
|
var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList();
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
private Task SendNotifications(Guid userId, List<BaseItem> changedItems, CancellationToken cancellationToken)
|
private Task SendNotifications(Guid userId, List<BaseItem> changedItems, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, "UserDataChanged", () => GetUserDataChangeInfo(userId, changedItems), cancellationToken);
|
return _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.UserDataChanged, () => GetUserDataChangeInfo(userId, changedItems), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List<BaseItem> changedItems)
|
private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List<BaseItem> changedItems)
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Json;
|
using MediaBrowser.Common.Json;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
Connection = this
|
Connection = this
|
||||||
};
|
};
|
||||||
|
|
||||||
if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
|
if (info.MessageType == SessionMessageType.KeepAlive)
|
||||||
{
|
{
|
||||||
await SendKeepAliveResponse().ConfigureAwait(false);
|
await SendKeepAliveResponse().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
new WebSocketMessage<string>
|
new WebSocketMessage<string>
|
||||||
{
|
{
|
||||||
MessageId = Guid.NewGuid(),
|
MessageId = Guid.NewGuid(),
|
||||||
MessageType = "KeepAlive"
|
MessageType = SessionMessageType.KeepAlive
|
||||||
}, CancellationToken.None);
|
}, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsEnabled => false;
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsLogged => true;
|
public bool IsLogged => true;
|
||||||
|
|
|
@ -1064,10 +1064,10 @@ namespace Emby.Server.Implementations.Session
|
||||||
AssertCanControl(session, controllingSession);
|
AssertCanControl(session, controllingSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SendMessageToSession(session, "GeneralCommand", command, cancellationToken);
|
return SendMessageToSession(session, SessionMessageType.GeneralCommand, command, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken)
|
private static async Task SendMessageToSession<T>(SessionInfo session, SessionMessageType name, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var controllers = session.SessionControllers;
|
var controllers = session.SessionControllers;
|
||||||
var messageId = Guid.NewGuid();
|
var messageId = Guid.NewGuid();
|
||||||
|
@ -1078,7 +1078,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, string name, T data, CancellationToken cancellationToken)
|
private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, SessionMessageType name, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IEnumerable<Task> GetTasks()
|
IEnumerable<Task> GetTasks()
|
||||||
{
|
{
|
||||||
|
@ -1178,7 +1178,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
|
await SendMessageToSession(session, SessionMessageType.Play, command, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1186,7 +1186,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
var session = GetSessionToRemoteControl(sessionId);
|
var session = GetSessionToRemoteControl(sessionId);
|
||||||
await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false);
|
await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1194,7 +1194,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
var session = GetSessionToRemoteControl(sessionId);
|
var session = GetSessionToRemoteControl(sessionId);
|
||||||
await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false);
|
await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
|
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
|
||||||
|
@ -1297,7 +1297,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SendMessageToSession(session, "Playstate", command, cancellationToken);
|
return SendMessageToSession(session, SessionMessageType.PlayState, command, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
|
private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession)
|
||||||
|
@ -1322,7 +1322,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken);
|
return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1334,7 +1334,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken);
|
return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1348,7 +1348,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
|
|
||||||
_logger.LogDebug("Beginning SendServerRestartNotification");
|
_logger.LogDebug("Beginning SendServerRestartNotification");
|
||||||
|
|
||||||
return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken);
|
return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1484,6 +1484,14 @@ namespace Emby.Server.Implementations.Session
|
||||||
throw new SecurityException("User is not allowed access from this device.");
|
throw new SecurityException("User is not allowed access from this device.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sessionsCount = Sessions.Count(i => i.UserId.Equals(user.Id));
|
||||||
|
int maxActiveSessions = user.MaxActiveSessions;
|
||||||
|
_logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions);
|
||||||
|
if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions)
|
||||||
|
{
|
||||||
|
throw new SecurityException("User is at their maximum number of sessions.");
|
||||||
|
}
|
||||||
|
|
||||||
var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
|
var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
|
||||||
|
|
||||||
var session = LogSessionActivity(
|
var session = LogSessionActivity(
|
||||||
|
@ -1866,7 +1874,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken)
|
public Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
@ -1879,7 +1887,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken)
|
public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
@ -1894,7 +1902,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken)
|
public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
@ -1903,7 +1911,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
|
public Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -316,7 +317,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
return webSocket.SendAsync(
|
return webSocket.SendAsync(
|
||||||
new WebSocketMessage<int>
|
new WebSocketMessage<int>
|
||||||
{
|
{
|
||||||
MessageType = "ForceKeepAlive",
|
MessageType = SessionMessageType.ForceKeepAlive,
|
||||||
Data = WebSocketLostTimeout
|
Data = WebSocketLostTimeout
|
||||||
},
|
},
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Session
|
namespace Emby.Server.Implementations.Session
|
||||||
|
@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task SendMessage<T>(
|
public Task SendMessage<T>(
|
||||||
string name,
|
SessionMessageType name,
|
||||||
Guid messageId,
|
Guid messageId,
|
||||||
T data,
|
T data,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
|
|
|
@ -301,8 +301,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||||
if (_group.IsPaused)
|
if (_group.IsPaused)
|
||||||
{
|
{
|
||||||
// Pick a suitable time that accounts for latency
|
// Pick a suitable time that accounts for latency
|
||||||
var delay = _group.GetHighestPing() * 2;
|
var delay = Math.Max(_group.GetHighestPing() * 2, GroupInfo.DefaultPing);
|
||||||
delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
|
|
||||||
|
|
||||||
// Unpause group and set starting point in future
|
// Unpause group and set starting point in future
|
||||||
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
|
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
|
||||||
|
@ -452,8 +451,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Client, that was buffering, resumed playback but did not update others in time
|
// Client, that was buffering, resumed playback but did not update others in time
|
||||||
delay = _group.GetHighestPing() * 2;
|
delay = Math.Max(_group.GetHighestPing() * 2, GroupInfo.DefaultPing);
|
||||||
delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
|
|
||||||
|
|
||||||
_group.LastActivity = currentTime.AddMilliseconds(
|
_group.LastActivity = currentTime.AddMilliseconds(
|
||||||
delay);
|
delay);
|
||||||
|
@ -497,7 +495,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||||
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
|
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
|
||||||
{
|
{
|
||||||
// Collected pings are used to account for network latency when unpausing playback
|
// Collected pings are used to account for network latency when unpausing playback
|
||||||
_group.UpdatePing(session, request.Ping ?? _group.DefaultPing);
|
_group.UpdatePing(session, request.Ping ?? GroupInfo.DefaultPing);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using Jellyfin.Api.Helpers;
|
using Jellyfin.Api.Helpers;
|
||||||
|
using Jellyfin.Api.ModelBinders;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
@ -378,7 +379,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
public ActionResult PostCapabilities(
|
public ActionResult PostCapabilities(
|
||||||
[FromQuery] string? id,
|
[FromQuery] string? id,
|
||||||
[FromQuery] string? playableMediaTypes,
|
[FromQuery] string? playableMediaTypes,
|
||||||
[FromQuery] string? supportedCommands,
|
[FromQuery] GeneralCommandType[] supportedCommands,
|
||||||
[FromQuery] bool supportsMediaControl = false,
|
[FromQuery] bool supportsMediaControl = false,
|
||||||
[FromQuery] bool supportsSync = false,
|
[FromQuery] bool supportsSync = false,
|
||||||
[FromQuery] bool supportsPersistentIdentifier = true)
|
[FromQuery] bool supportsPersistentIdentifier = true)
|
||||||
|
@ -391,7 +392,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
_sessionManager.ReportCapabilities(id, new ClientCapabilities
|
_sessionManager.ReportCapabilities(id, new ClientCapabilities
|
||||||
{
|
{
|
||||||
PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true),
|
PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true),
|
||||||
SupportedCommands = RequestHelpers.Split(supportedCommands, ',', true),
|
SupportedCommands = supportedCommands,
|
||||||
SupportsMediaControl = supportsMediaControl,
|
SupportsMediaControl = supportsMediaControl,
|
||||||
SupportsSync = supportsSync,
|
SupportsSync = supportsSync,
|
||||||
SupportsPersistentIdentifier = supportsPersistentIdentifier
|
SupportsPersistentIdentifier = supportsPersistentIdentifier
|
||||||
|
|
|
@ -554,7 +554,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress)
|
private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress)
|
||||||
{
|
{
|
||||||
var maxBitrate = clientMaxBitrate;
|
var maxBitrate = clientMaxBitrate;
|
||||||
var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
|
var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0;
|
||||||
|
|
||||||
if (remoteClientMaxBitrate <= 0)
|
if (remoteClientMaxBitrate <= 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -740,10 +740,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
|
private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
|
||||||
{
|
{
|
||||||
if (job != null)
|
job.HasExited = true;
|
||||||
{
|
|
||||||
job.HasExited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug("Disposing stream resources");
|
_logger.LogDebug("Disposing stream resources");
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
|
|
64
Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs
Normal file
64
Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.ModelBinders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Comma delimited array model binder.
|
||||||
|
/// Returns an empty array of specified type if there is no query parameter.
|
||||||
|
/// </summary>
|
||||||
|
public class CommaDelimitedArrayModelBinder : IModelBinder
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||||
|
{
|
||||||
|
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||||
|
var elementType = bindingContext.ModelType.GetElementType();
|
||||||
|
var converter = TypeDescriptor.GetConverter(elementType);
|
||||||
|
|
||||||
|
if (valueProviderResult == ValueProviderResult.None)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueProviderResult.Length > 1)
|
||||||
|
{
|
||||||
|
var result = Array.CreateInstance(elementType, valueProviderResult.Length);
|
||||||
|
|
||||||
|
for (int i = 0; i < valueProviderResult.Length; i++)
|
||||||
|
{
|
||||||
|
var value = converter.ConvertFromString(valueProviderResult.Values[i].Trim());
|
||||||
|
|
||||||
|
result.SetValue(value, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
bindingContext.Result = ModelBindingResult.Success(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var value = valueProviderResult.FirstValue;
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
var values = Array.ConvertAll(
|
||||||
|
value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries),
|
||||||
|
x => converter.ConvertFromString(x?.Trim()));
|
||||||
|
|
||||||
|
var typedValues = Array.CreateInstance(elementType, values.Length);
|
||||||
|
values.CopyTo(typedValues, 0);
|
||||||
|
|
||||||
|
bindingContext.Result = ModelBindingResult.Success(typedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var emptyResult = Array.CreateInstance(elementType, 0);
|
||||||
|
bindingContext.Result = ModelBindingResult.Success(emptyResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.ModelBinders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Comma delimited array model binder provider.
|
||||||
|
/// </summary>
|
||||||
|
public class CommaDelimitedArrayModelBinderProvider : IModelBinderProvider
|
||||||
|
{
|
||||||
|
private readonly IModelBinder _binder;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CommaDelimitedArrayModelBinderProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public CommaDelimitedArrayModelBinderProvider()
|
||||||
|
{
|
||||||
|
_binder = new CommaDelimitedArrayModelBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IModelBinder? GetBinder(ModelBinderProviderContext context)
|
||||||
|
{
|
||||||
|
return context.Metadata.ModelType.IsArray ? _binder : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Api.WebSocketListeners
|
namespace Jellyfin.Api.WebSocketListeners
|
||||||
|
@ -29,11 +30,14 @@ namespace Jellyfin.Api.WebSocketListeners
|
||||||
_activityManager.EntryCreated += OnEntryCreated;
|
_activityManager.EntryCreated += OnEntryCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the name.
|
protected override SessionMessageType Type => SessionMessageType.ActivityLogEntry;
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
/// <inheritdoc />
|
||||||
protected override string Name => "ActivityLogEntry";
|
protected override SessionMessageType StartType => SessionMessageType.ActivityLogEntryStart;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override SessionMessageType StopType => SessionMessageType.ActivityLogEntryStop;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data to send.
|
/// Gets the data to send.
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
@ -33,11 +34,14 @@ namespace Jellyfin.Api.WebSocketListeners
|
||||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets the name.
|
protected override SessionMessageType Type => SessionMessageType.ScheduledTasksInfo;
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
/// <inheritdoc />
|
||||||
protected override string Name => "ScheduledTasksInfo";
|
protected override SessionMessageType StartType => SessionMessageType.ScheduledTasksInfoStart;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override SessionMessageType StopType => SessionMessageType.ScheduledTasksInfoStop;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data to send.
|
/// Gets the data to send.
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Api.WebSocketListeners
|
namespace Jellyfin.Api.WebSocketListeners
|
||||||
|
@ -34,7 +35,13 @@ namespace Jellyfin.Api.WebSocketListeners
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override string Name => "Sessions";
|
protected override SessionMessageType Type => SessionMessageType.Sessions;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override SessionMessageType StartType => SessionMessageType.SessionsStart;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override SessionMessageType StopType => SessionMessageType.SessionsStop;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data to send.
|
/// Gets the data to send.
|
||||||
|
|
|
@ -188,6 +188,11 @@ namespace Jellyfin.Data.Entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? LoginAttemptsBeforeLockout { get; set; }
|
public int? LoginAttemptsBeforeLockout { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of active sessions the user can have at once.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxActiveSessions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the subtitle mode.
|
/// Gets or sets the subtitle mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.System
|
namespace Jellyfin.Server.Implementations.Events.Consumers.System
|
||||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnEvent(TaskCompletionEventArgs eventArgs)
|
public async Task OnEvent(TaskCompletionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions("ScheduledTaskEnded", eventArgs.Result, CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendMessageToAdminSessions(SessionMessageType.ScheduledTaskEnded, eventArgs.Result, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Events.Updates;
|
using MediaBrowser.Controller.Events.Updates;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
{
|
{
|
||||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs)
|
public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions("PackageInstallationCancelled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCancelled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
{
|
{
|
||||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnEvent(InstallationFailedEventArgs eventArgs)
|
public async Task OnEvent(InstallationFailedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions("PackageInstallationFailed", eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationFailed, eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Events.Updates;
|
using MediaBrowser.Controller.Events.Updates;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
{
|
{
|
||||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnEvent(PluginInstalledEventArgs eventArgs)
|
public async Task OnEvent(PluginInstalledEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions("PackageInstallationCompleted", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCompleted, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Events.Updates;
|
using MediaBrowser.Controller.Events.Updates;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
{
|
{
|
||||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnEvent(PluginInstallingEventArgs eventArgs)
|
public async Task OnEvent(PluginInstallingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions("PackageInstalling", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstalling, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Events.Updates;
|
using MediaBrowser.Controller.Events.Updates;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
{
|
{
|
||||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
|
public async Task OnEvent(PluginUninstalledEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToAdminSessions("PluginUninstalled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageUninstalled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Events.Users;
|
using Jellyfin.Data.Events.Users;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
||||||
{
|
{
|
||||||
|
@ -30,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToUserSessions(
|
await _sessionManager.SendMessageToUserSessions(
|
||||||
new List<Guid> { eventArgs.Argument.Id },
|
new List<Guid> { eventArgs.Argument.Id },
|
||||||
"UserDeleted",
|
SessionMessageType.UserDeleted,
|
||||||
eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture),
|
eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
CancellationToken.None).ConfigureAwait(false);
|
CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using Jellyfin.Data.Events.Users;
|
||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
||||||
{
|
{
|
||||||
|
@ -33,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
||||||
{
|
{
|
||||||
await _sessionManager.SendMessageToUserSessions(
|
await _sessionManager.SendMessageToUserSessions(
|
||||||
new List<Guid> { e.Argument.Id },
|
new List<Guid> { e.Argument.Id },
|
||||||
"UserUpdated",
|
SessionMessageType.UserUpdated,
|
||||||
_userManager.GetUserDto(e.Argument),
|
_userManager.GetUserDto(e.Argument),
|
||||||
CancellationToken.None).ConfigureAwait(false);
|
CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
464
Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs
generated
Normal file
464
Jellyfin.Server.Implementations/Migrations/20201004171403_AddMaxActiveSessions.Designer.cs
generated
Normal file
|
@ -0,0 +1,464 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Jellyfin.Server.Implementations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(JellyfinDb))]
|
||||||
|
[Migration("20201004171403_AddMaxActiveSessions")]
|
||||||
|
partial class AddMaxActiveSessions
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasDefaultSchema("jellyfin")
|
||||||
|
.HasAnnotation("ProductVersion", "3.1.8");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DayOfWeek")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("EndHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("StartHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AccessSchedules");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ItemId")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<int>("LogSeverity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(512);
|
||||||
|
|
||||||
|
b.Property<string>("Overview")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(512);
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ShortOverview")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(512);
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ActivityLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChromecastVersion")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(32);
|
||||||
|
|
||||||
|
b.Property<string>("DashboardTheme")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(32);
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextVideoInfoOverlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ScrollDirection")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowBackdrop")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowSidebar")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipBackwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipForwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TvHome")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(32);
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Client")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("DisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayPreferencesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Order")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayPreferencesId");
|
||||||
|
|
||||||
|
b.ToTable("HomeSection");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(512);
|
||||||
|
|
||||||
|
b.Property<Guid?>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ImageInfos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(32);
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberIndexing")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSorting")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SortBy")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(64);
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ViewType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ItemDisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Value")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Permission_Permissions_Guid");
|
||||||
|
|
||||||
|
b.ToTable("Permissions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(65535);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Preference_Preferences_Guid");
|
||||||
|
|
||||||
|
b.ToTable("Preferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AudioLanguagePreference")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(255);
|
||||||
|
|
||||||
|
b.Property<string>("AuthenticationProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(255);
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayCollectionsView")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayMissingEpisodes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("EasyPassword")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(65535);
|
||||||
|
|
||||||
|
b.Property<bool>("EnableAutoLogin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableLocalPassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableUserPreferenceAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("HidePlayedInLatest")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("InternalId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("InvalidLoginAttemptCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastActivityDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLoginDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxActiveSessions")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxParentalAgeRating")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("MustUpdatePassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(65535);
|
||||||
|
|
||||||
|
b.Property<string>("PasswordResetProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(255);
|
||||||
|
|
||||||
|
b.Property<bool>("PlayDefaultAudioTrack")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberAudioSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSubtitleSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("RemoteClientBitrateLimit")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SubtitleLanguagePreference")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(255);
|
||||||
|
|
||||||
|
b.Property<int>("SubtitleMode")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SyncPlayAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(255);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("AccessSchedules")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithOne("DisplayPreferences")
|
||||||
|
.HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
|
||||||
|
.WithMany("HomeSections")
|
||||||
|
.HasForeignKey("DisplayPreferencesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithOne("ProfileImage")
|
||||||
|
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("ItemDisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Permissions")
|
||||||
|
.HasForeignKey("Permission_Permissions_Guid");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Preferences")
|
||||||
|
.HasForeignKey("Preference_Preferences_Guid");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
#pragma warning disable SA1601
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddMaxActiveSessions : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "MaxActiveSessions",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "Users",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "MaxActiveSessions",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDefaultSchema("jellyfin")
|
.HasDefaultSchema("jellyfin")
|
||||||
.HasAnnotation("ProductVersion", "3.1.7");
|
.HasAnnotation("ProductVersion", "3.1.8");
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
{
|
{
|
||||||
|
@ -344,6 +344,9 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||||
b.Property<int?>("LoginAttemptsBeforeLockout")
|
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxActiveSessions")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int?>("MaxParentalAgeRating")
|
b.Property<int?>("MaxParentalAgeRating")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
public string Name => "InvalidOrMissingAuthenticationProvider";
|
public string Name => "InvalidOrMissingAuthenticationProvider";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsEnabled => true;
|
public bool IsEnabled => false;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||||
|
|
|
@ -379,6 +379,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
PasswordResetProviderId = user.PasswordResetProviderId,
|
PasswordResetProviderId = user.PasswordResetProviderId,
|
||||||
InvalidLoginAttemptCount = user.InvalidLoginAttemptCount,
|
InvalidLoginAttemptCount = user.InvalidLoginAttemptCount,
|
||||||
LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1,
|
LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1,
|
||||||
|
MaxActiveSessions = user.MaxActiveSessions,
|
||||||
IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator),
|
IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator),
|
||||||
IsHidden = user.HasPermission(PermissionKind.IsHidden),
|
IsHidden = user.HasPermission(PermissionKind.IsHidden),
|
||||||
IsDisabled = user.HasPermission(PermissionKind.IsDisabled),
|
IsDisabled = user.HasPermission(PermissionKind.IsDisabled),
|
||||||
|
@ -701,6 +702,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
user.PasswordResetProviderId = policy.PasswordResetProviderId;
|
user.PasswordResetProviderId = policy.PasswordResetProviderId;
|
||||||
user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
|
user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
|
||||||
user.LoginAttemptsBeforeLockout = maxLoginAttempts;
|
user.LoginAttemptsBeforeLockout = maxLoginAttempts;
|
||||||
|
user.MaxActiveSessions = policy.MaxActiveSessions;
|
||||||
user.SyncPlayAccess = policy.SyncPlayAccess;
|
user.SyncPlayAccess = policy.SyncPlayAccess;
|
||||||
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
||||||
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
|
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
|
||||||
|
@ -799,7 +801,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||||
|
|
||||||
private IList<IPasswordResetProvider> GetPasswordResetProviders(User user)
|
private IList<IPasswordResetProvider> GetPasswordResetProviders(User user)
|
||||||
{
|
{
|
||||||
var passwordResetProviderId = user?.PasswordResetProviderId;
|
var passwordResetProviderId = user.PasswordResetProviderId;
|
||||||
var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
|
var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(passwordResetProviderId))
|
if (!string.IsNullOrEmpty(passwordResetProviderId))
|
||||||
|
|
|
@ -16,6 +16,7 @@ using Jellyfin.Api.Auth.LocalAccessPolicy;
|
||||||
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using Jellyfin.Api.Controllers;
|
using Jellyfin.Api.Controllers;
|
||||||
|
using Jellyfin.Api.ModelBinders;
|
||||||
using Jellyfin.Server.Configuration;
|
using Jellyfin.Server.Configuration;
|
||||||
using Jellyfin.Server.Filters;
|
using Jellyfin.Server.Filters;
|
||||||
using Jellyfin.Server.Formatters;
|
using Jellyfin.Server.Formatters;
|
||||||
|
@ -166,6 +167,8 @@ namespace Jellyfin.Server.Extensions
|
||||||
|
|
||||||
opts.OutputFormatters.Add(new CssOutputFormatter());
|
opts.OutputFormatters.Add(new CssOutputFormatter());
|
||||||
opts.OutputFormatters.Add(new XmlOutputFormatter());
|
opts.OutputFormatters.Add(new XmlOutputFormatter());
|
||||||
|
|
||||||
|
opts.ModelBinderProviders.Insert(0, new CommaDelimitedArrayModelBinderProvider());
|
||||||
})
|
})
|
||||||
|
|
||||||
// Clear app parts to avoid other assemblies being picked up
|
// Clear app parts to avoid other assemblies being picked up
|
||||||
|
|
|
@ -125,8 +125,8 @@ namespace Jellyfin.Server.Middleware
|
||||||
switch (ex)
|
switch (ex)
|
||||||
{
|
{
|
||||||
case ArgumentException _: return StatusCodes.Status400BadRequest;
|
case ArgumentException _: return StatusCodes.Status400BadRequest;
|
||||||
case AuthenticationException _:
|
case AuthenticationException _: return StatusCodes.Status401Unauthorized;
|
||||||
case SecurityException _: return StatusCodes.Status401Unauthorized;
|
case SecurityException _: return StatusCodes.Status403Forbidden;
|
||||||
case DirectoryNotFoundException _:
|
case DirectoryNotFoundException _:
|
||||||
case FileNotFoundException _:
|
case FileNotFoundException _:
|
||||||
case ResourceNotFoundException _: return StatusCodes.Status404NotFound;
|
case ResourceNotFoundException _: return StatusCodes.Status404NotFound;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Mime;
|
||||||
using Jellyfin.Api.TypeConverters;
|
using Jellyfin.Api.TypeConverters;
|
||||||
using Jellyfin.Networking.Configuration;
|
using Jellyfin.Networking.Configuration;
|
||||||
using Jellyfin.Server.Extensions;
|
using Jellyfin.Server.Extensions;
|
||||||
|
@ -12,6 +13,7 @@ using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Extensions;
|
using MediaBrowser.Controller.Extensions;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
@ -124,10 +126,15 @@ namespace Jellyfin.Server
|
||||||
mainApp.UseStaticFiles();
|
mainApp.UseStaticFiles();
|
||||||
if (appConfig.HostWebClient())
|
if (appConfig.HostWebClient())
|
||||||
{
|
{
|
||||||
|
var extensionProvider = new FileExtensionContentTypeProvider();
|
||||||
|
|
||||||
|
// subtitles octopus requires .data files.
|
||||||
|
extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet);
|
||||||
mainApp.UseStaticFiles(new StaticFileOptions
|
mainApp.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
|
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
|
||||||
RequestPath = "/web"
|
RequestPath = "/web",
|
||||||
|
ContentTypeProvider = extensionProvider
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,37 +8,38 @@ namespace MediaBrowser.Common.Json.Converters
|
||||||
/// Converts a nullable struct or value to/from JSON.
|
/// Converts a nullable struct or value to/from JSON.
|
||||||
/// Required - some clients send an empty string.
|
/// Required - some clients send an empty string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The struct type.</typeparam>
|
/// <typeparam name="TStruct">The struct type.</typeparam>
|
||||||
public class JsonNullableStructConverter<T> : JsonConverter<T?>
|
public class JsonNullableStructConverter<TStruct> : JsonConverter<TStruct?>
|
||||||
where T : struct
|
where TStruct : struct
|
||||||
{
|
{
|
||||||
private readonly JsonConverter<T?> _baseJsonConverter;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="JsonNullableStructConverter{T}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="baseJsonConverter">The base json converter.</param>
|
|
||||||
public JsonNullableStructConverter(JsonConverter<T?> baseJsonConverter)
|
|
||||||
{
|
|
||||||
_baseJsonConverter = baseJsonConverter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override TStruct? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
// Handle empty string.
|
if (reader.TokenType == JsonTokenType.Null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token is empty string.
|
||||||
if (reader.TokenType == JsonTokenType.String && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) || reader.ValueSpan.IsEmpty))
|
if (reader.TokenType == JsonTokenType.String && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) || reader.ValueSpan.IsEmpty))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _baseJsonConverter.Read(ref reader, typeToConvert, options);
|
return JsonSerializer.Deserialize<TStruct>(ref reader, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, TStruct? value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
_baseJsonConverter.Write(writer, value, options);
|
if (value.HasValue)
|
||||||
|
{
|
||||||
|
JsonSerializer.Serialize(writer, value.Value, options);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteNullValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Json.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Json nullable struct converter factory.
|
||||||
|
/// </summary>
|
||||||
|
public class JsonNullableStructConverterFactory : JsonConverterFactory
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanConvert(Type typeToConvert)
|
||||||
|
{
|
||||||
|
return typeToConvert.IsGenericType
|
||||||
|
&& typeToConvert.GetGenericTypeDefinition() == typeof(Nullable<>)
|
||||||
|
&& typeToConvert.GenericTypeArguments[0].IsValueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var structType = typeToConvert.GenericTypeArguments[0];
|
||||||
|
return (JsonConverter)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,14 +39,9 @@ namespace MediaBrowser.Common.Json
|
||||||
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get built-in converters for fallback converting.
|
|
||||||
var baseNullableInt32Converter = (JsonConverter<int?>)options.GetConverter(typeof(int?));
|
|
||||||
var baseNullableInt64Converter = (JsonConverter<long?>)options.GetConverter(typeof(long?));
|
|
||||||
|
|
||||||
options.Converters.Add(new JsonGuidConverter());
|
options.Converters.Add(new JsonGuidConverter());
|
||||||
options.Converters.Add(new JsonStringEnumConverter());
|
options.Converters.Add(new JsonStringEnumConverter());
|
||||||
options.Converters.Add(new JsonNullableStructConverter<int>(baseNullableInt32Converter));
|
options.Converters.Add(new JsonNullableStructConverterFactory());
|
||||||
options.Converters.Add(new JsonNullableStructConverter<long>(baseNullableInt64Converter));
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.Net.WebSockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Net
|
namespace MediaBrowser.Controller.Net
|
||||||
|
@ -28,10 +29,22 @@ namespace MediaBrowser.Controller.Net
|
||||||
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
|
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name.
|
/// Gets the type used for the messages sent to the client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The type.</value>
|
||||||
protected abstract string Name { get; }
|
protected abstract SessionMessageType Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message type received from the client to start sending messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type.</value>
|
||||||
|
protected abstract SessionMessageType StartType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the message type received from the client to stop sending messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The type.</value>
|
||||||
|
protected abstract SessionMessageType StopType { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the data to send.
|
/// Gets the data to send.
|
||||||
|
@ -66,12 +79,12 @@ namespace MediaBrowser.Controller.Net
|
||||||
throw new ArgumentNullException(nameof(message));
|
throw new ArgumentNullException(nameof(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
|
if (message.MessageType == StartType)
|
||||||
{
|
{
|
||||||
Start(message);
|
Start(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
if (message.MessageType == StopType)
|
||||||
{
|
{
|
||||||
Stop(message);
|
Stop(message);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +172,7 @@ namespace MediaBrowser.Controller.Net
|
||||||
new WebSocketMessage<TReturnDataType>
|
new WebSocketMessage<TReturnDataType>
|
||||||
{
|
{
|
||||||
MessageId = Guid.NewGuid(),
|
MessageId = Guid.NewGuid(),
|
||||||
MessageType = Name,
|
MessageType = Type,
|
||||||
Data = data
|
Data = data
|
||||||
},
|
},
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -176,7 +189,7 @@ namespace MediaBrowser.Controller.Net
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error sending web socket message {Name}", Name);
|
Logger.LogError(ex, "Error sending web socket message {Name}", Type);
|
||||||
DisposeConnection(tuple);
|
DisposeConnection(tuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Session
|
namespace MediaBrowser.Controller.Session
|
||||||
{
|
{
|
||||||
|
@ -23,6 +24,6 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the message.
|
/// Sends the message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken);
|
Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,16 +188,16 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <param name="data">The data.</param>
|
/// <param name="data">The data.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken);
|
Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the message to user sessions.
|
/// Sends the message to user sessions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken);
|
Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken);
|
Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the message to user device sessions.
|
/// Sends the message to user device sessions.
|
||||||
|
@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <param name="data">The data.</param>
|
/// <param name="data">The data.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken);
|
Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the restart required message.
|
/// Sends the restart required message.
|
||||||
|
|
|
@ -230,8 +230,8 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// Gets or sets the supported commands.
|
/// Gets or sets the supported commands.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The supported commands.</value>
|
/// <value>The supported commands.</value>
|
||||||
public string[] SupportedCommands
|
public GeneralCommandType[] SupportedCommands
|
||||||
=> Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands;
|
=> Capabilities == null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
|
||||||
|
|
||||||
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
|
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,12 +14,12 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
public class GroupInfo
|
public class GroupInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default ping value used for sessions.
|
/// The default ping value used for sessions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long DefaultPing { get; } = 500;
|
public const long DefaultPing = 500;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the group identifier.
|
/// Gets the group identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The group identifier.</value>
|
/// <value>The group identifier.</value>
|
||||||
public Guid GroupId { get; } = Guid.NewGuid();
|
public Guid GroupId { get; } = Guid.NewGuid();
|
||||||
|
@ -58,7 +58,8 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a session is in this group.
|
/// Checks if a session is in this group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if the session is in this group; <c>false</c> otherwise.</value>
|
/// <param name="sessionId">The session id to check.</param>
|
||||||
|
/// <returns><c>true</c> if the session is in this group; <c>false</c> otherwise.</returns>
|
||||||
public bool ContainsSession(string sessionId)
|
public bool ContainsSession(string sessionId)
|
||||||
{
|
{
|
||||||
return Participants.ContainsKey(sessionId);
|
return Participants.ContainsKey(sessionId);
|
||||||
|
@ -70,16 +71,14 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
/// <param name="session">The session.</param>
|
/// <param name="session">The session.</param>
|
||||||
public void AddSession(SessionInfo session)
|
public void AddSession(SessionInfo session)
|
||||||
{
|
{
|
||||||
if (ContainsSession(session.Id))
|
Participants.TryAdd(
|
||||||
{
|
session.Id,
|
||||||
return;
|
new GroupMember
|
||||||
}
|
{
|
||||||
|
Session = session,
|
||||||
var member = new GroupMember();
|
Ping = DefaultPing,
|
||||||
member.Session = session;
|
IsBuffering = false
|
||||||
member.Ping = DefaultPing;
|
});
|
||||||
member.IsBuffering = false;
|
|
||||||
Participants[session.Id] = member;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -88,12 +87,7 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
/// <param name="session">The session.</param>
|
/// <param name="session">The session.</param>
|
||||||
public void RemoveSession(SessionInfo session)
|
public void RemoveSession(SessionInfo session)
|
||||||
{
|
{
|
||||||
if (!ContainsSession(session.Id))
|
Participants.Remove(session.Id);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Participants.Remove(session.Id, out _);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -103,18 +97,16 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
/// <param name="ping">The ping.</param>
|
/// <param name="ping">The ping.</param>
|
||||||
public void UpdatePing(SessionInfo session, long ping)
|
public void UpdatePing(SessionInfo session, long ping)
|
||||||
{
|
{
|
||||||
if (!ContainsSession(session.Id))
|
if (Participants.TryGetValue(session.Id, out GroupMember value))
|
||||||
{
|
{
|
||||||
return;
|
value.Ping = ping;
|
||||||
}
|
}
|
||||||
|
|
||||||
Participants[session.Id].Ping = ping;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the highest ping in the group.
|
/// Gets the highest ping in the group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value name="session">The highest ping in the group.</value>
|
/// <returns>The highest ping in the group.</returns>
|
||||||
public long GetHighestPing()
|
public long GetHighestPing()
|
||||||
{
|
{
|
||||||
long max = long.MinValue;
|
long max = long.MinValue;
|
||||||
|
@ -133,18 +125,16 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
/// <param name="isBuffering">The state.</param>
|
/// <param name="isBuffering">The state.</param>
|
||||||
public void SetBuffering(SessionInfo session, bool isBuffering)
|
public void SetBuffering(SessionInfo session, bool isBuffering)
|
||||||
{
|
{
|
||||||
if (!ContainsSession(session.Id))
|
if (Participants.TryGetValue(session.Id, out GroupMember value))
|
||||||
{
|
{
|
||||||
return;
|
value.IsBuffering = isBuffering;
|
||||||
}
|
}
|
||||||
|
|
||||||
Participants[session.Id].IsBuffering = isBuffering;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the group buffering state.
|
/// Gets the group buffering state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if there is a session buffering in the group; <c>false</c> otherwise.</value>
|
/// <returns><c>true</c> if there is a session buffering in the group; <c>false</c> otherwise.</returns>
|
||||||
public bool IsBuffering()
|
public bool IsBuffering()
|
||||||
{
|
{
|
||||||
foreach (var session in Participants.Values)
|
foreach (var session in Participants.Values)
|
||||||
|
@ -161,7 +151,7 @@ namespace MediaBrowser.Controller.SyncPlay
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the group is empty.
|
/// Checks if the group is empty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if the group is empty; <c>false</c> otherwise.</value>
|
/// <returns><c>true</c> if the group is empty; <c>false</c> otherwise.</returns>
|
||||||
public bool IsEmpty()
|
public bool IsEmpty()
|
||||||
{
|
{
|
||||||
return Participants.Count == 0;
|
return Participants.Count == 0;
|
||||||
|
|
|
@ -25,8 +25,6 @@ namespace MediaBrowser.Model.Configuration
|
||||||
|
|
||||||
public bool EnableInternetProviders { get; set; }
|
public bool EnableInternetProviders { get; set; }
|
||||||
|
|
||||||
public bool ImportMissingEpisodes { get; set; }
|
|
||||||
|
|
||||||
public bool EnableAutomaticSeriesGrouping { get; set; }
|
public bool EnableAutomaticSeriesGrouping { get; set; }
|
||||||
|
|
||||||
public bool EnableEmbeddedTitles { get; set; }
|
public bool EnableEmbeddedTitles { get; set; }
|
||||||
|
|
|
@ -455,9 +455,10 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
if (directPlayProfile == null)
|
if (directPlayProfile == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}",
|
_logger.LogInformation("Profile: {0}, No audio direct play profiles found for {1} with codec {2}",
|
||||||
options.Profile.Name ?? "Unknown Profile",
|
options.Profile.Name ?? "Unknown Profile",
|
||||||
item.Path ?? "Unknown path");
|
item.Path ?? "Unknown path",
|
||||||
|
audioStream.Codec ?? "Unknown codec");
|
||||||
|
|
||||||
return (Enumerable.Empty<PlayMethod>(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
|
return (Enumerable.Empty<PlayMethod>(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
|
||||||
}
|
}
|
||||||
|
@ -972,9 +973,10 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
if (directPlay == null)
|
if (directPlay == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}",
|
_logger.LogInformation("Profile: {0}, No video direct play profiles found for {1} with codec {2}",
|
||||||
profile.Name ?? "Unknown Profile",
|
profile.Name ?? "Unknown Profile",
|
||||||
mediaSource.Path ?? "Unknown path");
|
mediaSource.Path ?? "Unknown path",
|
||||||
|
videoStream.Codec ?? "Unknown codec");
|
||||||
|
|
||||||
return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
|
return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
|
||||||
}
|
}
|
||||||
|
|
46
MediaBrowser.Model/Extensions/EnumerableExtensions.cs
Normal file
46
MediaBrowser.Model/Extensions/EnumerableExtensions.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using MediaBrowser.Model.Providers;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="IEnumerable{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumerableExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Orders <see cref="RemoteImageInfo"/> by requested language in descending order, prioritizing "en" over other non-matches.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="remoteImageInfos">The remote image infos.</param>
|
||||||
|
/// <param name="requestedLanguage">The requested language for the images.</param>
|
||||||
|
/// <returns>The ordered remote image infos.</returns>
|
||||||
|
public static IEnumerable<RemoteImageInfo> OrderByLanguageDescending(this IEnumerable<RemoteImageInfo> remoteImageInfos, string requestedLanguage)
|
||||||
|
{
|
||||||
|
var isRequestedLanguageEn = string.Equals(requestedLanguage, "en", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return remoteImageInfos.OrderByDescending(i =>
|
||||||
|
{
|
||||||
|
if (string.Equals(requestedLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRequestedLanguageEn && string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(i.Language))
|
||||||
|
{
|
||||||
|
return isRequestedLanguageEn ? 3 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
.ThenByDescending(i => i.CommunityRating ?? 0)
|
||||||
|
.ThenByDescending(i => i.VoteCount ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Net
|
namespace MediaBrowser.Model.Net
|
||||||
{
|
{
|
||||||
|
@ -15,7 +16,7 @@ namespace MediaBrowser.Model.Net
|
||||||
/// Gets or sets the type of the message.
|
/// Gets or sets the type of the message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the message.</value>
|
/// <value>The type of the message.</value>
|
||||||
public string MessageType { get; set; }
|
public SessionMessageType MessageType { get; set; }
|
||||||
|
|
||||||
public Guid MessageId { get; set; }
|
public Guid MessageId { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Session
|
||||||
{
|
{
|
||||||
public string[] PlayableMediaTypes { get; set; }
|
public string[] PlayableMediaTypes { get; set; }
|
||||||
|
|
||||||
public string[] SupportedCommands { get; set; }
|
public GeneralCommandType[] SupportedCommands { get; set; }
|
||||||
|
|
||||||
public bool SupportsMediaControl { get; set; }
|
public bool SupportsMediaControl { get; set; }
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Session
|
||||||
public ClientCapabilities()
|
public ClientCapabilities()
|
||||||
{
|
{
|
||||||
PlayableMediaTypes = Array.Empty<string>();
|
PlayableMediaTypes = Array.Empty<string>();
|
||||||
SupportedCommands = Array.Empty<string>();
|
SupportedCommands = Array.Empty<GeneralCommandType>();
|
||||||
SupportsPersistentIdentifier = true;
|
SupportsPersistentIdentifier = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
50
MediaBrowser.Model/Session/SessionMessageType.cs
Normal file
50
MediaBrowser.Model/Session/SessionMessageType.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The different kinds of messages that are used in the WebSocket api.
|
||||||
|
/// </summary>
|
||||||
|
public enum SessionMessageType
|
||||||
|
{
|
||||||
|
// Server -> Client
|
||||||
|
ForceKeepAlive,
|
||||||
|
GeneralCommand,
|
||||||
|
UserDataChanged,
|
||||||
|
Sessions,
|
||||||
|
Play,
|
||||||
|
SyncPlayCommand,
|
||||||
|
SyncPlayGroupUpdate,
|
||||||
|
PlayState,
|
||||||
|
RestartRequired,
|
||||||
|
ServerShuttingDown,
|
||||||
|
ServerRestarting,
|
||||||
|
LibraryChanged,
|
||||||
|
UserDeleted,
|
||||||
|
UserUpdated,
|
||||||
|
SeriesTimerCreated,
|
||||||
|
TimerCreated,
|
||||||
|
SeriesTimerCancelled,
|
||||||
|
TimerCancelled,
|
||||||
|
RefreshProgress,
|
||||||
|
ScheduledTaskEnded,
|
||||||
|
PackageInstallationCancelled,
|
||||||
|
PackageInstallationFailed,
|
||||||
|
PackageInstallationCompleted,
|
||||||
|
PackageInstalling,
|
||||||
|
PackageUninstalled,
|
||||||
|
ActivityLogEntry,
|
||||||
|
ScheduledTasksInfo,
|
||||||
|
|
||||||
|
// Client -> Server
|
||||||
|
ActivityLogEntryStart,
|
||||||
|
ActivityLogEntryStop,
|
||||||
|
SessionsStart,
|
||||||
|
SessionsStop,
|
||||||
|
ScheduledTasksInfoStart,
|
||||||
|
ScheduledTasksInfoStop,
|
||||||
|
|
||||||
|
// Shared
|
||||||
|
KeepAlive,
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,6 +92,8 @@ namespace MediaBrowser.Model.Users
|
||||||
|
|
||||||
public int LoginAttemptsBeforeLockout { get; set; }
|
public int LoginAttemptsBeforeLockout { get; set; }
|
||||||
|
|
||||||
|
public int MaxActiveSessions { get; set; }
|
||||||
|
|
||||||
public bool EnablePublicSharing { get; set; }
|
public bool EnablePublicSharing { get; set; }
|
||||||
|
|
||||||
public Guid[] BlockedMediaFolders { get; set; }
|
public Guid[] BlockedMediaFolders { get; set; }
|
||||||
|
@ -144,6 +146,8 @@ namespace MediaBrowser.Model.Users
|
||||||
|
|
||||||
LoginAttemptsBeforeLockout = -1;
|
LoginAttemptsBeforeLockout = -1;
|
||||||
|
|
||||||
|
MaxActiveSessions = 0;
|
||||||
|
|
||||||
EnableAllChannels = true;
|
EnableAllChannels = true;
|
||||||
EnabledChannels = Array.Empty<Guid>();
|
EnabledChannels = Array.Empty<Guid>();
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,14 @@ namespace MediaBrowser.Providers.Manager
|
||||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||||
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (response.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
throw new HttpException("Invalid image received.")
|
||||||
|
{
|
||||||
|
StatusCode = response.StatusCode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var contentType = response.Content.Headers.ContentType.MediaType;
|
var contentType = response.Content.Headers.ContentType.MediaType;
|
||||||
|
|
||||||
// Workaround for tvheadend channel icons
|
// Workaround for tvheadend channel icons
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
|
||||||
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
||||||
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
|
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
|
||||||
|
<PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
|
||||||
<PackageReference Include="TvDbSharper" Version="3.2.2" />
|
<PackageReference Include="TvDbSharper" Version="3.2.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -80,32 +80,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
||||||
return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
|
return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
// Traverse all episode pages and join them together
|
|
||||||
var episodes = new List<EpisodeRecord>();
|
|
||||||
var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
episodes.AddRange(episodePage.Data);
|
|
||||||
if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
|
|
||||||
{
|
|
||||||
return episodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
int next = episodePage.Links.Next.Value;
|
|
||||||
int last = episodePage.Links.Last.Value;
|
|
||||||
|
|
||||||
for (var page = next; page <= last; ++page)
|
|
||||||
{
|
|
||||||
episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
episodes.AddRange(episodePage.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return episodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(
|
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(
|
||||||
string imdbId,
|
string imdbId,
|
||||||
string language,
|
string language,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -12,25 +13,25 @@ using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
||||||
{
|
{
|
||||||
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
|
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
|
||||||
{
|
{
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly TmdbClientManager _tmdbClientManager;
|
||||||
|
|
||||||
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory)
|
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_tmdbClientManager = tmdbClientManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => ProviderName;
|
public string Name => TmdbUtils.ProviderName;
|
||||||
|
|
||||||
public static string ProviderName => TmdbUtils.ProviderName;
|
public int Order => 0;
|
||||||
|
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
|
@ -48,112 +49,60 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
||||||
|
|
||||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tmdbId = item.GetProviderId(MetadataProvider.Tmdb);
|
var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(tmdbId))
|
if (tmdbId <= 0)
|
||||||
{
|
{
|
||||||
var language = item.GetPreferredMetadataLanguage();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
|
|
||||||
var mainResult = await TmdbBoxSetProvider.Current.GetMovieDbResult(tmdbId, null, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (mainResult != null)
|
|
||||||
{
|
|
||||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
|
||||||
|
|
||||||
return GetImages(mainResult, language, tmdbImageUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<RemoteImageInfo>();
|
var language = item.GetPreferredMetadataLanguage();
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<RemoteImageInfo> GetImages(CollectionResult obj, string language, string baseUrl)
|
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
var list = new List<RemoteImageInfo>();
|
|
||||||
|
|
||||||
var images = obj.Images ?? new CollectionImages();
|
if (collection?.Images == null)
|
||||||
|
|
||||||
list.AddRange(GetPosters(images).Select(i => new RemoteImageInfo
|
|
||||||
{
|
{
|
||||||
Url = baseUrl + i.File_Path,
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
CommunityRating = i.Vote_Average,
|
}
|
||||||
VoteCount = i.Vote_Count,
|
|
||||||
Width = i.Width,
|
|
||||||
Height = i.Height,
|
|
||||||
Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language),
|
|
||||||
ProviderName = Name,
|
|
||||||
Type = ImageType.Primary,
|
|
||||||
RatingType = RatingType.Score
|
|
||||||
}));
|
|
||||||
|
|
||||||
list.AddRange(GetBackdrops(images).Select(i => new RemoteImageInfo
|
var remoteImages = new List<RemoteImageInfo>();
|
||||||
|
|
||||||
|
for (var i = 0; i < collection.Images.Posters.Count; i++)
|
||||||
{
|
{
|
||||||
Url = baseUrl + i.File_Path,
|
var poster = collection.Images.Posters[i];
|
||||||
CommunityRating = i.Vote_Average,
|
remoteImages.Add(new RemoteImageInfo
|
||||||
VoteCount = i.Vote_Count,
|
{
|
||||||
Width = i.Width,
|
Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
|
||||||
Height = i.Height,
|
CommunityRating = poster.VoteAverage,
|
||||||
ProviderName = Name,
|
VoteCount = poster.VoteCount,
|
||||||
Type = ImageType.Backdrop,
|
Width = poster.Width,
|
||||||
RatingType = RatingType.Score
|
Height = poster.Height,
|
||||||
}));
|
Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language),
|
||||||
|
ProviderName = Name,
|
||||||
|
Type = ImageType.Primary,
|
||||||
|
RatingType = RatingType.Score
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
|
for (var i = 0; i < collection.Images.Backdrops.Count; i++)
|
||||||
|
|
||||||
return list.OrderByDescending(i =>
|
|
||||||
{
|
{
|
||||||
if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
|
var backdrop = collection.Images.Backdrops[i];
|
||||||
|
remoteImages.Add(new RemoteImageInfo
|
||||||
{
|
{
|
||||||
return 3;
|
Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath),
|
||||||
}
|
CommunityRating = backdrop.VoteAverage,
|
||||||
|
VoteCount = backdrop.VoteCount,
|
||||||
|
Width = backdrop.Width,
|
||||||
|
Height = backdrop.Height,
|
||||||
|
ProviderName = Name,
|
||||||
|
Type = ImageType.Backdrop,
|
||||||
|
RatingType = RatingType.Score
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!isLanguageEn)
|
return remoteImages.OrderByLanguageDescending(language);
|
||||||
{
|
|
||||||
if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(i.Language))
|
|
||||||
{
|
|
||||||
return isLanguageEn ? 3 : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
.ThenByDescending(i => i.CommunityRating ?? 0)
|
|
||||||
.ThenByDescending(i => i.VoteCount ?? 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the posters.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="images">The images.</param>
|
|
||||||
/// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
|
|
||||||
private IEnumerable<Poster> GetPosters(CollectionImages images)
|
|
||||||
{
|
|
||||||
return images.Posters ?? new List<Poster>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the backdrops.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="images">The images.</param>
|
|
||||||
/// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
|
|
||||||
private IEnumerable<Backdrop> GetBackdrops(CollectionImages images)
|
|
||||||
{
|
|
||||||
var eligibleBackdrops = images.Backdrops == null ? new List<Backdrop>() :
|
|
||||||
images.Backdrops;
|
|
||||||
|
|
||||||
return eligibleBackdrops.OrderByDescending(i => i.Vote_Average)
|
|
||||||
.ThenByDescending(i => i.Vote_Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Order => 0;
|
|
||||||
|
|
||||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||||
|
|
|
@ -3,270 +3,118 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
||||||
{
|
{
|
||||||
public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo>
|
public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo>
|
||||||
{
|
{
|
||||||
private const string GetCollectionInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/collection/{0}?api_key={1}&append_to_response=images";
|
|
||||||
|
|
||||||
internal static TmdbBoxSetProvider Current;
|
|
||||||
|
|
||||||
private readonly ILogger<TmdbBoxSetProvider> _logger;
|
|
||||||
private readonly IJsonSerializer _json;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly TmdbClientManager _tmdbClientManager;
|
||||||
|
|
||||||
public TmdbBoxSetProvider(
|
public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||||
ILogger<TmdbBoxSetProvider> logger,
|
|
||||||
IJsonSerializer json,
|
|
||||||
IServerConfigurationManager config,
|
|
||||||
IFileSystem fileSystem,
|
|
||||||
IHttpClientFactory httpClientFactory,
|
|
||||||
ILibraryManager libraryManager)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_json = json;
|
|
||||||
_config = config;
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_libraryManager = libraryManager;
|
_tmdbClientManager = tmdbClientManager;
|
||||||
Current = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
public string Name => TmdbUtils.ProviderName;
|
||||||
|
|
||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
|
var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||||
|
var language = searchInfo.MetadataLanguage;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(tmdbId))
|
if (tmdbId > 0)
|
||||||
{
|
{
|
||||||
await EnsureInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, searchInfo.MetadataLanguage);
|
if (collection == null)
|
||||||
var info = _json.DeserializeFromFile<CollectionResult>(dataFilePath);
|
{
|
||||||
|
return Enumerable.Empty<RemoteSearchResult>();
|
||||||
var images = (info.Images ?? new CollectionImages()).Posters ?? new List<Poster>();
|
}
|
||||||
|
|
||||||
var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
|
|
||||||
|
|
||||||
var result = new RemoteSearchResult
|
var result = new RemoteSearchResult
|
||||||
{
|
{
|
||||||
Name = info.Name,
|
Name = collection.Name,
|
||||||
SearchProviderName = Name,
|
SearchProviderName = Name
|
||||||
ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
result.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture));
|
if (collection.Images != null)
|
||||||
|
{
|
||||||
|
result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
return new[] { result };
|
return new[] { result };
|
||||||
}
|
}
|
||||||
|
|
||||||
return await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
|
var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var collections = new List<RemoteSearchResult>();
|
||||||
|
for (var i = 0; i < collectionSearchResults.Count; i++)
|
||||||
|
{
|
||||||
|
var collection = new RemoteSearchResult
|
||||||
|
{
|
||||||
|
Name = collectionSearchResults[i].Name,
|
||||||
|
SearchProviderName = Name
|
||||||
|
};
|
||||||
|
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
collections.Add(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
|
public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var tmdbId = id.GetProviderId(MetadataProvider.Tmdb);
|
var tmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||||
|
var language = id.MetadataLanguage;
|
||||||
// We don't already have an Id, need to fetch it
|
// We don't already have an Id, need to fetch it
|
||||||
if (string.IsNullOrEmpty(tmdbId))
|
if (tmdbId <= 0)
|
||||||
{
|
{
|
||||||
var searchResults = await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(id, cancellationToken).ConfigureAwait(false);
|
var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var searchResult = searchResults.FirstOrDefault();
|
if (searchResults != null && searchResults.Count > 0)
|
||||||
|
|
||||||
if (searchResult != null)
|
|
||||||
{
|
{
|
||||||
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
|
tmdbId = searchResults[0].Id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new MetadataResult<BoxSet>();
|
var result = new MetadataResult<BoxSet>();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(tmdbId))
|
if (tmdbId > 0)
|
||||||
{
|
{
|
||||||
var mainResult = await GetMovieDbResult(tmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (mainResult != null)
|
if (collection != null)
|
||||||
{
|
{
|
||||||
|
var item = new BoxSet
|
||||||
|
{
|
||||||
|
Name = collection.Name,
|
||||||
|
Overview = collection.Overview
|
||||||
|
};
|
||||||
|
|
||||||
|
item.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
result.HasMetadata = true;
|
result.HasMetadata = true;
|
||||||
result.Item = GetItem(mainResult);
|
result.Item = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<CollectionResult> GetMovieDbResult(string tmdbId, string language, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(tmdbId))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(tmdbId));
|
|
||||||
}
|
|
||||||
|
|
||||||
await EnsureInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, language);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(dataFilePath))
|
|
||||||
{
|
|
||||||
return _json.DeserializeFromFile<CollectionResult>(dataFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BoxSet GetItem(CollectionResult obj)
|
|
||||||
{
|
|
||||||
var item = new BoxSet
|
|
||||||
{
|
|
||||||
Name = obj.Name,
|
|
||||||
Overview = obj.Overview
|
|
||||||
};
|
|
||||||
|
|
||||||
item.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var mainResult = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (mainResult == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
|
|
||||||
|
|
||||||
_json.SerializeToFile(mainResult, dataFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<CollectionResult> FetchMainResult(string id, string language, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(language))
|
|
||||||
{
|
|
||||||
url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
|
|
||||||
|
|
||||||
// Get images in english and with no language
|
|
||||||
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
|
||||||
{
|
|
||||||
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
|
|
||||||
}
|
|
||||||
|
|
||||||
using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false);
|
|
||||||
await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
|
||||||
var mainResult = await _json.DeserializeFromStreamAsync<CollectionResult>(stream).ConfigureAwait(false);
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
if (mainResult != null && string.IsNullOrEmpty(mainResult.Name))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en";
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(language))
|
|
||||||
{
|
|
||||||
// Get images in english and with no language
|
|
||||||
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
|
|
||||||
}
|
|
||||||
|
|
||||||
using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
|
|
||||||
foreach (var header in TmdbUtils.AcceptHeaders)
|
|
||||||
{
|
|
||||||
langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var langStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
|
||||||
mainResult = await _json.DeserializeFromStreamAsync<CollectionResult>(langStream).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage);
|
|
||||||
|
|
||||||
var fileInfo = _fileSystem.GetFileSystemInfo(path);
|
|
||||||
|
|
||||||
if (fileInfo.Exists)
|
|
||||||
{
|
|
||||||
// If it's recent or automatic updates are enabled, don't re-download
|
|
||||||
if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name => TmdbUtils.ProviderName;
|
|
||||||
|
|
||||||
private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage)
|
|
||||||
{
|
|
||||||
var path = GetDataPath(appPaths, tmdbId);
|
|
||||||
|
|
||||||
var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage ?? string.Empty);
|
|
||||||
|
|
||||||
return Path.Combine(path, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetDataPath(IApplicationPaths appPaths, string tmdbId)
|
|
||||||
{
|
|
||||||
var dataPath = GetCollectionsDataPath(appPaths);
|
|
||||||
|
|
||||||
return Path.Combine(dataPath, tmdbId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetCollectionsDataPath(IApplicationPaths appPaths)
|
|
||||||
{
|
|
||||||
var dataPath = Path.Combine(appPaths.CachePath, "tmdb-collections");
|
|
||||||
|
|
||||||
return dataPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
|
|
||||||
{
|
|
||||||
public class CollectionImages
|
|
||||||
{
|
|
||||||
public List<Backdrop> Backdrops { get; set; }
|
|
||||||
|
|
||||||
public List<Poster> Posters { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
|
|
||||||
{
|
|
||||||
public class CollectionResult
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Overview { get; set; }
|
|
||||||
|
|
||||||
public string Poster_Path { get; set; }
|
|
||||||
|
|
||||||
public string Backdrop_Path { get; set; }
|
|
||||||
|
|
||||||
public List<Part> Parts { get; set; }
|
|
||||||
|
|
||||||
public CollectionImages Images { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections
|
|
||||||
{
|
|
||||||
public class Part
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Release_Date { get; set; }
|
|
||||||
|
|
||||||
public string Poster_Path { get; set; }
|
|
||||||
|
|
||||||
public string Backdrop_Path { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Backdrop
|
|
||||||
{
|
|
||||||
public double Aspect_Ratio { get; set; }
|
|
||||||
|
|
||||||
public string File_Path { get; set; }
|
|
||||||
|
|
||||||
public int Height { get; set; }
|
|
||||||
|
|
||||||
public string Iso_639_1 { get; set; }
|
|
||||||
|
|
||||||
public double Vote_Average { get; set; }
|
|
||||||
|
|
||||||
public int Vote_Count { get; set; }
|
|
||||||
|
|
||||||
public int Width { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Crew
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Credit_Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Department { get; set; }
|
|
||||||
|
|
||||||
public string Job { get; set; }
|
|
||||||
|
|
||||||
public string Profile_Path { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class ExternalIds
|
|
||||||
{
|
|
||||||
public string Imdb_Id { get; set; }
|
|
||||||
|
|
||||||
public object Freebase_Id { get; set; }
|
|
||||||
|
|
||||||
public string Freebase_Mid { get; set; }
|
|
||||||
|
|
||||||
public int? Tvdb_Id { get; set; }
|
|
||||||
|
|
||||||
public int? Tvrage_Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Genre
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Images
|
|
||||||
{
|
|
||||||
public List<Backdrop> Backdrops { get; set; }
|
|
||||||
|
|
||||||
public List<Poster> Posters { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Keyword
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Keywords
|
|
||||||
{
|
|
||||||
public List<Keyword> Results { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Poster
|
|
||||||
{
|
|
||||||
public double Aspect_Ratio { get; set; }
|
|
||||||
|
|
||||||
public string File_Path { get; set; }
|
|
||||||
|
|
||||||
public int Height { get; set; }
|
|
||||||
|
|
||||||
public string Iso_639_1 { get; set; }
|
|
||||||
|
|
||||||
public double Vote_Average { get; set; }
|
|
||||||
|
|
||||||
public int Vote_Count { get; set; }
|
|
||||||
|
|
||||||
public int Width { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Profile
|
|
||||||
{
|
|
||||||
public string File_Path { get; set; }
|
|
||||||
|
|
||||||
public int Width { get; set; }
|
|
||||||
|
|
||||||
public int Height { get; set; }
|
|
||||||
|
|
||||||
public object Iso_639_1 { get; set; }
|
|
||||||
|
|
||||||
public double Aspect_Ratio { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Still
|
|
||||||
{
|
|
||||||
public double Aspect_Ratio { get; set; }
|
|
||||||
|
|
||||||
public string File_Path { get; set; }
|
|
||||||
|
|
||||||
public int Height { get; set; }
|
|
||||||
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public string Iso_639_1 { get; set; }
|
|
||||||
|
|
||||||
public double Vote_Average { get; set; }
|
|
||||||
|
|
||||||
public int Vote_Count { get; set; }
|
|
||||||
|
|
||||||
public int Width { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class StillImages
|
|
||||||
{
|
|
||||||
public List<Still> Stills { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Video
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
|
|
||||||
public string Iso_639_1 { get; set; }
|
|
||||||
|
|
||||||
public string Iso_3166_1 { get; set; }
|
|
||||||
|
|
||||||
public string Key { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Site { get; set; }
|
|
||||||
|
|
||||||
public string Size { get; set; }
|
|
||||||
|
|
||||||
public string Type { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General
|
|
||||||
{
|
|
||||||
public class Videos
|
|
||||||
{
|
|
||||||
public IReadOnlyList<Video> Results { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class BelongsToCollection
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Poster_Path { get; set; }
|
|
||||||
|
|
||||||
public string Backdrop_Path { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class Cast
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Character { get; set; }
|
|
||||||
|
|
||||||
public int Order { get; set; }
|
|
||||||
|
|
||||||
public int Cast_Id { get; set; }
|
|
||||||
|
|
||||||
public string Profile_Path { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class Casts
|
|
||||||
{
|
|
||||||
public List<Cast> Cast { get; set; }
|
|
||||||
|
|
||||||
public List<Crew> Crew { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class Country
|
|
||||||
{
|
|
||||||
public string Iso_3166_1 { get; set; }
|
|
||||||
|
|
||||||
public string Certification { get; set; }
|
|
||||||
|
|
||||||
public DateTime Release_Date { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class MovieResult
|
|
||||||
{
|
|
||||||
public bool Adult { get; set; }
|
|
||||||
|
|
||||||
public string Backdrop_Path { get; set; }
|
|
||||||
|
|
||||||
public BelongsToCollection Belongs_To_Collection { get; set; }
|
|
||||||
|
|
||||||
public long Budget { get; set; }
|
|
||||||
|
|
||||||
public List<Genre> Genres { get; set; }
|
|
||||||
|
|
||||||
public string Homepage { get; set; }
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Imdb_Id { get; set; }
|
|
||||||
|
|
||||||
public string Original_Title { get; set; }
|
|
||||||
|
|
||||||
public string Original_Name { get; set; }
|
|
||||||
|
|
||||||
public string Overview { get; set; }
|
|
||||||
|
|
||||||
public double Popularity { get; set; }
|
|
||||||
|
|
||||||
public string Poster_Path { get; set; }
|
|
||||||
|
|
||||||
public List<ProductionCompany> Production_Companies { get; set; }
|
|
||||||
|
|
||||||
public List<ProductionCountry> Production_Countries { get; set; }
|
|
||||||
|
|
||||||
public string Release_Date { get; set; }
|
|
||||||
|
|
||||||
public long Revenue { get; set; }
|
|
||||||
|
|
||||||
public int Runtime { get; set; }
|
|
||||||
|
|
||||||
public List<SpokenLanguage> Spoken_Languages { get; set; }
|
|
||||||
|
|
||||||
public string Status { get; set; }
|
|
||||||
|
|
||||||
public string Tagline { get; set; }
|
|
||||||
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public double Vote_Average { get; set; }
|
|
||||||
|
|
||||||
public int Vote_Count { get; set; }
|
|
||||||
|
|
||||||
public Casts Casts { get; set; }
|
|
||||||
|
|
||||||
public Releases Releases { get; set; }
|
|
||||||
|
|
||||||
public Images Images { get; set; }
|
|
||||||
|
|
||||||
public Keywords Keywords { get; set; }
|
|
||||||
|
|
||||||
public Trailers Trailers { get; set; }
|
|
||||||
|
|
||||||
public string GetOriginalTitle()
|
|
||||||
{
|
|
||||||
return Original_Name ?? Original_Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetTitle()
|
|
||||||
{
|
|
||||||
return Name ?? Title ?? GetOriginalTitle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class ProductionCompany
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class ProductionCountry
|
|
||||||
{
|
|
||||||
public string Iso_3166_1 { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class Releases
|
|
||||||
{
|
|
||||||
public List<Country> Countries { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class SpokenLanguage
|
|
||||||
{
|
|
||||||
public string Iso_639_1 { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class Trailers
|
|
||||||
{
|
|
||||||
public IReadOnlyList<Youtube> Youtube { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies
|
|
||||||
{
|
|
||||||
public class Youtube
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Size { get; set; }
|
|
||||||
|
|
||||||
public string Source { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
|
|
||||||
{
|
|
||||||
public class PersonImages
|
|
||||||
{
|
|
||||||
public IReadOnlyList<Profile> Profiles { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People
|
|
||||||
{
|
|
||||||
public class PersonResult
|
|
||||||
{
|
|
||||||
public bool Adult { get; set; }
|
|
||||||
|
|
||||||
public List<string> Also_Known_As { get; set; }
|
|
||||||
|
|
||||||
public string Biography { get; set; }
|
|
||||||
|
|
||||||
public string Birthday { get; set; }
|
|
||||||
|
|
||||||
public string Deathday { get; set; }
|
|
||||||
|
|
||||||
public string Homepage { get; set; }
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Imdb_Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Place_Of_Birth { get; set; }
|
|
||||||
|
|
||||||
public double Popularity { get; set; }
|
|
||||||
|
|
||||||
public string Profile_Path { get; set; }
|
|
||||||
|
|
||||||
public PersonImages Images { get; set; }
|
|
||||||
|
|
||||||
public ExternalIds External_Ids { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
|
|
||||||
{
|
|
||||||
public class ExternalIdLookupResult
|
|
||||||
{
|
|
||||||
public List<TvResult> Tv_Results { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
|
|
||||||
{
|
|
||||||
public class MovieResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this <see cref="MovieResult" /> is adult.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
|
|
||||||
public bool Adult { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the backdrop_path.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The backdrop_path.</value>
|
|
||||||
public string Backdrop_Path { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the id.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The id.</value>
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the original_title.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The original_title.</value>
|
|
||||||
public string Original_Title { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the original_name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The original_name.</value>
|
|
||||||
public string Original_Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the release_date.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The release_date.</value>
|
|
||||||
public string Release_Date { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the poster_path.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The poster_path.</value>
|
|
||||||
public string Poster_Path { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the popularity.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The popularity.</value>
|
|
||||||
public double Popularity { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the title.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The title.</value>
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the vote_average.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The vote_average.</value>
|
|
||||||
public double Vote_Average { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For collection search results.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the vote_count.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The vote_count.</value>
|
|
||||||
public int Vote_Count { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
|
|
||||||
{
|
|
||||||
public class PersonSearchResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this <see cref="PersonSearchResult" /> is adult.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
|
|
||||||
public bool Adult { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the id.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The id.</value>
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the profile_ path.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The profile_ path.</value>
|
|
||||||
public string Profile_Path { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
|
|
||||||
{
|
|
||||||
public class TmdbSearchResult<T>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the page.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The page.</value>
|
|
||||||
public int Page { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the results.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The results.</value>
|
|
||||||
public List<T> Results { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the total_pages.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The total_pages.</value>
|
|
||||||
public int Total_Pages { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the total_results.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The total_results.</value>
|
|
||||||
public int Total_Results { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search
|
|
||||||
{
|
|
||||||
public class TvResult
|
|
||||||
{
|
|
||||||
public string Backdrop_Path { get; set; }
|
|
||||||
|
|
||||||
public string First_Air_Date { get; set; }
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Original_Name { get; set; }
|
|
||||||
|
|
||||||
public string Poster_Path { get; set; }
|
|
||||||
|
|
||||||
public double Popularity { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public double Vote_Average { get; set; }
|
|
||||||
|
|
||||||
public int Vote_Count { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
|
|
||||||
{
|
|
||||||
public class Cast
|
|
||||||
{
|
|
||||||
public string Character { get; set; }
|
|
||||||
|
|
||||||
public string Credit_Id { get; set; }
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Profile_Path { get; set; }
|
|
||||||
|
|
||||||
public int Order { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
|
|
||||||
{
|
|
||||||
public class ContentRating
|
|
||||||
{
|
|
||||||
public string Iso_3166_1 { get; set; }
|
|
||||||
|
|
||||||
public string Rating { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV
|
|
||||||
{
|
|
||||||
public class ContentRatings
|
|
||||||
{
|
|
||||||
public List<ContentRating> Results { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue