Merge remote-tracking branch 'upstream/master' into NetworkPR2
This commit is contained in:
commit
ebe650afa9
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
|
||||
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
|
||||
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
|
||||
displayName: 'Build Docker'
|
||||
|
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
inputs:
|
||||
command: "test"
|
||||
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
|
||||
testRunTitle: $(Agent.JobName)
|
||||
workingDirectory: "$(Build.SourcesDirectory)"
|
||||
|
|
|
@ -34,6 +34,12 @@ jobs:
|
|||
Linux: 'ubuntu-latest'
|
||||
Windows: 'windows-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'))) }}:
|
||||
- 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')) }}:
|
||||
- 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-src.*
|
||||
MediaBrowser.WebDashboard/jellyfin-web
|
||||
apiclient/generated
|
||||
|
|
|
@ -137,6 +137,7 @@
|
|||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||
- [Pusta](https://github.com/pusta)
|
||||
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
||||
- [skyfrk](https://github.com/skyfrk)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
|
|
@ -811,7 +811,7 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
|
@ -823,17 +823,17 @@ namespace Emby.Dlna.PlayTo
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
|
||||
if (name == SessionMessageType.Play)
|
||||
{
|
||||
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
||||
}
|
||||
|
||||
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
||||
if (name == SessionMessageType.PlayState)
|
||||
{
|
||||
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
||||
}
|
||||
|
||||
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
||||
if (name == SessionMessageType.GeneralCommand)
|
||||
{
|
||||
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
||||
}
|
||||
|
|
|
@ -209,15 +209,15 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
SupportedCommands = new[]
|
||||
{
|
||||
GeneralCommandType.VolumeDown.ToString(),
|
||||
GeneralCommandType.VolumeUp.ToString(),
|
||||
GeneralCommandType.Mute.ToString(),
|
||||
GeneralCommandType.Unmute.ToString(),
|
||||
GeneralCommandType.ToggleMute.ToString(),
|
||||
GeneralCommandType.SetVolume.ToString(),
|
||||
GeneralCommandType.SetAudioStreamIndex.ToString(),
|
||||
GeneralCommandType.SetSubtitleStreamIndex.ToString(),
|
||||
GeneralCommandType.PlayMediaSource.ToString()
|
||||
GeneralCommandType.VolumeDown,
|
||||
GeneralCommandType.VolumeUp,
|
||||
GeneralCommandType.Mute,
|
||||
GeneralCommandType.Unmute,
|
||||
GeneralCommandType.ToggleMute,
|
||||
GeneralCommandType.SetVolume,
|
||||
GeneralCommandType.SetAudioStreamIndex,
|
||||
GeneralCommandType.SetSubtitleStreamIndex,
|
||||
GeneralCommandType.PlayMediaSource
|
||||
},
|
||||
|
||||
SupportsMediaControl = true
|
||||
|
|
|
@ -101,6 +101,7 @@ using MediaBrowser.Model.Tasks;
|
|||
using MediaBrowser.Providers.Chapters;
|
||||
using MediaBrowser.Providers.Manager;
|
||||
using MediaBrowser.Providers.Plugins.TheTvdb;
|
||||
using MediaBrowser.Providers.Plugins.Tmdb;
|
||||
using MediaBrowser.Providers.Subtitles;
|
||||
using MediaBrowser.XbmcMetadata.Providers;
|
||||
using Microsoft.AspNetCore.DataProtection.Repositories;
|
||||
|
@ -549,6 +550,7 @@ namespace Emby.Server.Implementations
|
|||
|
||||
ServiceCollection.AddSingleton(_fileSystemManager);
|
||||
ServiceCollection.AddSingleton<TvdbClientManager>();
|
||||
ServiceCollection.AddSingleton<TmdbClientManager>();
|
||||
|
||||
ServiceCollection.AddSingleton(NetManager);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ using MediaBrowser.Controller.Plugins;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
|
@ -105,7 +106,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
try
|
||||
{
|
||||
_sessionManager.SendMessageToAdminSessions("RefreshProgress", dict, CancellationToken.None);
|
||||
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
try
|
||||
{
|
||||
_sessionManager.SendMessageToAdminSessions("RefreshProgress", collectionFolderDict, CancellationToken.None);
|
||||
_sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -345,7 +346,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.EntryPoints
|
||||
|
@ -46,25 +47,25 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
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)
|
||||
{
|
||||
await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false);
|
||||
await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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();
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||
|
||||
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)
|
||||
|
|
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
|||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
Connection = this
|
||||
};
|
||||
|
||||
if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
|
||||
if (info.MessageType == SessionMessageType.KeepAlive)
|
||||
{
|
||||
await SendKeepAliveResponse().ConfigureAwait(false);
|
||||
}
|
||||
|
@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||
new WebSocketMessage<string>
|
||||
{
|
||||
MessageId = Guid.NewGuid(),
|
||||
MessageType = "KeepAlive"
|
||||
MessageType = SessionMessageType.KeepAlive
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||
public bool IsHidden => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => false;
|
||||
public bool IsEnabled => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLogged => true;
|
||||
|
|
|
@ -1064,10 +1064,10 @@ namespace Emby.Server.Implementations.Session
|
|||
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 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()
|
||||
{
|
||||
|
@ -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 />
|
||||
|
@ -1186,7 +1186,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
CheckDisposed();
|
||||
var session = GetSessionToRemoteControl(sessionId);
|
||||
await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false);
|
||||
await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -1194,7 +1194,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
CheckDisposed();
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -1322,7 +1322,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
CheckDisposed();
|
||||
|
||||
return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken);
|
||||
return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1334,7 +1334,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
CheckDisposed();
|
||||
|
||||
return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken);
|
||||
return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1348,7 +1348,7 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
_logger.LogDebug("Beginning SendServerRestartNotification");
|
||||
|
||||
return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken);
|
||||
return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1484,6 +1484,14 @@ namespace Emby.Server.Implementations.Session
|
|||
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 session = LogSessionActivity(
|
||||
|
@ -1866,7 +1874,7 @@ namespace Emby.Server.Implementations.Session
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken)
|
||||
public Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
|
@ -1879,7 +1887,7 @@ namespace Emby.Server.Implementations.Session
|
|||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
|
@ -1894,7 +1902,7 @@ namespace Emby.Server.Implementations.Session
|
|||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
|
@ -1903,7 +1911,7 @@ namespace Emby.Server.Implementations.Session
|
|||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ using Jellyfin.Data.Events;
|
|||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -316,7 +317,7 @@ namespace Emby.Server.Implementations.Session
|
|||
return webSocket.SendAsync(
|
||||
new WebSocketMessage<int>
|
||||
{
|
||||
MessageType = "ForceKeepAlive",
|
||||
MessageType = SessionMessageType.ForceKeepAlive,
|
||||
Data = WebSocketLostTimeout
|
||||
},
|
||||
CancellationToken.None);
|
||||
|
|
|
@ -11,6 +11,7 @@ using System.Threading.Tasks;
|
|||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Session
|
||||
|
@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
/// <inheritdoc />
|
||||
public Task SendMessage<T>(
|
||||
string name,
|
||||
SessionMessageType name,
|
||||
Guid messageId,
|
||||
T data,
|
||||
CancellationToken cancellationToken)
|
||||
|
|
|
@ -301,8 +301,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
|||
if (_group.IsPaused)
|
||||
{
|
||||
// Pick a suitable time that accounts for latency
|
||||
var delay = _group.GetHighestPing() * 2;
|
||||
delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
|
||||
var delay = Math.Max(_group.GetHighestPing() * 2, GroupInfo.DefaultPing);
|
||||
|
||||
// Unpause group and set starting point in future
|
||||
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
|
||||
|
@ -452,8 +451,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
|||
else
|
||||
{
|
||||
// Client, that was buffering, resumed playback but did not update others in time
|
||||
delay = _group.GetHighestPing() * 2;
|
||||
delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
|
||||
delay = Math.Max(_group.GetHighestPing() * 2, GroupInfo.DefaultPing);
|
||||
|
||||
_group.LastActivity = currentTime.AddMilliseconds(
|
||||
delay);
|
||||
|
@ -497,7 +495,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
|||
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
|
||||
{
|
||||
// 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 />
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -378,7 +379,7 @@ namespace Jellyfin.Api.Controllers
|
|||
public ActionResult PostCapabilities(
|
||||
[FromQuery] string? id,
|
||||
[FromQuery] string? playableMediaTypes,
|
||||
[FromQuery] string? supportedCommands,
|
||||
[FromQuery] GeneralCommandType[] supportedCommands,
|
||||
[FromQuery] bool supportsMediaControl = false,
|
||||
[FromQuery] bool supportsSync = false,
|
||||
[FromQuery] bool supportsPersistentIdentifier = true)
|
||||
|
@ -391,7 +392,7 @@ namespace Jellyfin.Api.Controllers
|
|||
_sessionManager.ReportCapabilities(id, new ClientCapabilities
|
||||
{
|
||||
PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true),
|
||||
SupportedCommands = RequestHelpers.Split(supportedCommands, ',', true),
|
||||
SupportedCommands = supportedCommands,
|
||||
SupportsMediaControl = supportsMediaControl,
|
||||
SupportsSync = supportsSync,
|
||||
SupportsPersistentIdentifier = supportsPersistentIdentifier
|
||||
|
|
|
@ -554,7 +554,7 @@ namespace Jellyfin.Api.Helpers
|
|||
private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress)
|
||||
{
|
||||
var maxBitrate = clientMaxBitrate;
|
||||
var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
|
||||
var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0;
|
||||
|
||||
if (remoteClientMaxBitrate <= 0)
|
||||
{
|
||||
|
|
|
@ -740,10 +740,7 @@ namespace Jellyfin.Api.Helpers
|
|||
/// <param name="state">The state.</param>
|
||||
private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
|
||||
{
|
||||
if (job != null)
|
||||
{
|
||||
job.HasExited = true;
|
||||
}
|
||||
job.HasExited = true;
|
||||
|
||||
_logger.LogDebug("Disposing stream resources");
|
||||
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 MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.WebSocketListeners
|
||||
|
@ -29,11 +30,14 @@ namespace Jellyfin.Api.WebSocketListeners
|
|||
_activityManager.EntryCreated += OnEntryCreated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
protected override string Name => "ActivityLogEntry";
|
||||
/// <inheritdoc />
|
||||
protected override SessionMessageType Type => SessionMessageType.ActivityLogEntry;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override SessionMessageType StartType => SessionMessageType.ActivityLogEntryStart;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override SessionMessageType StopType => SessionMessageType.ActivityLogEntryStop;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data to send.
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -33,11 +34,14 @@ namespace Jellyfin.Api.WebSocketListeners
|
|||
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
protected override string Name => "ScheduledTasksInfo";
|
||||
/// <inheritdoc />
|
||||
protected override SessionMessageType Type => SessionMessageType.ScheduledTasksInfo;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override SessionMessageType StartType => SessionMessageType.ScheduledTasksInfoStart;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override SessionMessageType StopType => SessionMessageType.ScheduledTasksInfoStop;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data to send.
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.WebSocketListeners
|
||||
|
@ -34,7 +35,13 @@ namespace Jellyfin.Api.WebSocketListeners
|
|||
}
|
||||
|
||||
/// <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>
|
||||
/// Gets the data to send.
|
||||
|
|
|
@ -188,6 +188,11 @@ namespace Jellyfin.Data.Entities
|
|||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the subtitle mode.
|
||||
/// </summary>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.System
|
||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System
|
|||
/// <inheritdoc />
|
||||
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.Updates;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
|||
/// <inheritdoc />
|
||||
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.Controller.Events;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
|||
/// <inheritdoc />
|
||||
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.Updates;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
|||
/// <inheritdoc />
|
||||
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.Updates;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
|||
/// <inheritdoc />
|
||||
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.Updates;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
||||
{
|
||||
|
@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates
|
|||
/// <inheritdoc />
|
||||
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 MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
||||
{
|
||||
|
@ -30,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
|||
{
|
||||
await _sessionManager.SendMessageToUserSessions(
|
||||
new List<Guid> { eventArgs.Argument.Id },
|
||||
"UserDeleted",
|
||||
SessionMessageType.UserDeleted,
|
||||
eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ using Jellyfin.Data.Events.Users;
|
|||
using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
||||
{
|
||||
|
@ -33,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users
|
|||
{
|
||||
await _sessionManager.SendMessageToUserSessions(
|
||||
new List<Guid> { e.Argument.Id },
|
||||
"UserUpdated",
|
||||
SessionMessageType.UserUpdated,
|
||||
_userManager.GetUserDto(e.Argument),
|
||||
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
|
||||
modelBuilder
|
||||
.HasDefaultSchema("jellyfin")
|
||||
.HasAnnotation("ProductVersion", "3.1.7");
|
||||
.HasAnnotation("ProductVersion", "3.1.8");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
|
@ -344,6 +344,9 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("MaxActiveSessions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("MaxParentalAgeRating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
public string Name => "InvalidOrMissingAuthenticationProvider";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled => true;
|
||||
public bool IsEnabled => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
|
||||
|
|
|
@ -379,6 +379,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
PasswordResetProviderId = user.PasswordResetProviderId,
|
||||
InvalidLoginAttemptCount = user.InvalidLoginAttemptCount,
|
||||
LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1,
|
||||
MaxActiveSessions = user.MaxActiveSessions,
|
||||
IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator),
|
||||
IsHidden = user.HasPermission(PermissionKind.IsHidden),
|
||||
IsDisabled = user.HasPermission(PermissionKind.IsDisabled),
|
||||
|
@ -701,6 +702,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
user.PasswordResetProviderId = policy.PasswordResetProviderId;
|
||||
user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
|
||||
user.LoginAttemptsBeforeLockout = maxLoginAttempts;
|
||||
user.MaxActiveSessions = policy.MaxActiveSessions;
|
||||
user.SyncPlayAccess = policy.SyncPlayAccess;
|
||||
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
||||
user.SetPermission(PermissionKind.IsHidden, policy.IsHidden);
|
||||
|
@ -799,7 +801,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
|
||||
private IList<IPasswordResetProvider> GetPasswordResetProviders(User user)
|
||||
{
|
||||
var passwordResetProviderId = user?.PasswordResetProviderId;
|
||||
var passwordResetProviderId = user.PasswordResetProviderId;
|
||||
var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
|
||||
|
||||
if (!string.IsNullOrEmpty(passwordResetProviderId))
|
||||
|
|
|
@ -16,6 +16,7 @@ using Jellyfin.Api.Auth.LocalAccessPolicy;
|
|||
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Controllers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using Jellyfin.Server.Formatters;
|
||||
|
@ -166,6 +167,8 @@ namespace Jellyfin.Server.Extensions
|
|||
|
||||
opts.OutputFormatters.Add(new CssOutputFormatter());
|
||||
opts.OutputFormatters.Add(new XmlOutputFormatter());
|
||||
|
||||
opts.ModelBinderProviders.Insert(0, new CommaDelimitedArrayModelBinderProvider());
|
||||
})
|
||||
|
||||
// Clear app parts to avoid other assemblies being picked up
|
||||
|
|
|
@ -125,8 +125,8 @@ namespace Jellyfin.Server.Middleware
|
|||
switch (ex)
|
||||
{
|
||||
case ArgumentException _: return StatusCodes.Status400BadRequest;
|
||||
case AuthenticationException _:
|
||||
case SecurityException _: return StatusCodes.Status401Unauthorized;
|
||||
case AuthenticationException _: return StatusCodes.Status401Unauthorized;
|
||||
case SecurityException _: return StatusCodes.Status403Forbidden;
|
||||
case DirectoryNotFoundException _:
|
||||
case FileNotFoundException _:
|
||||
case ResourceNotFoundException _: return StatusCodes.Status404NotFound;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using Jellyfin.Api.TypeConverters;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Server.Extensions;
|
||||
|
@ -12,6 +13,7 @@ using MediaBrowser.Controller.Configuration;
|
|||
using MediaBrowser.Controller.Extensions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
@ -124,10 +126,15 @@ namespace Jellyfin.Server
|
|||
mainApp.UseStaticFiles();
|
||||
if (appConfig.HostWebClient())
|
||||
{
|
||||
var extensionProvider = new FileExtensionContentTypeProvider();
|
||||
|
||||
// subtitles octopus requires .data files.
|
||||
extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet);
|
||||
mainApp.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
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.
|
||||
/// Required - some clients send an empty string.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The struct type.</typeparam>
|
||||
public class JsonNullableStructConverter<T> : JsonConverter<T?>
|
||||
where T : struct
|
||||
/// <typeparam name="TStruct">The struct type.</typeparam>
|
||||
public class JsonNullableStructConverter<TStruct> : JsonConverter<TStruct?>
|
||||
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 />
|
||||
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))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _baseJsonConverter.Read(ref reader, typeToConvert, options);
|
||||
return JsonSerializer.Deserialize<TStruct>(ref reader, options);
|
||||
}
|
||||
|
||||
/// <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
|
||||
};
|
||||
|
||||
// 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 JsonStringEnumConverter());
|
||||
options.Converters.Add(new JsonNullableStructConverter<int>(baseNullableInt32Converter));
|
||||
options.Converters.Add(new JsonNullableStructConverter<long>(baseNullableInt64Converter));
|
||||
options.Converters.Add(new JsonNullableStructConverterFactory());
|
||||
|
||||
return options;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Net.WebSockets;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
|
@ -28,10 +29,22 @@ namespace MediaBrowser.Controller.Net
|
|||
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// Gets the type used for the messages sent to the client.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
protected abstract string Name { get; }
|
||||
/// <value>The type.</value>
|
||||
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>
|
||||
/// Gets the data to send.
|
||||
|
@ -66,12 +79,12 @@ namespace MediaBrowser.Controller.Net
|
|||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
|
||||
if (message.MessageType == StartType)
|
||||
{
|
||||
Start(message);
|
||||
}
|
||||
|
||||
if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
||||
if (message.MessageType == StopType)
|
||||
{
|
||||
Stop(message);
|
||||
}
|
||||
|
@ -159,7 +172,7 @@ namespace MediaBrowser.Controller.Net
|
|||
new WebSocketMessage<TReturnDataType>
|
||||
{
|
||||
MessageId = Guid.NewGuid(),
|
||||
MessageType = Name,
|
||||
MessageType = Type,
|
||||
Data = data
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
@ -176,7 +189,7 @@ namespace MediaBrowser.Controller.Net
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace MediaBrowser.Controller.Session
|
||||
{
|
||||
|
@ -23,6 +24,6 @@ namespace MediaBrowser.Controller.Session
|
|||
/// <summary>
|
||||
/// Sends the message.
|
||||
/// </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="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken);
|
||||
Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the message to user sessions.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <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>
|
||||
/// Sends the message to user device sessions.
|
||||
|
@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Session
|
|||
/// <param name="data">The data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <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>
|
||||
/// Sends the restart required message.
|
||||
|
|
|
@ -230,8 +230,8 @@ namespace MediaBrowser.Controller.Session
|
|||
/// Gets or sets the supported commands.
|
||||
/// </summary>
|
||||
/// <value>The supported commands.</value>
|
||||
public string[] SupportedCommands
|
||||
=> Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands;
|
||||
public GeneralCommandType[] SupportedCommands
|
||||
=> Capabilities == null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
|
||||
|
||||
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
|
||||
{
|
||||
|
|
|
@ -14,12 +14,12 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
public class GroupInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default ping value used for sessions.
|
||||
/// The default ping value used for sessions.
|
||||
/// </summary>
|
||||
public long DefaultPing { get; } = 500;
|
||||
public const long DefaultPing = 500;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the group identifier.
|
||||
/// Gets the group identifier.
|
||||
/// </summary>
|
||||
/// <value>The group identifier.</value>
|
||||
public Guid GroupId { get; } = Guid.NewGuid();
|
||||
|
@ -58,7 +58,8 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
/// <summary>
|
||||
/// Checks if a session is in this group.
|
||||
/// </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)
|
||||
{
|
||||
return Participants.ContainsKey(sessionId);
|
||||
|
@ -70,16 +71,14 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
/// <param name="session">The session.</param>
|
||||
public void AddSession(SessionInfo session)
|
||||
{
|
||||
if (ContainsSession(session.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var member = new GroupMember();
|
||||
member.Session = session;
|
||||
member.Ping = DefaultPing;
|
||||
member.IsBuffering = false;
|
||||
Participants[session.Id] = member;
|
||||
Participants.TryAdd(
|
||||
session.Id,
|
||||
new GroupMember
|
||||
{
|
||||
Session = session,
|
||||
Ping = DefaultPing,
|
||||
IsBuffering = false
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -88,12 +87,7 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
/// <param name="session">The session.</param>
|
||||
public void RemoveSession(SessionInfo session)
|
||||
{
|
||||
if (!ContainsSession(session.Id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Participants.Remove(session.Id, out _);
|
||||
Participants.Remove(session.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -103,18 +97,16 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
/// <param name="ping">The ping.</param>
|
||||
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>
|
||||
/// Gets the highest ping in the group.
|
||||
/// </summary>
|
||||
/// <value name="session">The highest ping in the group.</value>
|
||||
/// <returns>The highest ping in the group.</returns>
|
||||
public long GetHighestPing()
|
||||
{
|
||||
long max = long.MinValue;
|
||||
|
@ -133,18 +125,16 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
/// <param name="isBuffering">The state.</param>
|
||||
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>
|
||||
/// Gets the group buffering state.
|
||||
/// </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()
|
||||
{
|
||||
foreach (var session in Participants.Values)
|
||||
|
@ -161,7 +151,7 @@ namespace MediaBrowser.Controller.SyncPlay
|
|||
/// <summary>
|
||||
/// Checks if the group is empty.
|
||||
/// </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()
|
||||
{
|
||||
return Participants.Count == 0;
|
||||
|
|
|
@ -25,8 +25,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
public bool EnableInternetProviders { get; set; }
|
||||
|
||||
public bool ImportMissingEpisodes { get; set; }
|
||||
|
||||
public bool EnableAutomaticSeriesGrouping { get; set; }
|
||||
|
||||
public bool EnableEmbeddedTitles { get; set; }
|
||||
|
|
|
@ -455,9 +455,10 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
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",
|
||||
item.Path ?? "Unknown path");
|
||||
item.Path ?? "Unknown path",
|
||||
audioStream.Codec ?? "Unknown codec");
|
||||
|
||||
return (Enumerable.Empty<PlayMethod>(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
|
||||
}
|
||||
|
@ -972,9 +973,10 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
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",
|
||||
mediaSource.Path ?? "Unknown path");
|
||||
mediaSource.Path ?? "Unknown path",
|
||||
videoStream.Codec ?? "Unknown codec");
|
||||
|
||||
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
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
||||
namespace MediaBrowser.Model.Net
|
||||
{
|
||||
|
@ -15,7 +16,7 @@ namespace MediaBrowser.Model.Net
|
|||
/// Gets or sets the type of the message.
|
||||
/// </summary>
|
||||
/// <value>The type of the message.</value>
|
||||
public string MessageType { get; set; }
|
||||
public SessionMessageType MessageType { get; set; }
|
||||
|
||||
public Guid MessageId { get; set; }
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Session
|
|||
{
|
||||
public string[] PlayableMediaTypes { get; set; }
|
||||
|
||||
public string[] SupportedCommands { get; set; }
|
||||
public GeneralCommandType[] SupportedCommands { get; set; }
|
||||
|
||||
public bool SupportsMediaControl { get; set; }
|
||||
|
||||
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Session
|
|||
public ClientCapabilities()
|
||||
{
|
||||
PlayableMediaTypes = Array.Empty<string>();
|
||||
SupportedCommands = Array.Empty<string>();
|
||||
SupportedCommands = Array.Empty<GeneralCommandType>();
|
||||
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 MaxActiveSessions { get; set; }
|
||||
|
||||
public bool EnablePublicSharing { get; set; }
|
||||
|
||||
public Guid[] BlockedMediaFolders { get; set; }
|
||||
|
@ -144,6 +146,8 @@ namespace MediaBrowser.Model.Users
|
|||
|
||||
LoginAttemptsBeforeLockout = -1;
|
||||
|
||||
MaxActiveSessions = 0;
|
||||
|
||||
EnableAllChannels = true;
|
||||
EnabledChannels = Array.Empty<Guid>();
|
||||
|
||||
|
|
|
@ -158,6 +158,14 @@ namespace MediaBrowser.Providers.Manager
|
|||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
||||
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;
|
||||
|
||||
// Workaround for tvheadend channel icons
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.8" />
|
||||
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.1.2" />
|
||||
<PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
|
||||
<PackageReference Include="TvDbSharper" Version="3.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -80,32 +80,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||
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(
|
||||
string imdbId,
|
||||
string language,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
|
@ -12,25 +13,25 @@ using MediaBrowser.Controller.Entities.Movies;
|
|||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
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
|
||||
{
|
||||
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory)
|
||||
public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
|
@ -48,112 +49,60 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
|||
|
||||
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();
|
||||
|
||||
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 Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
return new List<RemoteImageInfo>();
|
||||
}
|
||||
var language = item.GetPreferredMetadataLanguage();
|
||||
|
||||
private IEnumerable<RemoteImageInfo> GetImages(CollectionResult obj, string language, string baseUrl)
|
||||
{
|
||||
var list = new List<RemoteImageInfo>();
|
||||
var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var images = obj.Images ?? new CollectionImages();
|
||||
|
||||
list.AddRange(GetPosters(images).Select(i => new RemoteImageInfo
|
||||
if (collection?.Images == null)
|
||||
{
|
||||
Url = baseUrl + i.File_Path,
|
||||
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
|
||||
}));
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
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,
|
||||
CommunityRating = i.Vote_Average,
|
||||
VoteCount = i.Vote_Count,
|
||||
Width = i.Width,
|
||||
Height = i.Height,
|
||||
ProviderName = Name,
|
||||
Type = ImageType.Backdrop,
|
||||
RatingType = RatingType.Score
|
||||
}));
|
||||
var poster = collection.Images.Posters[i];
|
||||
remoteImages.Add(new RemoteImageInfo
|
||||
{
|
||||
Url = _tmdbClientManager.GetPosterUrl(poster.FilePath),
|
||||
CommunityRating = poster.VoteAverage,
|
||||
VoteCount = poster.VoteCount,
|
||||
Width = poster.Width,
|
||||
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);
|
||||
|
||||
return list.OrderByDescending(i =>
|
||||
for (var i = 0; i < collection.Images.Backdrops.Count; 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)
|
||||
{
|
||||
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);
|
||||
return remoteImages.OrderByLanguageDescending(language);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
|
||||
|
|
|
@ -3,270 +3,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
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
|
||||
{
|
||||
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 ILibraryManager _libraryManager;
|
||||
private readonly TmdbClientManager _tmdbClientManager;
|
||||
|
||||
public TmdbBoxSetProvider(
|
||||
ILogger<TmdbBoxSetProvider> logger,
|
||||
IJsonSerializer json,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager)
|
||||
public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_json = json;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
Current = this;
|
||||
_tmdbClientManager = tmdbClientManager;
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
|
||||
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);
|
||||
var info = _json.DeserializeFromFile<CollectionResult>(dataFilePath);
|
||||
|
||||
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");
|
||||
if (collection == null)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
|
||||
var result = new RemoteSearchResult
|
||||
{
|
||||
Name = info.Name,
|
||||
SearchProviderName = Name,
|
||||
ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path)
|
||||
Name = collection.Name,
|
||||
SearchProviderName = Name
|
||||
};
|
||||
|
||||
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 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)
|
||||
{
|
||||
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
|
||||
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 (searchResult != null)
|
||||
if (searchResults != null && searchResults.Count > 0)
|
||||
{
|
||||
tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb);
|
||||
tmdbId = searchResults[0].Id;
|
||||
}
|
||||
}
|
||||
|
||||
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.Item = GetItem(mainResult);
|
||||
result.Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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…
Reference in New Issue
Block a user