completed auth database

This commit is contained in:
Luke Pulverenti 2014-07-07 21:41:03 -04:00
parent 379fa00228
commit c02e917f56
41 changed files with 936 additions and 139 deletions

View file

@ -37,7 +37,7 @@ namespace MediaBrowser.Api
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1,1); public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class. /// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
@ -102,7 +102,7 @@ namespace MediaBrowser.Api
{ {
var jobCount = _activeTranscodingJobs.Count; var jobCount = _activeTranscodingJobs.Count;
Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, FileDeleteMode.All)); Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, path => true));
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0) if (jobCount > 0)
@ -295,17 +295,18 @@ namespace MediaBrowser.Api
{ {
var job = (TranscodingJob)state; var job = (TranscodingJob)state;
KillTranscodingJob(job, FileDeleteMode.All); KillTranscodingJob(job, path => true);
} }
/// <summary> /// <summary>
/// Kills the single transcoding job. /// Kills the single transcoding job.
/// </summary> /// </summary>
/// <param name="deviceId">The device id.</param> /// <param name="deviceId">The device id.</param>
/// <param name="deleteMode">The delete mode.</param> /// <param name="delete">The delete.</param>
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param> /// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">sourcePath</exception> /// <exception cref="System.ArgumentNullException">sourcePath</exception>
internal async Task KillTranscodingJobs(string deviceId, FileDeleteMode deleteMode, bool acquireLock) internal async Task KillTranscodingJobs(string deviceId, Func<string, bool> delete, bool acquireLock)
{ {
if (string.IsNullOrEmpty(deviceId)) if (string.IsNullOrEmpty(deviceId))
{ {
@ -330,12 +331,12 @@ namespace MediaBrowser.Api
{ {
await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); await TranscodingStartLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
} }
try try
{ {
foreach (var job in jobs) foreach (var job in jobs)
{ {
KillTranscodingJob(job, deleteMode); KillTranscodingJob(job, delete);
} }
} }
finally finally
@ -352,10 +353,11 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
/// <param name="deviceId">The device identifier.</param> /// <param name="deviceId">The device identifier.</param>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="deleteMode">The delete mode.</param> /// <param name="delete">The delete.</param>
/// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param> /// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">deviceId</exception> /// <exception cref="System.ArgumentNullException">deviceId</exception>
internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, FileDeleteMode deleteMode, bool acquireLock) internal async Task KillTranscodingJobs(string deviceId, TranscodingJobType type, Func<string, bool> delete, bool acquireLock)
{ {
if (string.IsNullOrEmpty(deviceId)) if (string.IsNullOrEmpty(deviceId))
{ {
@ -385,7 +387,7 @@ namespace MediaBrowser.Api
{ {
foreach (var job in jobs) foreach (var job in jobs)
{ {
KillTranscodingJob(job, deleteMode); KillTranscodingJob(job, delete);
} }
} }
finally finally
@ -401,8 +403,8 @@ namespace MediaBrowser.Api
/// Kills the transcoding job. /// Kills the transcoding job.
/// </summary> /// </summary>
/// <param name="job">The job.</param> /// <param name="job">The job.</param>
/// <param name="deleteMode">The delete mode.</param> /// <param name="delete">The delete.</param>
private void KillTranscodingJob(TranscodingJob job, FileDeleteMode deleteMode) private void KillTranscodingJob(TranscodingJob job, Func<string, bool> delete)
{ {
lock (_activeTranscodingJobs) lock (_activeTranscodingJobs)
{ {
@ -454,7 +456,7 @@ namespace MediaBrowser.Api
} }
} }
if (deleteMode == FileDeleteMode.All) if (delete(job.Path))
{ {
DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
} }
@ -593,10 +595,4 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
Hls Hls
} }
public enum FileDeleteMode
{
None,
All
}
} }

View file

@ -18,6 +18,7 @@ namespace MediaBrowser.Api
/// Class GetConfiguration /// Class GetConfiguration
/// </summary> /// </summary>
[Route("/System/Configuration", "GET", Summary = "Gets application configuration")] [Route("/System/Configuration", "GET", Summary = "Gets application configuration")]
[Authenticated]
public class GetConfiguration : IReturn<ServerConfiguration> public class GetConfiguration : IReturn<ServerConfiguration>
{ {

View file

@ -22,7 +22,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public abstract class BaseHlsService : BaseStreamingService public abstract class BaseHlsService : BaseStreamingService
{ {
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder) protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder)
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder)
{ {
} }
@ -103,8 +104,8 @@ namespace MediaBrowser.Api.Playback.Hls
} }
else else
{ {
await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.All, false).ConfigureAwait(false); await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => true, false).ConfigureAwait(false);
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
@ -252,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{ {
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
var itsOffsetMs = hlsVideoRequest == null var itsOffsetMs = hlsVideoRequest == null
? 0 ? 0
: hlsVideoRequest.TimeStampOffsetMs; : hlsVideoRequest.TimeStampOffsetMs;

View file

@ -127,8 +127,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
// TODO: Delete files from other jobs, but not this one await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase), false).ConfigureAwait(false);
await ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, TranscodingJobType.Hls, FileDeleteMode.None, false).ConfigureAwait(false);
if (currentTranscodingIndex.HasValue) if (currentTranscodingIndex.HasValue)
{ {

View file

@ -74,7 +74,7 @@ namespace MediaBrowser.Api.Playback.Hls
public void Delete(StopEncodingProcess request) public void Delete(StopEncodingProcess request)
{ {
var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, FileDeleteMode.All, true); var task = ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, path => true, true);
Task.WaitAll(task); Task.WaitAll(task);
} }

View file

@ -213,6 +213,7 @@ namespace MediaBrowser.Api
} }
[Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")]
[Authenticated]
public class PostCapabilities : IReturnVoid public class PostCapabilities : IReturnVoid
{ {
/// <summary> /// <summary>
@ -235,6 +236,11 @@ namespace MediaBrowser.Api
public bool SupportsMediaControl { get; set; } public bool SupportsMediaControl { get; set; }
} }
[Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
public class ReportSessionEnded : IReturnVoid
{
}
/// <summary> /// <summary>
/// Class SessionsService /// Class SessionsService
/// </summary> /// </summary>
@ -246,16 +252,26 @@ namespace MediaBrowser.Api
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IAuthorizationContext _authContext;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SessionsService" /> class. /// Initializes a new instance of the <see cref="SessionsService" /> class.
/// </summary> /// </summary>
/// <param name="sessionManager">The session manager.</param> /// <param name="sessionManager">The session manager.</param>
/// <param name="userManager">The user manager.</param> /// <param name="userManager">The user manager.</param>
public SessionsService(ISessionManager sessionManager, IUserManager userManager) public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext)
{ {
_sessionManager = sessionManager; _sessionManager = sessionManager;
_userManager = userManager; _userManager = userManager;
_authContext = authContext;
}
public void Post(ReportSessionEnded request)
{
var auth = _authContext.GetAuthorizationInfo(Request);
_sessionManager.Logout(auth.Token);
} }
/// <summary> /// <summary>

View file

@ -15,6 +15,7 @@ namespace MediaBrowser.Api
/// Class GetSystemInfo /// Class GetSystemInfo
/// </summary> /// </summary>
[Route("/System/Info", "GET", Summary = "Gets information about the server")] [Route("/System/Info", "GET", Summary = "Gets information about the server")]
[Authenticated]
public class GetSystemInfo : IReturn<SystemInfo> public class GetSystemInfo : IReturn<SystemInfo>
{ {

View file

@ -4,7 +4,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
using ServiceStack; using ServiceStack;
using ServiceStack.Text.Controller; using ServiceStack.Text.Controller;
@ -19,6 +18,7 @@ namespace MediaBrowser.Api
/// Class GetUsers /// Class GetUsers
/// </summary> /// </summary>
[Route("/Users", "GET", Summary = "Gets a list of users")] [Route("/Users", "GET", Summary = "Gets a list of users")]
[Authenticated]
public class GetUsers : IReturn<List<UserDto>> public class GetUsers : IReturn<List<UserDto>>
{ {
[ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
@ -37,6 +37,7 @@ namespace MediaBrowser.Api
/// Class GetUser /// Class GetUser
/// </summary> /// </summary>
[Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")]
[Authenticated]
public class GetUser : IReturn<UserDto> public class GetUser : IReturn<UserDto>
{ {
/// <summary> /// <summary>
@ -159,11 +160,6 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
public class UserService : BaseApiService, IHasAuthorization public class UserService : BaseApiService, IHasAuthorization
{ {
/// <summary>
/// The _XML serializer
/// </summary>
private readonly IXmlSerializer _xmlSerializer;
/// <summary> /// <summary>
/// The _user manager /// The _user manager
/// </summary> /// </summary>
@ -176,19 +172,12 @@ namespace MediaBrowser.Api
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserService" /> class. /// Initializes a new instance of the <see cref="UserService" /> class.
/// </summary> /// </summary>
/// <param name="xmlSerializer">The XML serializer.</param>
/// <param name="userManager">The user manager.</param> /// <param name="userManager">The user manager.</param>
/// <param name="dtoService">The dto service.</param> /// <param name="dtoService">The dto service.</param>
/// <param name="sessionMananger">The session mananger.</param>
/// <exception cref="System.ArgumentNullException">xmlSerializer</exception> /// <exception cref="System.ArgumentNullException">xmlSerializer</exception>
public UserService(IXmlSerializer xmlSerializer, IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger) public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger)
: base()
{ {
if (xmlSerializer == null)
{
throw new ArgumentNullException("xmlSerializer");
}
_xmlSerializer = xmlSerializer;
_userManager = userManager; _userManager = userManager;
_dtoService = dtoService; _dtoService = dtoService;
_sessionMananger = sessionMananger; _sessionMananger = sessionMananger;
@ -196,6 +185,11 @@ namespace MediaBrowser.Api
public object Get(GetPublicUsers request) public object Get(GetPublicUsers request)
{ {
if (!Request.IsLocal && !_sessionMananger.IsLocal(Request.RemoteIp))
{
return ToOptimizedResult(new List<UserDto>());
}
return Get(new GetUsers return Get(new GetUsers
{ {
IsHidden = false, IsHidden = false,
@ -368,9 +362,15 @@ namespace MediaBrowser.Api
{ {
throw new ArgumentException("There must be at least one enabled user in the system."); throw new ArgumentException("There must be at least one enabled user in the system.");
} }
var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N"));
Task.WaitAll(revokeTask);
} }
var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ?
_userManager.UpdateUser(user) :
_userManager.RenameUser(user, dtoUser.Name);
Task.WaitAll(task); Task.WaitAll(task);

View file

@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Channels
{ {
public string Name { get; set; } public string Name { get; set; }
public string SeriesName { get; set; }
public string Id { get; set; } public string Id { get; set; }
public ChannelItemType Type { get; set; } public ChannelItemType Type { get; set; }
@ -28,8 +30,6 @@ namespace MediaBrowser.Controller.Channels
public long? RunTimeTicks { get; set; } public long? RunTimeTicks { get; set; }
public bool IsInfiniteStream { get; set; }
public string ImageUrl { get; set; } public string ImageUrl { get; set; }
public ChannelMediaType MediaType { get; set; } public ChannelMediaType MediaType { get; set; }
@ -43,9 +43,14 @@ namespace MediaBrowser.Controller.Channels
public int? ProductionYear { get; set; } public int? ProductionYear { get; set; }
public DateTime? DateCreated { get; set; } public DateTime? DateCreated { get; set; }
public int? IndexNumber { get; set; }
public int? ParentIndexNumber { get; set; }
public List<ChannelMediaInfo> MediaSources { get; set; } public List<ChannelMediaInfo> MediaSources { get; set; }
public bool IsInfiniteStream { get; set; }
public ChannelItemInfo() public ChannelItemInfo()
{ {
MediaSources = new List<ChannelMediaInfo>(); MediaSources = new List<ChannelMediaInfo>();

View file

@ -0,0 +1,37 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Collections
{
public class CollectionCreatedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the collection.
/// </summary>
/// <value>The collection.</value>
public BoxSet Collection { get; set; }
/// <summary>
/// Gets or sets the options.
/// </summary>
/// <value>The options.</value>
public CollectionCreationOptions Options { get; set; }
}
public class CollectionModifiedEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the collection.
/// </summary>
/// <value>The collection.</value>
public BoxSet Collection { get; set; }
/// <summary>
/// Gets or sets the items changed.
/// </summary>
/// <value>The items changed.</value>
public List<BaseItem> ItemsChanged { get; set; }
}
}

View file

@ -8,6 +8,21 @@ namespace MediaBrowser.Controller.Collections
{ {
public interface ICollectionManager public interface ICollectionManager
{ {
/// <summary>
/// Occurs when [collection created].
/// </summary>
event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
/// <summary>
/// Occurs when [items added to collection].
/// </summary>
event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
/// <summary>
/// Occurs when [items removed from collection].
/// </summary>
event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
/// <summary> /// <summary>
/// Creates the collection. /// Creates the collection.
/// </summary> /// </summary>

View file

@ -50,6 +50,13 @@ namespace MediaBrowser.Controller.Dto
/// <returns>ChapterInfoDto.</returns> /// <returns>ChapterInfoDto.</returns>
ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item); ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item);
/// <summary>
/// Gets the user item data dto.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>UserItemDataDto.</returns>
UserItemDataDto GetUserItemDataDto(UserItemData data);
/// <summary> /// <summary>
/// Gets the item by name dto. /// Gets the item by name dto.
/// </summary> /// </summary>

View file

@ -95,6 +95,7 @@
<Compile Include="Chapters\IChapterProvider.cs" /> <Compile Include="Chapters\IChapterProvider.cs" />
<Compile Include="Chapters\ChapterResponse.cs" /> <Compile Include="Chapters\ChapterResponse.cs" />
<Compile Include="Collections\CollectionCreationOptions.cs" /> <Compile Include="Collections\CollectionCreationOptions.cs" />
<Compile Include="Collections\CollectionEvents.cs" />
<Compile Include="Collections\ICollectionManager.cs" /> <Compile Include="Collections\ICollectionManager.cs" />
<Compile Include="Dlna\ControlRequest.cs" /> <Compile Include="Dlna\ControlRequest.cs" />
<Compile Include="Dlna\ControlResponse.cs" /> <Compile Include="Dlna\ControlResponse.cs" />
@ -233,6 +234,9 @@
<Compile Include="Providers\IRemoteMetadataProvider.cs" /> <Compile Include="Providers\IRemoteMetadataProvider.cs" />
<Compile Include="Providers\VideoContentType.cs" /> <Compile Include="Providers\VideoContentType.cs" />
<Compile Include="RelatedMedia\IRelatedMediaProvider.cs" /> <Compile Include="RelatedMedia\IRelatedMediaProvider.cs" />
<Compile Include="Security\AuthenticationInfo.cs" />
<Compile Include="Security\AuthenticationInfoQuery.cs" />
<Compile Include="Security\IAuthenticationRepository.cs" />
<Compile Include="Security\IEncryptionManager.cs" /> <Compile Include="Security\IEncryptionManager.cs" />
<Compile Include="Subtitles\ISubtitleManager.cs" /> <Compile Include="Subtitles\ISubtitleManager.cs" />
<Compile Include="Subtitles\ISubtitleProvider.cs" /> <Compile Include="Subtitles\ISubtitleProvider.cs" />

View file

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Providers namespace MediaBrowser.Controller.Providers
{ {

View file

@ -0,0 +1,61 @@
using System;
namespace MediaBrowser.Controller.Security
{
public class AuthenticationInfo
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
/// <summary>
/// Gets or sets the access token.
/// </summary>
/// <value>The access token.</value>
public string AccessToken { get; set; }
/// <summary>
/// Gets or sets the device identifier.
/// </summary>
/// <value>The device identifier.</value>
public string DeviceId { get; set; }
/// <summary>
/// Gets or sets the name of the application.
/// </summary>
/// <value>The name of the application.</value>
public string AppName { get; set; }
/// <summary>
/// Gets or sets the name of the device.
/// </summary>
/// <value>The name of the device.</value>
public string DeviceName { get; set; }
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is active.
/// </summary>
/// <value><c>true</c> if this instance is active; otherwise, <c>false</c>.</value>
public bool IsActive { get; set; }
/// <summary>
/// Gets or sets the date created.
/// </summary>
/// <value>The date created.</value>
public DateTime DateCreated { get; set; }
/// <summary>
/// Gets or sets the date revoked.
/// </summary>
/// <value>The date revoked.</value>
public DateTime? DateRevoked { get; set; }
}
}

View file

@ -0,0 +1,42 @@

namespace MediaBrowser.Controller.Security
{
public class AuthenticationInfoQuery
{
/// <summary>
/// Gets or sets the device identifier.
/// </summary>
/// <value>The device identifier.</value>
public string DeviceId { get; set; }
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
/// <summary>
/// Gets or sets the access token.
/// </summary>
/// <value>The access token.</value>
public string AccessToken { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is active.
/// </summary>
/// <value><c>null</c> if [is active] contains no value, <c>true</c> if [is active]; otherwise, <c>false</c>.</value>
public bool? IsActive { get; set; }
/// <summary>
/// Gets or sets the start index.
/// </summary>
/// <value>The start index.</value>
public int? StartIndex { get; set; }
/// <summary>
/// Gets or sets the limit.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
}
}

View file

@ -0,0 +1,39 @@
using MediaBrowser.Model.Querying;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Security
{
public interface IAuthenticationRepository
{
/// <summary>
/// Creates the specified information.
/// </summary>
/// <param name="info">The information.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task Create(AuthenticationInfo info, CancellationToken cancellationToken);
/// <summary>
/// Updates the specified information.
/// </summary>
/// <param name="info">The information.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task Update(AuthenticationInfo info, CancellationToken cancellationToken);
/// <summary>
/// Gets the specified query.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>QueryResult{AuthenticationInfo}.</returns>
QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query);
/// <summary>
/// Gets the specified identifier.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>AuthenticationInfo.</returns>
AuthenticationInfo Get(string id);
}
}

View file

@ -259,7 +259,28 @@ namespace MediaBrowser.Controller.Session
/// <summary> /// <summary>
/// Validates the security token. /// Validates the security token.
/// </summary> /// </summary>
/// <param name="token">The token.</param> /// <param name="accessToken">The access token.</param>
void ValidateSecurityToken(string token); void ValidateSecurityToken(string accessToken);
/// <summary>
/// Logouts the specified access token.
/// </summary>
/// <param name="accessToken">The access token.</param>
/// <returns>Task.</returns>
Task Logout(string accessToken);
/// <summary>
/// Revokes the user tokens.
/// </summary>
/// <param name="userId">The user identifier.</param>
/// <returns>Task.</returns>
Task RevokeUserTokens(string userId);
/// <summary>
/// Determines whether the specified remote endpoint is local.
/// </summary>
/// <param name="remoteEndpoint">The remote endpoint.</param>
/// <returns><c>true</c> if the specified remote endpoint is local; otherwise, <c>false</c>.</returns>
bool IsLocal(string remoteEndpoint);
} }
} }

View file

@ -123,7 +123,7 @@ namespace MediaBrowser.Controller.Session
public List<string> SupportedCommands { get; set; } public List<string> SupportedCommands { get; set; }
public TranscodingInfo TranscodingInfo { get; set; } public TranscodingInfo TranscodingInfo { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is active. /// Gets a value indicating whether this instance is active.
/// </summary> /// </summary>

View file

@ -1,11 +1,11 @@
using System; using MediaBrowser.Common.IO;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.LocalMetadata namespace MediaBrowser.LocalMetadata
{ {

View file

@ -246,7 +246,7 @@ namespace MediaBrowser.Model.ApiClient
/// Gets the client session asynchronous. /// Gets the client session asynchronous.
/// </summary> /// </summary>
/// <returns>Task{SessionInfoDto}.</returns> /// <returns>Task{SessionInfoDto}.</returns>
Task<SessionInfoDto> GetCurrentSessionAsync(); Task<SessionInfoDto> GetCurrentSessionAsync(CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the item counts async. /// Gets the item counts async.
@ -644,6 +644,13 @@ namespace MediaBrowser.Model.ApiClient
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task SetVolume(string sessionId, int volume); Task SetVolume(string sessionId, int volume);
/// <summary>
/// Stops the transcoding processes.
/// </summary>
/// <param name="deviceId">The device identifier.</param>
/// <returns>Task.</returns>
Task StopTranscodingProcesses(string deviceId);
/// <summary> /// <summary>
/// Sets the index of the audio stream. /// Sets the index of the audio stream.
/// </summary> /// </summary>
@ -984,7 +991,7 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{LiveTvInfo}.</returns> /// <returns>Task{LiveTvInfo}.</returns>
Task<QueryResult<ChannelInfoDto>> GetLiveTvChannelsAsync(ChannelQuery query, CancellationToken cancellationToken); Task<QueryResult<ChannelInfoDto>> GetLiveTvChannelsAsync(LiveTvChannelQuery query, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the live tv channel asynchronous. /// Gets the live tv channel asynchronous.
@ -1187,5 +1194,13 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{QueryResult{BaseItemDto}}.</returns> /// <returns>Task{QueryResult{BaseItemDto}}.</returns>
Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken); Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken);
/// <summary>
/// Gets the latest channel items.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{QueryResult{BaseItemDto}}.</returns>
Task<QueryResult<BaseItemDto>> GetLatestChannelItems(AllChannelMediaQuery query, CancellationToken cancellationToken);
} }
} }

View file

@ -210,6 +210,8 @@ namespace MediaBrowser.Model.Configuration
public bool DefaultMetadataSettingsApplied { get; set; } public bool DefaultMetadataSettingsApplied { get; set; }
public bool EnableTokenAuthentication { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class. /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary> /// </summary>

View file

@ -21,6 +21,6 @@ namespace MediaBrowser.Model.Users
/// Gets or sets the authentication token. /// Gets or sets the authentication token.
/// </summary> /// </summary>
/// <value>The authentication token.</value> /// <value>The authentication token.</value>
public string AuthenticationToken { get; set; } public string AccessToken { get; set; }
} }
} }

View file

@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;

View file

@ -170,7 +170,7 @@ namespace MediaBrowser.Providers.Manager
var key = id.Key; var key = id.Key;
// Don't replace existing Id's. // Don't replace existing Id's.
if (!target.ProviderIds.ContainsKey(key)) if (replaceData || !target.ProviderIds.ContainsKey(key))
{ {
target.ProviderIds[key] = id.Value; target.ProviderIds[key] = id.Value;
} }

View file

@ -19,7 +19,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV namespace MediaBrowser.Providers.TV
{ {
public class MovieDbEpisodeImageProvider : IRemoteImageProvider, IHasOrder public class MovieDbEpisodeImageProvider/* : IRemoteImageProvider, IHasOrder*/
{ {
private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos"; private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;

View file

@ -1133,6 +1133,8 @@ namespace MediaBrowser.Server.Implementations.Channels
item.CommunityRating = info.CommunityRating; item.CommunityRating = info.CommunityRating;
item.OfficialRating = info.OfficialRating; item.OfficialRating = info.OfficialRating;
item.Overview = info.Overview; item.Overview = info.Overview;
item.IndexNumber = info.IndexNumber;
item.ParentIndexNumber = info.ParentIndexNumber;
item.People = info.People; item.People = info.People;
item.PremiereDate = info.PremiereDate; item.PremiereDate = info.PremiereDate;
item.ProductionYear = info.ProductionYear; item.ProductionYear = info.ProductionYear;
@ -1159,7 +1161,6 @@ namespace MediaBrowser.Server.Implementations.Channels
if (channelMediaItem != null) if (channelMediaItem != null)
{ {
channelMediaItem.IsInfiniteStream = info.IsInfiniteStream;
channelMediaItem.ContentType = info.ContentType; channelMediaItem.ContentType = info.ContentType;
channelMediaItem.ChannelMediaSources = info.MediaSources; channelMediaItem.ChannelMediaSources = info.MediaSources;

View file

@ -1,9 +1,11 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.Events;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
using MoreLinq; using MoreLinq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -19,12 +21,18 @@ namespace MediaBrowser.Server.Implementations.Collections
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _iLibraryMonitor; private readonly ILibraryMonitor _iLibraryMonitor;
private readonly ILogger _logger;
public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor) public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor, ILogger logger)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_iLibraryMonitor = iLibraryMonitor; _iLibraryMonitor = iLibraryMonitor;
_logger = logger;
} }
public Folder GetCollectionsFolder(string userId) public Folder GetCollectionsFolder(string userId)
@ -74,9 +82,16 @@ namespace MediaBrowser.Server.Implementations.Collections
if (options.ItemIdList.Count > 0) if (options.ItemIdList.Count > 0)
{ {
await AddToCollection(collection.Id, options.ItemIdList); await AddToCollection(collection.Id, options.ItemIdList, false);
} }
EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs
{
Collection = collection,
Options = options
}, _logger);
return collection; return collection;
} }
finally finally
@ -113,7 +128,12 @@ namespace MediaBrowser.Server.Implementations.Collections
return GetCollectionsFolder(string.Empty); return GetCollectionsFolder(string.Empty);
} }
public async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids) public Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
{
return AddToCollection(collectionId, ids, true);
}
private async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent)
{ {
var collection = _libraryManager.GetItemById(collectionId) as BoxSet; var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
@ -123,6 +143,7 @@ namespace MediaBrowser.Server.Implementations.Collections
} }
var list = new List<LinkedChild>(); var list = new List<LinkedChild>();
var itemList = new List<BaseItem>();
var currentLinkedChildren = collection.GetLinkedChildren().ToList(); var currentLinkedChildren = collection.GetLinkedChildren().ToList();
foreach (var itemId in ids) foreach (var itemId in ids)
@ -134,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Collections
throw new ArgumentException("No item exists with the supplied Id"); throw new ArgumentException("No item exists with the supplied Id");
} }
itemList.Add(item);
if (currentLinkedChildren.Any(i => i.Id == itemId)) if (currentLinkedChildren.Any(i => i.Id == itemId))
{ {
throw new ArgumentException("Item already exists in collection"); throw new ArgumentException("Item already exists in collection");
@ -165,6 +188,16 @@ namespace MediaBrowser.Server.Implementations.Collections
await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
if (fireEvent)
{
EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs
{
Collection = collection,
ItemsChanged = itemList
}, _logger);
}
} }
public async Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds) public async Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
@ -177,6 +210,7 @@ namespace MediaBrowser.Server.Implementations.Collections
} }
var list = new List<LinkedChild>(); var list = new List<LinkedChild>();
var itemList = new List<BaseItem>();
foreach (var itemId in itemIds) foreach (var itemId in itemIds)
{ {
@ -190,6 +224,12 @@ namespace MediaBrowser.Server.Implementations.Collections
list.Add(child); list.Add(child);
var childItem = _libraryManager.GetItemById(itemId); var childItem = _libraryManager.GetItemById(itemId);
if (childItem != null)
{
itemList.Add(childItem);
}
var supportsGrouping = childItem as ISupportsBoxSetGrouping; var supportsGrouping = childItem as ISupportsBoxSetGrouping;
if (supportsGrouping != null) if (supportsGrouping != null)
@ -221,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Collections
{ {
File.Delete(file); File.Delete(file);
} }
foreach (var child in list) foreach (var child in list)
{ {
collection.LinkedChildren.Remove(child); collection.LinkedChildren.Remove(child);
@ -238,6 +278,13 @@ namespace MediaBrowser.Server.Implementations.Collections
await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs
{
Collection = collection,
ItemsChanged = itemList
}, _logger);
} }
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user) public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)

View file

@ -1,4 +1,4 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
@ -13,9 +13,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{ {
public class AuthService : IAuthService public class AuthService : IAuthService
{ {
public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext) private readonly IServerConfigurationManager _config;
public AuthService(IUserManager userManager, ISessionManager sessionManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config)
{ {
AuthorizationContext = authorizationContext; AuthorizationContext = authorizationContext;
_config = config;
SessionManager = sessionManager; SessionManager = sessionManager;
UserManager = userManager; UserManager = userManager;
} }
@ -54,29 +57,31 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
//This code is executed before the service //This code is executed before the service
var auth = AuthorizationContext.GetAuthorizationInfo(req); var auth = AuthorizationContext.GetAuthorizationInfo(req);
if (string.IsNullOrWhiteSpace(auth.Token)) if (!string.IsNullOrWhiteSpace(auth.Token) || _config.Configuration.EnableTokenAuthentication)
{
// Legacy
// TODO: Deprecate this in Oct 2014
User user = null;
if (!string.IsNullOrWhiteSpace(auth.UserId))
{
var userId = auth.UserId;
user = UserManager.GetUserById(new Guid(userId));
}
if (user == null || user.Configuration.IsDisabled)
{
throw new UnauthorizedAccessException("Unauthorized access.");
}
}
else
{ {
SessionManager.ValidateSecurityToken(auth.Token); SessionManager.ValidateSecurityToken(auth.Token);
} }
var user = string.IsNullOrWhiteSpace(auth.UserId)
? null
: UserManager.GetUserById(new Guid(auth.UserId));
if (user != null && user.Configuration.IsDisabled)
{
throw new UnauthorizedAccessException("User account has been disabled.");
}
if (!string.IsNullOrWhiteSpace(auth.DeviceId) &&
!string.IsNullOrWhiteSpace(auth.Client) &&
!string.IsNullOrWhiteSpace(auth.Device))
{
SessionManager.LogSessionActivity(auth.Client,
auth.Version,
auth.DeviceId,
auth.Device,
req.RemoteIp,
user);
}
} }
private void ExecuteBasic(IRequest req, IResponse res, object requestDto) private void ExecuteBasic(IRequest req, IResponse res, object requestDto)
@ -108,11 +113,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
} }
} }
private void LogRequest()
{
}
protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false) protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false)
{ {
var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect; var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect;

View file

@ -216,6 +216,7 @@
<Compile Include="ScheduledTasks\ChapterImagesTask.cs" /> <Compile Include="ScheduledTasks\ChapterImagesTask.cs" />
<Compile Include="ScheduledTasks\RefreshIntrosTask.cs" /> <Compile Include="ScheduledTasks\RefreshIntrosTask.cs" />
<Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" /> <Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
<Compile Include="Security\AuthenticationRepository.cs" />
<Compile Include="Security\EncryptionManager.cs" /> <Compile Include="Security\EncryptionManager.cs" />
<Compile Include="ServerApplicationPaths.cs" /> <Compile Include="ServerApplicationPaths.cs" />
<Compile Include="ServerManager\ServerManager.cs" /> <Compile Include="ServerManager\ServerManager.cs" />

View file

@ -14,7 +14,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Persistence namespace MediaBrowser.Server.Implementations.Persistence
{ {
public class SqliteFileOrganizationRepository : IFileOrganizationRepository public class SqliteFileOrganizationRepository : IFileOrganizationRepository, IDisposable
{ {
private IDbConnection _connection; private IDbConnection _connection;

View file

@ -0,0 +1,338 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Server.Implementations.Persistence;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Security
{
public class AuthenticationRepository : IAuthenticationRepository
{
private IDbConnection _connection;
private readonly ILogger _logger;
private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
private readonly IServerApplicationPaths _appPaths;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private IDbCommand _saveInfoCommand;
public AuthenticationRepository(ILogger logger, IServerApplicationPaths appPaths)
{
_logger = logger;
_appPaths = appPaths;
}
public async Task Initialize()
{
var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db");
_connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
string[] queries = {
"create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)",
"create index if not exists idx_AccessTokens on AccessTokens(Id)",
//pragmas
"pragma temp_store = memory",
"pragma shrink_memory"
};
_connection.RunQueries(queries, _logger);
PrepareStatements();
}
private void PrepareStatements()
{
_saveInfoCommand = _connection.CreateCommand();
_saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)";
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@Id");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AccessToken");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceId");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppName");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceName");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@UserId");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@IsActive");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateCreated");
_saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateRevoked");
}
public Task Create(AuthenticationInfo info, CancellationToken cancellationToken)
{
info.Id = Guid.NewGuid().ToString("N");
return Update(info, cancellationToken);
}
public async Task Update(AuthenticationInfo info, CancellationToken cancellationToken)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
cancellationToken.ThrowIfCancellationRequested();
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
IDbTransaction transaction = null;
try
{
transaction = _connection.BeginTransaction();
var index = 0;
_saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id);
_saveInfoCommand.GetParameter(index++).Value = info.AccessToken;
_saveInfoCommand.GetParameter(index++).Value = info.DeviceId;
_saveInfoCommand.GetParameter(index++).Value = info.AppName;
_saveInfoCommand.GetParameter(index++).Value = info.DeviceName;
_saveInfoCommand.GetParameter(index++).Value = info.UserId;
_saveInfoCommand.GetParameter(index++).Value = info.IsActive;
_saveInfoCommand.GetParameter(index++).Value = info.DateCreated;
_saveInfoCommand.GetParameter(index++).Value = info.DateRevoked;
_saveInfoCommand.Transaction = transaction;
_saveInfoCommand.ExecuteNonQuery();
transaction.Commit();
}
catch (OperationCanceledException)
{
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
catch (Exception e)
{
_logger.ErrorException("Failed to save record:", e);
if (transaction != null)
{
transaction.Rollback();
}
throw;
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
_writeLock.Release();
}
}
private const string BaseSelectText = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens";
public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = BaseSelectText;
var whereClauses = new List<string>();
var startIndex = query.StartIndex ?? 0;
if (startIndex > 0)
{
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens ORDER BY DateCreated LIMIT {0})",
startIndex.ToString(_usCulture)));
}
if (!string.IsNullOrWhiteSpace(query.AccessToken))
{
whereClauses.Add("AccessToken=@AccessToken");
cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken;
}
if (!string.IsNullOrWhiteSpace(query.UserId))
{
whereClauses.Add("UserId=@UserId");
cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
}
if (!string.IsNullOrWhiteSpace(query.DeviceId))
{
whereClauses.Add("DeviceId=@DeviceId");
cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId;
}
if (query.IsActive.HasValue)
{
whereClauses.Add("IsActive=@IsActive");
cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value;
}
if (whereClauses.Count > 0)
{
cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
}
cmd.CommandText += " ORDER BY DateCreated";
if (query.Limit.HasValue)
{
cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
}
cmd.CommandText += "; select count (Id) from AccessTokens";
var list = new List<AuthenticationInfo>();
var count = 0;
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
while (reader.Read())
{
list.Add(Get(reader));
}
if (reader.NextResult() && reader.Read())
{
count = reader.GetInt32(0);
}
}
return new QueryResult<AuthenticationInfo>()
{
Items = list.ToArray(),
TotalRecordCount = count
};
}
}
public AuthenticationInfo Get(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException("id");
}
var guid = new Guid(id);
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = BaseSelectText + " where Id=@Id";
cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
if (reader.Read())
{
return Get(reader);
}
}
}
return null;
}
private AuthenticationInfo Get(IDataReader reader)
{
var s = "select Id, AccessToken, DeviceId, AppName, DeviceName, UserId, IsActive, DateCreated, DateRevoked from AccessTokens";
var info = new AuthenticationInfo
{
Id = reader.GetGuid(0).ToString("N"),
AccessToken = reader.GetString(1)
};
if (!reader.IsDBNull(2))
{
info.DeviceId = reader.GetString(2);
}
if (!reader.IsDBNull(3))
{
info.AppName = reader.GetString(3);
}
if (!reader.IsDBNull(4))
{
info.DeviceName = reader.GetString(4);
}
if (!reader.IsDBNull(5))
{
info.UserId = reader.GetString(5);
}
info.IsActive = reader.GetBoolean(6);
info.DateCreated = reader.GetDateTime(7);
if (!reader.IsDBNull(8))
{
info.DateRevoked = reader.GetDateTime(8);
}
return info;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private readonly object _disposeLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
try
{
lock (_disposeLock)
{
if (_connection != null)
{
if (_connection.IsOpen())
{
_connection.Close();
}
_connection.Dispose();
_connection = null;
}
}
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing database", ex);
}
}
}
}
}

View file

@ -1,6 +1,4 @@
using System.Security.Cryptography; using MediaBrowser.Common.Events;
using System.Text;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
@ -13,12 +11,14 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library; using MediaBrowser.Model.Library;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.Users;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -27,7 +27,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Server.Implementations.Session namespace MediaBrowser.Server.Implementations.Session
{ {
@ -62,6 +61,8 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IAuthenticationRepository _authRepo;
/// <summary> /// <summary>
/// Gets or sets the configuration manager. /// Gets or sets the configuration manager.
/// </summary> /// </summary>
@ -104,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="userRepository">The user repository.</param> /// <param name="userRepository">The user repository.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient) public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IItemRepository itemRepo, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo)
{ {
_userDataRepository = userDataRepository; _userDataRepository = userDataRepository;
_configurationManager = configurationManager; _configurationManager = configurationManager;
@ -119,6 +120,7 @@ namespace MediaBrowser.Server.Implementations.Session
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_appHost = appHost; _appHost = appHost;
_httpClient = httpClient; _httpClient = httpClient;
_authRepo = authRepo;
} }
/// <summary> /// <summary>
@ -204,7 +206,12 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">user</exception> /// <exception cref="System.ArgumentNullException">user</exception>
/// <exception cref="System.UnauthorizedAccessException"></exception> /// <exception cref="System.UnauthorizedAccessException"></exception>
public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) public async Task<SessionInfo> LogSessionActivity(string clientType,
string appVersion,
string deviceId,
string deviceName,
string remoteEndPoint,
User user)
{ {
if (string.IsNullOrEmpty(clientType)) if (string.IsNullOrEmpty(clientType))
{ {
@ -1157,7 +1164,37 @@ namespace MediaBrowser.Server.Implementations.Session
public void ValidateSecurityToken(string token) public void ValidateSecurityToken(string token)
{ {
if (string.IsNullOrWhiteSpace(token))
{
throw new UnauthorizedAccessException();
}
var result = _authRepo.Get(new AuthenticationInfoQuery
{
AccessToken = token
});
var info = result.Items.FirstOrDefault();
if (info == null)
{
throw new UnauthorizedAccessException();
}
if (!info.IsActive)
{
throw new UnauthorizedAccessException("Access token has expired.");
}
if (!string.IsNullOrWhiteSpace(info.UserId))
{
var user = _userManager.GetUserById(new Guid(info.UserId));
if (user == null || user.Configuration.IsDisabled)
{
throw new UnauthorizedAccessException("User account has been disabled.");
}
}
} }
/// <summary> /// <summary>
@ -1175,7 +1212,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <exception cref="UnauthorizedAccessException"></exception> /// <exception cref="UnauthorizedAccessException"></exception>
public async Task<AuthenticationResult> AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint) public async Task<AuthenticationResult> AuthenticateNewSession(string username, string password, string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint)
{ {
var result = await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); var result = IsLocalhost(remoteEndPoint) || await _userManager.AuthenticateUser(username, password).ConfigureAwait(false);
if (!result) if (!result)
{ {
@ -1185,6 +1222,8 @@ namespace MediaBrowser.Server.Implementations.Session
var user = _userManager.Users var user = _userManager.Users
.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false);
var session = await LogSessionActivity(clientType, var session = await LogSessionActivity(clientType,
appVersion, appVersion,
deviceId, deviceId,
@ -1197,11 +1236,108 @@ namespace MediaBrowser.Server.Implementations.Session
{ {
User = _dtoService.GetUserDto(user), User = _dtoService.GetUserDto(user),
SessionInfo = GetSessionInfoDto(session), SessionInfo = GetSessionInfoDto(session),
AuthenticationToken = Guid.NewGuid().ToString("N") AccessToken = token
}; };
} }
private bool IsLocal(string remoteEndpoint) private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string deviceName)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
{
DeviceId = deviceId,
IsActive = true,
UserId = userId,
Limit = 1
});
if (existing.Items.Length > 0)
{
_logger.Debug("Reissuing access token");
return existing.Items[0].AccessToken;
}
var newToken = new AuthenticationInfo
{
AppName = app,
DateCreated = DateTime.UtcNow,
DeviceId = deviceId,
DeviceName = deviceName,
UserId = userId,
IsActive = true,
AccessToken = Guid.NewGuid().ToString("N")
};
_logger.Debug("Creating new access token for user {0}", userId);
await _authRepo.Create(newToken, CancellationToken.None).ConfigureAwait(false);
return newToken.AccessToken;
}
public async Task Logout(string accessToken)
{
if (string.IsNullOrWhiteSpace(accessToken))
{
throw new ArgumentNullException("accessToken");
}
var existing = _authRepo.Get(new AuthenticationInfoQuery
{
Limit = 1,
AccessToken = accessToken
}).Items.FirstOrDefault();
if (existing != null)
{
existing.IsActive = false;
await _authRepo.Update(existing, CancellationToken.None).ConfigureAwait(false);
var sessions = Sessions
.Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var session in sessions)
{
try
{
ReportSessionEnded(session.Id);
}
catch (Exception ex)
{
_logger.ErrorException("Error reporting session ended", ex);
}
}
}
}
public async Task RevokeUserTokens(string userId)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
{
IsActive = true,
UserId = userId
});
foreach (var info in existing.Items)
{
await Logout(info.AccessToken).ConfigureAwait(false);
}
}
private bool IsLocalhost(string remoteEndpoint)
{
if (string.IsNullOrWhiteSpace(remoteEndpoint))
{
throw new ArgumentNullException("remoteEndpoint");
}
return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 ||
remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase);
}
public bool IsLocal(string remoteEndpoint)
{ {
if (string.IsNullOrWhiteSpace(remoteEndpoint)) if (string.IsNullOrWhiteSpace(remoteEndpoint))
{ {
@ -1211,12 +1347,11 @@ namespace MediaBrowser.Server.Implementations.Session
// Private address space: // Private address space:
// http://en.wikipedia.org/wiki/Private_network // http://en.wikipedia.org/wiki/Private_network
return remoteEndpoint.IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1 || return IsLocalhost(remoteEndpoint) ||
remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("192.", StringComparison.OrdinalIgnoreCase) ||
remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase) ||
remoteEndpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || remoteEndpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase);
remoteEndpoint.StartsWith("::", StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>

View file

@ -210,6 +210,8 @@ namespace MediaBrowser.ServerApplication
private IUserViewManager UserViewManager { get; set; } private IUserViewManager UserViewManager { get; set; }
private IAuthenticationRepository AuthenticationRepository { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost"/> class. /// Initializes a new instance of the <see cref="ApplicationHost"/> class.
/// </summary> /// </summary>
@ -586,6 +588,9 @@ namespace MediaBrowser.ServerApplication
FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false); FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false);
RegisterSingleInstance(FileOrganizationRepository); RegisterSingleInstance(FileOrganizationRepository);
AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false);
RegisterSingleInstance(AuthenticationRepository);
UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer); UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer);
RegisterSingleInstance(UserManager); RegisterSingleInstance(UserManager);
@ -625,7 +630,7 @@ namespace MediaBrowser.ServerApplication
DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager); DtoService = new DtoService(Logger, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager);
RegisterSingleInstance(DtoService); RegisterSingleInstance(DtoService);
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient); SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, ItemRepository, JsonSerializer, this, HttpClient, AuthenticationRepository);
RegisterSingleInstance(SessionManager); RegisterSingleInstance(SessionManager);
var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
@ -651,7 +656,7 @@ namespace MediaBrowser.ServerApplication
var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient); var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient);
RegisterSingleInstance<IConnectionManager>(connectionManager); RegisterSingleInstance<IConnectionManager>(connectionManager);
var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"));
RegisterSingleInstance<ICollectionManager>(collectionManager); RegisterSingleInstance<ICollectionManager>(collectionManager);
LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager); LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager);
@ -678,7 +683,7 @@ namespace MediaBrowser.ServerApplication
var authContext = new AuthorizationContext(); var authContext = new AuthorizationContext();
RegisterSingleInstance<IAuthorizationContext>(authContext); RegisterSingleInstance<IAuthorizationContext>(authContext);
RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
RegisterSingleInstance<IAuthService>(new AuthService(UserManager, SessionManager, authContext)); RegisterSingleInstance<IAuthService>(new AuthService(UserManager, SessionManager, authContext, ServerConfigurationManager));
RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder)); RegisterSingleInstance<ISubtitleEncoder>(new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder));
@ -755,6 +760,15 @@ namespace MediaBrowser.ServerApplication
return repo; return repo;
} }
private async Task<IAuthenticationRepository> GetAuthenticationRepository()
{
var repo = new AuthenticationRepository(LogManager.GetLogger("AuthenticationRepository"), ServerConfigurationManager.ApplicationPaths);
await repo.Initialize().ConfigureAwait(false);
return repo;
}
/// <summary> /// <summary>
/// Configures the repositories. /// Configures the repositories.
/// </summary> /// </summary>

View file

@ -77,7 +77,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken) private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken)
{ {
using (var streamReader = new StreamReader(metadataFile, encoding)) using (var streamReader = new StreamReader(metadataFile))
{ {
// Use XmlReader for best performance // Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings)) using (var reader = XmlReader.Create(streamReader, settings))

View file

@ -27,7 +27,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
var path = file.FullName; var path = file.FullName;
await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
try try
{ {
@ -44,10 +44,10 @@ namespace MediaBrowser.XbmcMetadata.Providers
{ {
result.HasMetadata = false; result.HasMetadata = false;
} }
finally //finally
{ //{
XmlProviderUtils.XmlParsingResourcePool.Release(); // XmlProviderUtils.XmlParsingResourcePool.Release();
} //}
return result; return result;
} }

View file

@ -1,15 +1,14 @@
using System.Collections.Generic; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Security; using System.Security;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.XbmcMetadata.Savers namespace MediaBrowser.XbmcMetadata.Savers
{ {

View file

@ -1,12 +1,4 @@
using System; using MediaBrowser.Common.Extensions;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Xml;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -18,6 +10,14 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.XbmcMetadata.Configuration; using MediaBrowser.XbmcMetadata.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Xml;
namespace MediaBrowser.XbmcMetadata.Savers namespace MediaBrowser.XbmcMetadata.Savers
{ {
@ -392,9 +392,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
builder.Append("<writer>" + SecurityElement.Escape(person) + "</writer>"); builder.Append("<writer>" + SecurityElement.Escape(person) + "</writer>");
} }
if (writers.Count > 0) foreach (var person in writers)
{ {
builder.Append("<credits>" + SecurityElement.Escape(string.Join(" / ", writers.ToArray())) + "</credits>"); builder.Append("<credits>" + SecurityElement.Escape(person) + "</credits>");
} }
var hasTrailer = item as IHasTrailers; var hasTrailer = item as IHasTrailers;

View file

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common.Internal</id> <id>MediaBrowser.Common.Internal</id>
<version>3.0.412</version> <version>3.0.414</version>
<title>MediaBrowser.Common.Internal</title> <title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors> <authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright> <copyright>Copyright © Media Browser 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.412" /> <dependency id="MediaBrowser.Common" version="3.0.414" />
<dependency id="NLog" version="2.1.0" /> <dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.5.0" /> <dependency id="SimpleInjector" version="2.5.0" />
<dependency id="sharpcompress" version="0.10.2" /> <dependency id="sharpcompress" version="0.10.2" />

View file

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common</id> <id>MediaBrowser.Common</id>
<version>3.0.412</version> <version>3.0.414</version>
<title>MediaBrowser.Common</title> <title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors> <authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>

View file

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Server.Core</id> <id>MediaBrowser.Server.Core</id>
<version>3.0.412</version> <version>3.0.414</version>
<title>Media Browser.Server.Core</title> <title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors> <authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description> <description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright> <copyright>Copyright © Media Browser 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.412" /> <dependency id="MediaBrowser.Common" version="3.0.414" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>