support adding additional users to sessions

This commit is contained in:
Luke Pulverenti 2014-01-03 21:35:41 -05:00
parent 6da4231244
commit 135168b0e0
15 changed files with 318 additions and 68 deletions

View File

@ -172,6 +172,28 @@ namespace MediaBrowser.Api
public long? TimeoutMs { get; set; }
}
[Route("/Sessions/{Id}/Users/{UserId}", "POST")]
[Api(("Adds an additional user to a session"))]
public class AddUserToSession : IReturnVoid
{
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public Guid Id { get; set; }
[ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public Guid UserId { get; set; }
}
[Route("/Sessions/{Id}/Users/{UserId}", "DELETE")]
[Api(("Removes an additional user from a session"))]
public class RemoveUserFromSession : IReturnVoid
{
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public Guid Id { get; set; }
[ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public Guid UserId { get; set; }
}
/// <summary>
/// Class SessionsService
/// </summary>
@ -217,7 +239,7 @@ namespace MediaBrowser.Api
if (!user.Configuration.EnableRemoteControlOfOtherUsers)
{
result = result.Where(i => i.User == null || i.User.Id == request.ControllableByUserId.Value);
result = result.Where(i => !i.UserId.HasValue || i.UserId.Value == request.ControllableByUserId.Value);
}
}
@ -303,5 +325,15 @@ namespace MediaBrowser.Api
Task.WaitAll(task);
}
public void Post(AddUserToSession request)
{
_sessionManager.AddAdditionalUser(request.Id, request.UserId);
}
public void Delete(RemoveUserFromSession request)
{
_sessionManager.RemoveAdditionalUser(request.Id, request.UserId);
}
}
}

View File

@ -45,6 +45,9 @@ namespace MediaBrowser.Controller.Entities.TV
public List<MediaUrl> RemoteTrailers { get; set; }
/// <summary>
/// airdate, dvd or absolute
/// </summary>
public string DisplayOrder { get; set; }
/// <summary>

View File

@ -1,5 +1,6 @@
using System;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Library
{
@ -8,10 +9,14 @@ namespace MediaBrowser.Controller.Library
/// </summary>
public class PlaybackProgressEventArgs : EventArgs
{
public User User { get; set; }
public List<User> Users { get; set; }
public long? PlaybackPositionTicks { get; set; }
public BaseItem Item { get; set; }
public UserItemData UserData { get; set; }
public PlaybackProgressEventArgs()
{
Users = new List<User>();
}
}
public class PlaybackStopEventArgs : PlaybackProgressEventArgs

View File

@ -141,5 +141,19 @@ namespace MediaBrowser.Controller.Session
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendServerRestartNotification(CancellationToken cancellationToken);
/// <summary>
/// Adds the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="userId">The user identifier.</param>
void AddAdditionalUser(Guid sessionId, Guid userId);
/// <summary>
/// Removes the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="userId">The user identifier.</param>
void RemoveAdditionalUser(Guid sessionId, Guid userId);
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
@ -12,8 +13,12 @@ namespace MediaBrowser.Controller.Session
public SessionInfo()
{
QueueableMediaTypes = new List<string>();
AdditionalUsersPresent = new List<SessionUserInfo>();
}
public List<SessionUserInfo> AdditionalUsersPresent { get; set; }
/// <summary>
/// Gets or sets the remote end point.
/// </summary>
@ -42,7 +47,13 @@ namespace MediaBrowser.Controller.Session
/// Gets or sets the user id.
/// </summary>
/// <value>The user id.</value>
public User User { get; set; }
public Guid? UserId { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
/// <value>The username.</value>
public string UserName { get; set; }
/// <summary>
/// Gets or sets the type of the client.

View File

@ -392,6 +392,12 @@ namespace MediaBrowser.Model.Dto
/// <value>The album.</value>
public string Album { get; set; }
/// <summary>
/// Gets or sets the type of the collection.
/// </summary>
/// <value>The type of the collection.</value>
public string CollectionType { get; set; }
/// <summary>
/// Gets or sets the display order.
/// </summary>

View File

@ -43,6 +43,12 @@ namespace MediaBrowser.Model.Session
/// <value>The name of the user.</value>
public string UserName { get; set; }
/// <summary>
/// Gets or sets the additional users present.
/// </summary>
/// <value>The additional users present.</value>
public List<SessionUserInfo> AdditionalUsersPresent { get; set; }
/// <summary>
/// Gets or sets the application version.
/// </summary>
@ -128,5 +134,27 @@ namespace MediaBrowser.Model.Session
public bool SupportsRemoteControl { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public SessionInfoDto()
{
AdditionalUsersPresent = new List<SessionUserInfo>();
}
}
/// <summary>
/// Class SessionUserInfo.
/// </summary>
public class SessionUserInfo
{
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
/// <summary>
/// Gets or sets the name of the user.
/// </summary>
/// <value>The name of the user.</value>
public string UserName { get; set; }
}
}

View File

@ -244,7 +244,8 @@ namespace MediaBrowser.Server.Implementations.Dto
ApplicationVersion = session.ApplicationVersion,
CanSeek = session.CanSeek,
QueueableMediaTypes = session.QueueableMediaTypes,
RemoteEndPoint = session.RemoteEndPoint
RemoteEndPoint = session.RemoteEndPoint,
AdditionalUsersPresent = session.AdditionalUsersPresent
};
if (session.NowPlayingItem != null)
@ -252,11 +253,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.NowPlayingItem = GetBaseItemInfo(session.NowPlayingItem);
}
if (session.User != null)
if (session.UserId.HasValue)
{
dto.UserId = session.User.Id.ToString("N");
dto.UserName = session.User.Name;
dto.UserId = session.UserId.Value.ToString("N");
}
dto.UserName = session.UserName;
return dto;
}
@ -770,6 +771,12 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
}
var collectionFolder = item as CollectionFolder;
if (collectionFolder != null)
{
dto.CollectionType = collectionFolder.CollectionType;
}
if (fields.Contains(ItemFields.RemoteTrailers))
{
dto.RemoteTrailers = hasTrailers != null ?

View File

@ -190,7 +190,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{
var id = user.Id;
var userSessions = _sessionManager.Sessions
.Where(u => u.User != null && u.User.Id == id && u.SessionController != null && u.IsActive)
.Where(u => u.UserId.HasValue && u.UserId.Value == id && u.SessionController != null && u.IsActive)
.ToList();
if (userSessions.Count > 0)

View File

@ -94,7 +94,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
{
var userId = pair.Key;
var userSessions = _sessionManager.Sessions
.Where(u => u.User != null && u.User.Id == userId && u.SessionController != null && u.IsActive)
.Where(u => u.UserId.HasValue && u.UserId.Value == userId && u.SessionController != null && u.IsActive)
.ToList();
if (userSessions.Count > 0)

View File

@ -39,6 +39,7 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
/// <summary>
/// Gets or sets the configuration manager.
@ -75,13 +76,14 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="logger">The logger.</param>
/// <param name="userRepository">The user repository.</param>
/// <param name="libraryManager">The library manager.</param>
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager)
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager)
{
_userDataRepository = userDataRepository;
_configurationManager = configurationManager;
_logger = logger;
_userRepository = userRepository;
_libraryManager = libraryManager;
_userManager = userManager;
}
/// <summary>
@ -140,7 +142,10 @@ namespace MediaBrowser.Server.Implementations.Session
var activityDate = DateTime.UtcNow;
var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, user);
var userId = user == null ? (Guid?)null : user.Id;
var username = user == null ? null : user.Name;
var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, userId, username);
session.LastActivityDate = activityDate;
@ -209,9 +214,10 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <param name="userId">The user identifier.</param>
/// <param name="username">The username.</param>
/// <returns>SessionInfo.</returns>
private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Guid? userId, string username)
{
var key = clientType + deviceId + appVersion;
@ -224,9 +230,15 @@ namespace MediaBrowser.Server.Implementations.Session
});
connection.DeviceName = deviceName;
connection.User = user;
connection.UserId = userId;
connection.UserName = username;
connection.RemoteEndPoint = remoteEndPoint;
if (!userId.HasValue)
{
connection.AdditionalUsersPresent.Clear();
}
if (connection.SessionController == null)
{
connection.SessionController = _sessionFactories
@ -237,6 +249,31 @@ namespace MediaBrowser.Server.Implementations.Session
return connection;
}
private List<User> GetUsers(SessionInfo session)
{
var users = new List<User>();
if (session.UserId.HasValue)
{
var user = _userManager.GetUserById(session.UserId.Value);
if (user == null)
{
throw new InvalidOperationException("User not found");
}
users.Add(user);
var additionalUsers = session.AdditionalUsersPresent
.Select(i => _userManager.GetUserById(new Guid(i.UserId)))
.Where(i => i != null);
users.AddRange(additionalUsers);
}
return users;
}
/// <summary>
/// Used to report that playback has started for an item
/// </summary>
@ -265,9 +302,33 @@ namespace MediaBrowser.Server.Implementations.Session
var key = item.GetUserDataKey();
var user = session.User;
var users = GetUsers(session);
var data = _userDataRepository.GetUserData(user.Id, key);
foreach (var user in users)
{
await OnPlaybackStart(user.Id, key, item).ConfigureAwait(false);
}
// Nothing to save here
// Fire events to inform plugins
EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
{
Item = item,
Users = users
}, _logger);
}
/// <summary>
/// Called when [playback start].
/// </summary>
/// <param name="userId">The user identifier.</param>
/// <param name="userDataKey">The user data key.</param>
/// <param name="item">The item.</param>
/// <returns>Task.</returns>
private async Task OnPlaybackStart(Guid userId, string userDataKey, IHasUserData item)
{
var data = _userDataRepository.GetUserData(userId, userDataKey);
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
@ -277,17 +338,7 @@ namespace MediaBrowser.Server.Implementations.Session
data.Played = true;
}
await _userDataRepository.SaveUserData(user.Id, info.Item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
// Nothing to save here
// Fire events to inform plugins
EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs
{
Item = item,
User = user,
UserData = data
}, _logger);
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
@ -315,27 +366,34 @@ namespace MediaBrowser.Server.Implementations.Session
var key = info.Item.GetUserDataKey();
var user = session.User;
var users = GetUsers(session);
var data = _userDataRepository.GetUserData(user.Id, key);
if (info.PositionTicks.HasValue)
foreach (var user in users)
{
UpdatePlayState(info.Item, data, info.PositionTicks.Value);
await _userDataRepository.SaveUserData(user.Id, info.Item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
await OnPlaybackProgress(user.Id, key, info.Item, info.PositionTicks).ConfigureAwait(false);
}
EventHelper.QueueEventIfNotNull(PlaybackProgress, this, new PlaybackProgressEventArgs
{
Item = info.Item,
User = user,
PlaybackPositionTicks = info.PositionTicks,
UserData = data
Users = users,
PlaybackPositionTicks = info.PositionTicks
}, _logger);
}
private async Task OnPlaybackProgress(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
{
var data = _userDataRepository.GetUserData(userId, userDataKey);
if (positionTicks.HasValue)
{
UpdatePlayState(item, data, positionTicks.Value);
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
}
}
/// <summary>
/// Used to report that playback has ended for an item
/// </summary>
@ -371,14 +429,32 @@ namespace MediaBrowser.Server.Implementations.Session
var key = info.Item.GetUserDataKey();
var user = session.User;
var users = GetUsers(session);
var data = _userDataRepository.GetUserData(user.Id, key);
var playedToCompletion = false;
foreach (var user in users)
{
playedToCompletion = await OnPlaybackStopped(user.Id, key, info.Item, info.PositionTicks).ConfigureAwait(false);
}
EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs
{
Item = info.Item,
Users = users,
PlaybackPositionTicks = info.PositionTicks,
PlayedToCompletion = playedToCompletion
}, _logger);
}
private async Task<bool> OnPlaybackStopped(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
{
var data = _userDataRepository.GetUserData(userId, userDataKey);
bool playedToCompletion;
if (info.PositionTicks.HasValue)
if (positionTicks.HasValue)
{
playedToCompletion = UpdatePlayState(info.Item, data, info.PositionTicks.Value);
playedToCompletion = UpdatePlayState(item, data, positionTicks.Value);
}
else
{
@ -389,17 +465,9 @@ namespace MediaBrowser.Server.Implementations.Session
playedToCompletion = true;
}
await _userDataRepository.SaveUserData(user.Id, info.Item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false);
EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs
{
Item = info.Item,
User = user,
PlaybackPositionTicks = info.PositionTicks,
UserData = data,
PlayedToCompletion = playedToCompletion
}, _logger);
return playedToCompletion;
}
/// <summary>
@ -462,12 +530,12 @@ namespace MediaBrowser.Server.Implementations.Session
}
/// <summary>
/// Gets the session for remote control.
/// Gets the session.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <param name="sessionId">The session identifier.</param>
/// <returns>SessionInfo.</returns>
/// <exception cref="ResourceNotFoundException"></exception>
private SessionInfo GetSessionForRemoteControl(Guid sessionId)
private SessionInfo GetSession(Guid sessionId)
{
var session = Sessions.First(i => i.Id.Equals(sessionId));
@ -476,6 +544,19 @@ namespace MediaBrowser.Server.Implementations.Session
throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
}
return session;
}
/// <summary>
/// Gets the session for remote control.
/// </summary>
/// <param name="sessionId">The session id.</param>
/// <returns>SessionInfo.</returns>
/// <exception cref="ResourceNotFoundException"></exception>
private SessionInfo GetSessionForRemoteControl(Guid sessionId)
{
var session = GetSession(sessionId);
if (!session.SupportsRemoteControl)
{
throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
@ -595,7 +676,7 @@ namespace MediaBrowser.Server.Implementations.Session
_logger.ErrorException("Error in SendRestartRequiredNotification.", ex);
}
}));
}, cancellationToken));
return Task.WhenAll(tasks);
}
@ -649,5 +730,68 @@ namespace MediaBrowser.Server.Implementations.Session
return Task.WhenAll(tasks);
}
/// <summary>
/// Adds the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="userId">The user identifier.</param>
/// <exception cref="System.UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception>
/// <exception cref="System.ArgumentException">The requested user is already the primary user of the session.</exception>
public void AddAdditionalUser(Guid sessionId, Guid userId)
{
var session = GetSession(sessionId);
if (!session.UserId.HasValue)
{
throw new UnauthorizedAccessException("Cannot modify additional users without authenticating first.");
}
if (session.UserId.Value == userId)
{
throw new ArgumentException("The requested user is already the primary user of the session.");
}
if (session.AdditionalUsersPresent.All(i => new Guid(i.UserId) != userId))
{
var user = _userManager.GetUserById(userId);
session.AdditionalUsersPresent.Add(new SessionUserInfo
{
UserId = userId.ToString("N"),
UserName = user.Name
});
}
}
/// <summary>
/// Removes the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="userId">The user identifier.</param>
/// <exception cref="System.UnauthorizedAccessException">Cannot modify additional users without authenticating first.</exception>
/// <exception cref="System.ArgumentException">The requested user is already the primary user of the session.</exception>
public void RemoveAdditionalUser(Guid sessionId, Guid userId)
{
var session = GetSession(sessionId);
if (!session.UserId.HasValue)
{
throw new UnauthorizedAccessException("Cannot modify additional users without authenticating first.");
}
if (session.UserId.Value == userId)
{
throw new ArgumentException("The requested user is already the primary user of the session.");
}
var user = session.AdditionalUsersPresent.FirstOrDefault(i => new Guid(i.UserId) == userId);
if (user != null)
{
session.AdditionalUsersPresent.Remove(user);
}
}
}
}

View File

@ -191,7 +191,7 @@ namespace MediaBrowser.Server.Implementations.Session
var session = GetSessionFromMessage(message);
if (session != null && session.User != null)
if (session != null && session.UserId.HasValue)
{
var vals = message.Data.Split('|');
@ -229,7 +229,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
var session = GetSessionFromMessage(message);
if (session != null && session.User != null)
if (session != null && session.UserId.HasValue)
{
var vals = message.Data.Split('|');
@ -273,7 +273,7 @@ namespace MediaBrowser.Server.Implementations.Session
var session = GetSessionFromMessage(message);
if (session != null && session.User != null)
if (session != null && session.UserId.HasValue)
{
var vals = message.Data.Split('|');

View File

@ -254,7 +254,7 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager));
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager);
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager);
RegisterSingleInstance(SessionManager);
HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html");

View File

@ -2,7 +2,7 @@
window.MediaBrowser = {};
}
MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, window) {
MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, window, FileReader) {
/**
* Creates a new api client instance
@ -3982,7 +3982,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
};
}(jQuery, navigator, window.JSON, window.WebSocket, setTimeout, window);
}(jQuery, navigator, window.JSON, window.WebSocket, setTimeout, window, window.FileReader);
/**
* Provides a friendly way to create an api client instance using information from the browser's current url
@ -4208,7 +4208,7 @@ MediaBrowser.SHA1 = function (msg) {
}
if (matched.platform) {
browser[matched.platform] = true
browser[matched.platform] = true;
}
// Chrome is Webkit, but Webkit is also Safari.

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.217" targetFramework="net45" />
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.218" targetFramework="net45" />
</packages>