diff options
59 files changed, 1536 insertions, 300 deletions
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 2cc046f1d..3736814e5 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -230,7 +230,7 @@ namespace MediaBrowser.Api SortOrder = request.SortOrder, SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), Filters = request.GetFilters().ToArray(), - Fields = request.GetItemFields().ToList() + Fields = request.GetItemFields().ToArray() }, CancellationToken.None).Result; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index ea7eaa947..43e9ad3ef 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -40,6 +40,7 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "GET")] + [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "HEAD")] [Api(Description = "Gets an item image")] public class GetItemImage : ImageRequest { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 9a7d28ec4..df689cb24 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -118,10 +118,11 @@ <Compile Include="ScheduledTasks\ScheduledTasksWebSocketListener.cs" /> <Compile Include="ApiEntryPoint.cs" /> <Compile Include="SearchService.cs" /> - <Compile Include="SessionsService.cs" /> + <Compile Include="Session\SessionsService.cs" /> <Compile Include="SimilarItemsHelper.cs" /> <Compile Include="Sync\SyncService.cs" /> - <Compile Include="SystemService.cs" /> + <Compile Include="System\ActivityLogService.cs" /> + <Compile Include="System\SystemService.cs" /> <Compile Include="Movies\TrailersService.cs" /> <Compile Include="TvShowsService.cs" /> <Compile Include="UserLibrary\ArtistsService.cs" /> @@ -139,8 +140,8 @@ <Compile Include="UserService.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="VideosService.cs" /> - <Compile Include="WebSocket\SessionInfoWebSocketListener.cs" /> - <Compile Include="WebSocket\SystemInfoWebSocketListener.cs" /> + <Compile Include="Session\SessionInfoWebSocketListener.cs" /> + <Compile Include="System\SystemInfoWebSocketListener.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> @@ -173,4 +174,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index 600d9e405..e6b525e53 100644 --- a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace MediaBrowser.Api.WebSocket +namespace MediaBrowser.Api.Session { /// <summary> /// Class SessionInfoWebSocketListener diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 8017f3523..e2c95eba9 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Session { /// <summary> /// Class GetSessions diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs new file mode 100644 index 000000000..0ccc28c6f --- /dev/null +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using ServiceStack; + +namespace MediaBrowser.Api.System +{ + [Route("/System/ActivityLog/Entries", "GET", Summary = "Gets activity log entries")] + public class GetActivityLogs : IReturn<QueryResult<ActivityLogEntry>> + { + /// <summary> + /// Skips over a given number of items within the results. Use for paging. + /// </summary> + /// <value>The start index.</value> + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// <summary> + /// The maximum number of items to return + /// </summary> + /// <value>The limit.</value> + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + [Authenticated] + public class ActivityLogService : BaseApiService + { + private readonly IActivityManager _activityManager; + + public ActivityLogService(IActivityManager activityManager) + { + _activityManager = activityManager; + } + + public object Get(GetActivityLogs request) + { + var result = _activityManager.GetActivityLogEntries(request.StartIndex, request.Limit); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs index 2940bcef0..c20cef3b3 100644 --- a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs +++ b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; using System.Threading.Tasks; -namespace MediaBrowser.Api.WebSocket +namespace MediaBrowser.Api.System { /// <summary> /// Class SystemInfoWebSocketListener diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index 259b1d892..3913275ee 100644 --- a/MediaBrowser.Api/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -4,12 +4,13 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Net; using MediaBrowser.Model.System; using ServiceStack; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.System { /// <summary> /// Class GetSystemInfo @@ -73,7 +74,7 @@ namespace MediaBrowser.Api /// <param name="appHost">The app host.</param> /// <param name="appPaths">The application paths.</param> /// <param name="fileSystem">The file system.</param> - /// <exception cref="System.ArgumentNullException">jsonSerializer</exception> + /// <exception cref="ArgumentNullException">jsonSerializer</exception> public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) { _appHost = appHost; @@ -89,7 +90,7 @@ namespace MediaBrowser.Api { files = new DirectoryInfo(_appPaths.LogDirectoryPath) .EnumerateFiles("*", SearchOption.AllDirectories) - .Where(i => string.Equals(i.Extension, ".txt", System.StringComparison.OrdinalIgnoreCase)) + .Where(i => string.Equals(i.Extension, ".txt", global::System.StringComparison.OrdinalIgnoreCase)) .ToList(); } catch (DirectoryNotFoundException) @@ -116,7 +117,7 @@ namespace MediaBrowser.Api { var file = new DirectoryInfo(_appPaths.LogDirectoryPath) .EnumerateFiles("*", SearchOption.AllDirectories) - .First(i => string.Equals(i.Name, request.Name, System.StringComparison.OrdinalIgnoreCase)); + .First(i => string.Equals(i.Name, request.Name, global::System.StringComparison.OrdinalIgnoreCase)); return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index f5a1f54cb..4ffe5b391 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -195,7 +195,7 @@ namespace MediaBrowser.Api var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); - if ((Request.IsLocal && isDashboard) || + if ((Request.IsLocal && isDashboard) || !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers @@ -327,7 +327,7 @@ namespace MediaBrowser.Api var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); Task.WaitAll(revokeTask); - + var task = _userManager.DeleteUser(user); Task.WaitAll(task); @@ -374,8 +374,17 @@ namespace MediaBrowser.Api auth.DeviceId = "Unknown device id"; } - var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp, Request.IsLocal).Result; + var result = _sessionMananger.AuthenticateNewSession(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device, + Password = request.Password, + RemoteEndPoint = Request.RemoteIp, + Username = request.Username + + }, Request.IsLocal).Result; return ToOptimizedResult(result); } @@ -457,8 +466,8 @@ namespace MediaBrowser.Api Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? - _userManager.UpdateUser(user) : + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs index 60abc14f1..cb6121c9f 100644 --- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs +++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs @@ -29,6 +29,11 @@ namespace MediaBrowser.Common.Implementations.Configuration public event EventHandler<EventArgs> ConfigurationUpdated; /// <summary> + /// Occurs when [configuration updating]. + /// </summary> + public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating; + + /// <summary> /// Occurs when [named configuration updated]. /// </summary> public event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdated; @@ -217,6 +222,13 @@ namespace MediaBrowser.Common.Implementations.Configuration throw new ArgumentException("Expected configuration type is " + configurationType.Name); } + EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs + { + Key = key, + NewConfiguration = configuration + + }, Logger); + _configurations.AddOrUpdate(key, configuration, (k, v) => configuration); var path = GetConfigurationFile(key); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 68222d843..0dc67f8c0 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -547,6 +547,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks if (ex != null) { result.ErrorMessage = ex.Message; + result.LongErrorMessage = ex.StackTrace; } var path = GetHistoryFilePath(); diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs index 25698d972..d826a3ee7 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -7,6 +7,11 @@ namespace MediaBrowser.Common.Configuration public interface IConfigurationManager { /// <summary> + /// Occurs when [configuration updating]. + /// </summary> + event EventHandler<ConfigurationUpdateEventArgs> NamedConfigurationUpdating; + + /// <summary> /// Occurs when [configuration updated]. /// </summary> event EventHandler<EventArgs> ConfigurationUpdated; diff --git a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs index fc6963070..6989dea06 100644 --- a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs @@ -13,4 +13,9 @@ /// <value><c>true</c> if this instance is enabled; otherwise, <c>false</c>.</value> bool IsEnabled { get; } } + + public interface IScheduledTaskActivityLog + { + bool IsActivityLogged { get; } + } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Activity/IActivityManager.cs b/MediaBrowser.Controller/Activity/IActivityManager.cs new file mode 100644 index 000000000..0c565ae36 --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityManager.cs @@ -0,0 +1,17 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Querying; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityManager + { + event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; + + Task Create(ActivityLogEntry entry); + + QueryResult<ActivityLogEntry> GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Activity/IActivityRepository.cs b/MediaBrowser.Controller/Activity/IActivityRepository.cs new file mode 100644 index 000000000..29e60ff1f --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityRepository.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityRepository + { + Task Create(ActivityLogEntry entry); + + QueryResult<ActivityLogEntry> GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index aac8cda2e..13c9f8d84 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,7 +1,5 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Events; -using System; namespace MediaBrowser.Controller.Configuration { @@ -11,11 +9,6 @@ namespace MediaBrowser.Controller.Configuration public interface IServerConfigurationManager : IConfigurationManager { /// <summary> - /// Occurs when [configuration updating]. - /// </summary> - event EventHandler<GenericEventArgs<ServerConfiguration>> ConfigurationUpdating; - - /// <summary> /// Gets the application paths. /// </summary> /// <value>The application paths.</value> diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 5e6bd9707..19c960167 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -102,17 +102,9 @@ namespace MediaBrowser.Controller.Entities.Movies var totalItems = items.Count; var percentages = new Dictionary<Guid, double>(totalItems); - var tasks = new List<Task>(); - // Refresh songs foreach (var item in items) { - if (tasks.Count >= 3) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - } - cancellationToken.ThrowIfCancellationRequested(); var innerProgress = new ActionableProgress<double>(); @@ -132,13 +124,9 @@ namespace MediaBrowser.Controller.Entities.Movies }); // Avoid implicitly captured closure - var taskChild = item; - tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken)); + await RefreshItem(item, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false); } - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - // Refresh current item await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 0da5f9272..c6bbf02ae 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -31,6 +31,7 @@ namespace MediaBrowser.Controller.Library event EventHandler<GenericEventArgs<User>> UserCreated; event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated; + event EventHandler<GenericEventArgs<User>> UserPasswordChanged; /// <summary> /// Updates the configuration. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index d8d836597..34486182b 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -269,7 +269,7 @@ namespace MediaBrowser.Controller.Library if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { - logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); + //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); continue; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5243e1a2a..28e1ffb1c 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -68,6 +68,8 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="Activity\IActivityManager.cs" /> + <Compile Include="Activity\IActivityRepository.cs" /> <Compile Include="Channels\ChannelFolderItem.cs" /> <Compile Include="Channels\ChannelItemInfo.cs" /> <Compile Include="Channels\ChannelItemResult.cs" /> @@ -241,6 +243,7 @@ <Compile Include="Security\AuthenticationInfoQuery.cs" /> <Compile Include="Security\IAuthenticationRepository.cs" /> <Compile Include="Security\IEncryptionManager.cs" /> + <Compile Include="Session\AuthenticationRequest.cs" /> <Compile Include="Subtitles\ISubtitleManager.cs" /> <Compile Include="Subtitles\ISubtitleProvider.cs" /> <Compile Include="Providers\ItemIdentifier.cs" /> @@ -320,6 +323,7 @@ <Compile Include="Sorting\IUserBaseItemComparer.cs" /> <Compile Include="Providers\BaseItemXmlParser.cs" /> <Compile Include="Sorting\SortExtensions.cs" /> + <Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" /> <Compile Include="Subtitles\SubtitleResponse.cs" /> <Compile Include="Subtitles\SubtitleSearchRequest.cs" /> <Compile Include="Sync\ICloudSyncProvider.cs" /> @@ -360,4 +364,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs index 87b89e79c..254e56e05 100644 --- a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs +++ b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs @@ -16,10 +16,6 @@ namespace MediaBrowser.Controller.Notifications /// </summary> event EventHandler<NotificationUpdateEventArgs> NotificationAdded; /// <summary> - /// Occurs when [notification updated]. - /// </summary> - event EventHandler<NotificationUpdateEventArgs> NotificationUpdated; - /// <summary> /// Occurs when [notifications marked read]. /// </summary> event EventHandler<NotificationReadEventArgs> NotificationsMarkedRead; @@ -38,14 +34,6 @@ namespace MediaBrowser.Controller.Notifications NotificationResult GetNotifications(NotificationQuery query); /// <summary> - /// Gets the notification. - /// </summary> - /// <param name="id">The id.</param> - /// <param name="userId">The user id.</param> - /// <returns>Notification.</returns> - Notification GetNotification(string id, string userId); - - /// <summary> /// Adds the notification. /// </summary> /// <param name="notification">The notification.</param> diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs new file mode 100644 index 000000000..38871e814 --- /dev/null +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -0,0 +1,14 @@ + +namespace MediaBrowser.Controller.Session +{ + public class AuthenticationRequest + { + public string Username { get; set; } + public string Password { get; set; } + public string App { get; set; } + public string AppVersion { get; set; } + public string DeviceId { get; set; } + public string DeviceName { get; set; } + public string RemoteEndPoint { get; set; } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index e37a13923..f715ce770 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; using System; @@ -46,6 +47,16 @@ namespace MediaBrowser.Controller.Session /// Occurs when [capabilities changed]. /// </summary> event EventHandler<SessionEventArgs> CapabilitiesChanged; + + /// <summary> + /// Occurs when [authentication failed]. + /// </summary> + event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed; + + /// <summary> + /// Occurs when [authentication succeeded]. + /// </summary> + event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationSucceeded; /// <summary> /// Gets the sessions. @@ -211,23 +222,10 @@ namespace MediaBrowser.Controller.Session /// <summary> /// Authenticates the new session. /// </summary> - /// <param name="username">The username.</param> - /// <param name="password">The password.</param> - /// <param name="clientType">Type of the client.</param> - /// <param name="appVersion">The application version.</param> - /// <param name="deviceId">The device identifier.</param> - /// <param name="deviceName">Name of the device.</param> - /// <param name="remoteEndPoint">The remote end point.</param> + /// <param name="request">The request.</param> /// <param name="isLocal">if set to <c>true</c> [is local].</param> /// <returns>Task{SessionInfo}.</returns> - Task<AuthenticationResult> AuthenticateNewSession(string username, - string password, - string clientType, - string appVersion, - string deviceId, - string deviceName, - string remoteEndPoint, - bool isLocal); + Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request, bool isLocal); /// <summary> /// Reports the capabilities. diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 1d66d1505..0c814c0d4 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Providers; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -9,6 +10,16 @@ namespace MediaBrowser.Controller.Subtitles public interface ISubtitleManager { /// <summary> + /// Occurs when [subtitle download failure]. + /// </summary> + event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure; + + /// <summary> + /// Occurs when [subtitles downloaded]. + /// </summary> + event EventHandler<SubtitleDownloadEventArgs> SubtitlesDownloaded; + + /// <summary> /// Adds the parts. /// </summary> /// <param name="subtitleProviders">The subtitle providers.</param> @@ -31,7 +42,7 @@ namespace MediaBrowser.Controller.Subtitles /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns> - Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, + Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken); /// <summary> @@ -41,8 +52,8 @@ namespace MediaBrowser.Controller.Subtitles /// <param name="subtitleId">The subtitle identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task DownloadSubtitles(Video video, - string subtitleId, + Task DownloadSubtitles(Video video, + string subtitleId, CancellationToken cancellationToken); /// <summary> diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs new file mode 100644 index 000000000..1d204f2cb --- /dev/null +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Subtitles +{ + public class SubtitleDownloadEventArgs + { + public BaseItem Item { get; set; } + + public string Format { get; set; } + + public string Language { get; set; } + + public bool IsForced { get; set; } + + public string Provider { get; set; } + } + + public class SubtitleDownloadFailureEventArgs + { + public BaseItem Item { get; set; } + + public string Provider { get; set; } + + public Exception Exception { get; set; } + } +} diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index 62aec5ecb..25778d036 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.LocalMetadata var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -46,7 +46,7 @@ namespace MediaBrowser.LocalMetadata } finally { - XmlProviderUtils.XmlParsingResourcePool.Release(); + //XmlProviderUtils.XmlParsingResourcePool.Release(); } return result; diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ca48b8889..2a99076d4 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -83,6 +83,9 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="..\MediaBrowser.Model\Activity\ActivityLogEntry.cs"> + <Link>Activity\ActivityLogEntry.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\ApiClient\ApiClientExtensions.cs"> <Link>ApiClient\ApiClientExtensions.cs</Link> </Compile> @@ -173,9 +176,6 @@ <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs"> <Link>Configuration\ServerConfiguration.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Configuration\SubtitleOptions.cs"> - <Link>Configuration\SubtitleOptions.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Configuration\SubtitlePlaybackMode.cs"> <Link>Configuration\SubtitlePlaybackMode.cs</Link> </Compile> @@ -728,6 +728,9 @@ <Compile Include="..\MediaBrowser.Model\Providers\RemoteSubtitleInfo.cs"> <Link>Providers\RemoteSubtitleInfo.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Providers\SubtitleOptions.cs"> + <Link>Providers\SubtitleOptions.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Querying\AllThemeMediaResult.cs"> <Link>Querying\AllThemeMediaResult.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 1adf83d36..72414d454 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -52,6 +52,9 @@ <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> + <Compile Include="..\mediabrowser.model\activity\ActivityLogEntry.cs"> + <Link>Activity\ActivityLogEntry.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\ApiClient\GeneralCommandEventArgs.cs"> <Link>ApiClient\GeneralCommandEventArgs.cs</Link> </Compile> @@ -136,9 +139,6 @@ <Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs"> <Link>Configuration\ServerConfiguration.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Configuration\SubtitleOptions.cs"> - <Link>Configuration\SubtitleOptions.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Configuration\SubtitlePlaybackMode.cs"> <Link>Configuration\SubtitlePlaybackMode.cs</Link> </Compile> @@ -685,6 +685,9 @@ <Compile Include="..\MediaBrowser.Model\Providers\RemoteSubtitleInfo.cs"> <Link>Providers\RemoteSubtitleInfo.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Providers\SubtitleOptions.cs"> + <Link>Providers\SubtitleOptions.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Querying\AllThemeMediaResult.cs"> <Link>Querying\AllThemeMediaResult.cs</Link> </Compile> diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs new file mode 100644 index 000000000..8fad57461 --- /dev/null +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Model.Logging; +using System; + +namespace MediaBrowser.Model.Activity +{ + public class ActivityLogEntry + { + /// <summary> + /// Gets or sets the identifier. + /// </summary> + /// <value>The identifier.</value> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the overview. + /// </summary> + /// <value>The overview.</value> + public string Overview { get; set; } + + /// <summary> + /// Gets or sets the short overview. + /// </summary> + /// <value>The short overview.</value> + public string ShortOverview { get; set; } + + /// <summary> + /// Gets or sets the type. + /// </summary> + /// <value>The type.</value> + public string Type { get; set; } + + /// <summary> + /// Gets or sets the item identifier. + /// </summary> + /// <value>The item identifier.</value> + public string ItemId { get; set; } + + /// <summary> + /// Gets or sets the date. + /// </summary> + /// <value>The date.</value> + public DateTime Date { 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 log severity. + /// </summary> + /// <value>The log severity.</value> + public LogSeverity Severity { get; set; } + } +} diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 363500954..98b765e6b 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -623,7 +623,7 @@ namespace MediaBrowser.Model.ApiClient Task ReportPlaybackStoppedAsync(PlaybackStopInfo info); /// <summary> - /// Instructs antoher client to browse to a library item. + /// Instructs another client to browse to a library item. /// </summary> /// <param name="sessionId">The session id.</param> /// <param name="itemId">The id of the item to browse to.</param> diff --git a/MediaBrowser.Model/Channels/ChannelItemQuery.cs b/MediaBrowser.Model/Channels/ChannelItemQuery.cs index a76c6cd2d..4aacc1619 100644 --- a/MediaBrowser.Model/Channels/ChannelItemQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelItemQuery.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using System.Collections.Generic; namespace MediaBrowser.Model.Channels { @@ -39,13 +38,13 @@ namespace MediaBrowser.Model.Channels public SortOrder? SortOrder { get; set; } public string[] SortBy { get; set; } public ItemFilter[] Filters { get; set; } - public List<ItemFields> Fields { get; set; } + public ItemFields[] Fields { get; set; } public ChannelItemQuery() { Filters = new ItemFilter[] { }; SortBy = new string[] { }; - Fields = new List<ItemFields>(); + Fields = new ItemFields[] { }; } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 4734e2af7..6600a3e91 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Model.Configuration { @@ -285,8 +286,6 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions(0, 1280) {ItemType = "Season"} }; - - SubtitleOptions = new SubtitleOptions(); } } } diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs index 5a83419e1..3c558577a 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/MediaBrowser.Model/Events/GenericEventArgs.cs @@ -13,5 +13,21 @@ namespace MediaBrowser.Model.Events /// </summary> /// <value>The argument.</value> public T Argument { get; set; } + + /// <summary> + /// Initializes a new instance of the <see cref="GenericEventArgs{T}"/> class. + /// </summary> + /// <param name="arg">The argument.</param> + public GenericEventArgs(T arg) + { + Argument = arg; + } + + /// <summary> + /// Initializes a new instance of the <see cref="GenericEventArgs{T}"/> class. + /// </summary> + public GenericEventArgs() + { + } } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 042828887..75694cb04 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -59,6 +59,7 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="Activity\ActivityLogEntry.cs" /> <Compile Include="ApiClient\HttpResponseEventArgs.cs" /> <Compile Include="ApiClient\IApiClient.cs" /> <Compile Include="ApiClient\ApiClientExtensions.cs" /> @@ -99,7 +100,7 @@ <Compile Include="Configuration\PathSubstitution.cs" /> <Compile Include="Notifications\SendToUserType.cs" /> <Compile Include="Configuration\ServerConfiguration.cs" /> - <Compile Include="Configuration\SubtitleOptions.cs" /> + <Compile Include="Providers\SubtitleOptions.cs" /> <Compile Include="Configuration\UnratedItem.cs" /> <Compile Include="Dlna\AudioOptions.cs" /> <Compile Include="Dlna\CodecProfile.cs" /> @@ -378,4 +379,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs index d50dba1b2..84f01e0b7 100644 --- a/MediaBrowser.Model/Configuration/SubtitleOptions.cs +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Configuration +namespace MediaBrowser.Model.Providers { public class SubtitleOptions { diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs index e73b4c9a1..956d68ae4 100644 --- a/MediaBrowser.Model/Tasks/TaskResult.cs +++ b/MediaBrowser.Model/Tasks/TaskResult.cs @@ -42,5 +42,11 @@ namespace MediaBrowser.Model.Tasks /// </summary> /// <value>The error message.</value> public string ErrorMessage { get; set; } + + /// <summary> + /// Gets or sets the long error message. + /// </summary> + /// <value>The long error message.</value> + public string LongErrorMessage { get; set; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 76a1e52f5..66188f796 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -152,6 +152,7 @@ <Compile Include="Manager\ProviderUtils.cs" /> <Compile Include="Studios\StudiosImageProvider.cs" /> <Compile Include="Studios\StudioMetadataService.cs" /> + <Compile Include="Subtitles\ConfigurationExtension.cs" /> <Compile Include="Subtitles\OpenSubtitleDownloader.cs" /> <Compile Include="Subtitles\SubtitleManager.cs" /> <Compile Include="TV\EpisodeMetadataService.cs" /> @@ -213,4 +214,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index a2e1ba05a..f48707582 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -13,10 +13,12 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; @@ -464,6 +466,11 @@ namespace MediaBrowser.Providers.MediaInfo } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration<SubtitleOptions>("subtitles"); + } + /// <summary> /// Adds the external subtitles. /// </summary> @@ -484,9 +491,11 @@ namespace MediaBrowser.Providers.MediaInfo var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; - if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && + var subtitleOptions = GetOptions(); + + if (enableSubtitleDownloading && (subtitleOptions.DownloadEpisodeSubtitles && video is Episode) || - (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && + (subtitleOptions.DownloadMovieSubtitles && video is Movie)) { var downloadedLanguages = await new SubtitleDownloader(_logger, @@ -494,9 +503,9 @@ namespace MediaBrowser.Providers.MediaInfo .DownloadSubtitles(video, currentStreams, externalSubtitleStreams, - _config.Configuration.SubtitleOptions.SkipIfGraphicalSubtitlesPresent, - _config.Configuration.SubtitleOptions.SkipIfAudioTrackMatches, - _config.Configuration.SubtitleOptions.DownloadLanguages, + subtitleOptions.SkipIfGraphicalSubtitlesPresent, + subtitleOptions.SkipIfAudioTrackMatches, + subtitleOptions.DownloadLanguages, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 361cc317c..63df3f50d 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -1,10 +1,12 @@ -using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -12,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.MediaInfo { @@ -45,8 +48,15 @@ namespace MediaBrowser.Providers.MediaInfo get { return "Library"; } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration<SubtitleOptions>("subtitles"); + } + public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { + var options = GetOptions(); + var videos = _libraryManager.RootFolder .RecursiveChildren .OfType<Video>() @@ -57,9 +67,9 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - return (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && + return (options.DownloadEpisodeSubtitles && i is Episode) || - (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && + (options.DownloadMovieSubtitles && i is Movie); }) .ToList(); @@ -70,7 +80,7 @@ namespace MediaBrowser.Providers.MediaInfo { try { - await DownloadSubtitles(video, cancellationToken).ConfigureAwait(false); + await DownloadSubtitles(video, options, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -86,11 +96,11 @@ namespace MediaBrowser.Providers.MediaInfo } } - private async Task DownloadSubtitles(Video video, CancellationToken cancellationToken) + private async Task DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken) { - if ((_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && + if ((options.DownloadEpisodeSubtitles && video is Episode) || - (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && + (options.DownloadMovieSubtitles && video is Movie)) { var mediaStreams = video.GetMediaSources(false).First().MediaStreams; @@ -103,9 +113,9 @@ namespace MediaBrowser.Providers.MediaInfo .DownloadSubtitles(video, currentStreams, externalSubtitleStreams, - _config.Configuration.SubtitleOptions.SkipIfGraphicalSubtitlesPresent, - _config.Configuration.SubtitleOptions.SkipIfAudioTrackMatches, - _config.Configuration.SubtitleOptions.DownloadLanguages, + options.SkipIfGraphicalSubtitlesPresent, + options.SkipIfAudioTrackMatches, + options.DownloadLanguages, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/Subtitles/ConfigurationExtension.cs b/MediaBrowser.Providers/Subtitles/ConfigurationExtension.cs new file mode 100644 index 000000000..f520915d8 --- /dev/null +++ b/MediaBrowser.Providers/Subtitles/ConfigurationExtension.cs @@ -0,0 +1,29 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Providers; +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Subtitles +{ + public static class ConfigurationExtension + { + public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager) + { + return manager.GetConfiguration<SubtitleOptions>("subtitles"); + } + } + + public class SubtitleConfigurationFactory : IConfigurationFactory + { + public IEnumerable<ConfigurationStore> GetConfigurations() + { + return new List<ConfigurationStore> + { + new ConfigurationStore + { + Key = "subtitles", + ConfigurationType = typeof (SubtitleOptions) + } + }; + } + } +} diff --git a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs index c25fbee6f..722d0d3f3 100644 --- a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs +++ b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Providers; @@ -6,7 +7,6 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Providers; using OpenSubtitlesHandler; @@ -45,16 +45,21 @@ namespace MediaBrowser.Providers.Subtitles _config = config; _encryption = encryption; - _config.ConfigurationUpdating += _config_ConfigurationUpdating; + _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating; // Reset the count every 24 hours _dailyTimer = new Timer(state => _dailyDownloadCount = 0, null, TimeSpan.FromHours(24), TimeSpan.FromHours(24)); } private const string PasswordHashPrefix = "h:"; - void _config_ConfigurationUpdating(object sender, GenericEventArgs<ServerConfiguration> e) + void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e) { - var options = e.Argument.SubtitleOptions; + if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + var options = (SubtitleOptions)e.NewConfiguration; if (options != null && !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && @@ -85,12 +90,19 @@ namespace MediaBrowser.Providers.Subtitles get { return "Open Subtitles"; } } + private SubtitleOptions GetOptions() + { + return _config.GetSubtitleConfiguration(); + } + public IEnumerable<VideoContentType> SupportedMediaTypes { get { - if (string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesUsername) || - string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesPasswordHash)) + var options = GetOptions(); + + if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) || + string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash)) { return new VideoContentType[] { }; } @@ -101,10 +113,11 @@ namespace MediaBrowser.Providers.Subtitles public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken) { - return GetSubtitlesInternal(id, cancellationToken); + return GetSubtitlesInternal(id, GetOptions(), cancellationToken); } private async Task<SubtitleResponse> GetSubtitlesInternal(string id, + SubtitleOptions options, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(id)) @@ -113,7 +126,7 @@ namespace MediaBrowser.Providers.Subtitles } if (_dailyDownloadCount >= MaxDownloadsPerDay && - !_config.Configuration.SubtitleOptions.IsOpenSubtitleVipAccount) + !options.IsOpenSubtitleVipAccount) { throw new InvalidOperationException("Open Subtitle's daily download limit has been exceeded. Please try again tomorrow."); } @@ -167,7 +180,7 @@ namespace MediaBrowser.Providers.Subtitles return; } - var options = _config.Configuration.SubtitleOptions ?? new SubtitleOptions(); + var options = GetOptions(); var user = options.OpenSubtitlesUsername ?? string.Empty; var password = DecryptPassword(options.OpenSubtitlesPasswordHash); @@ -289,7 +302,7 @@ namespace MediaBrowser.Providers.Subtitles public void Dispose() { - _config.ConfigurationUpdating -= _config_ConfigurationUpdating; + _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating; if (_dailyTimer != null) { diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 34656f05b..179ab425b 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -28,6 +29,9 @@ namespace MediaBrowser.Providers.Subtitles private readonly ILibraryManager _libraryManager; private readonly IItemRepository _itemRepo; + public event EventHandler<SubtitleDownloadEventArgs> SubtitlesDownloaded; + public event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure; + public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IItemRepository itemRepo) { _logger = logger; @@ -100,35 +104,63 @@ namespace MediaBrowser.Providers.Subtitles string subtitleId, CancellationToken cancellationToken) { - var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false); + var parts = subtitleId.Split(new[] { '_' }, 2); + var provider = GetProvider(parts.First()); - using (var stream = response.Stream) + try { - var savePath = Path.Combine(Path.GetDirectoryName(video.Path), - _fileSystem.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower()); + var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false); - if (response.IsForced) + using (var stream = response.Stream) { - savePath += ".forced"; - } + var savePath = Path.Combine(Path.GetDirectoryName(video.Path), + _fileSystem.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower()); - savePath += "." + response.Format.ToLower(); + if (response.IsForced) + { + savePath += ".forced"; + } - _logger.Info("Saving subtitles to {0}", savePath); + savePath += "." + response.Format.ToLower(); - _monitor.ReportFileSystemChangeBeginning(savePath); + _logger.Info("Saving subtitles to {0}", savePath); - try - { - using (var fs = _fileSystem.GetFileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + _monitor.ReportFileSystemChangeBeginning(savePath); + + try + { + using (var fs = _fileSystem.GetFileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) + { + await stream.CopyToAsync(fs).ConfigureAwait(false); + } + + EventHelper.FireEventIfNotNull(SubtitlesDownloaded, this, new SubtitleDownloadEventArgs + { + Item = video, + Format = response.Format, + Language = response.Language, + IsForced = response.IsForced, + Provider = provider.Name + + }, _logger); + } + finally { - await stream.CopyToAsync(fs).ConfigureAwait(false); + _monitor.ReportFileSystemChangeComplete(savePath, false); } } - finally + } + catch (Exception ex) + { + EventHelper.FireEventIfNotNull(SubtitleDownloadFailure, this, new SubtitleDownloadFailureEventArgs { - _monitor.ReportFileSystemChangeComplete(savePath, false); - } + Item = video, + Exception = ex, + Provider = provider.Name + + }, _logger); + + throw; } } @@ -267,5 +299,6 @@ namespace MediaBrowser.Providers.Subtitles Id = GetProviderId(i.Name) }); } + } } diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityManager.cs b/MediaBrowser.Server.Implementations/Activity/ActivityManager.cs new file mode 100644 index 000000000..46eaa7f21 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Activity/ActivityManager.cs @@ -0,0 +1,40 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Activity +{ + public class ActivityManager : IActivityManager + { + public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated; + + private readonly IActivityRepository _repo; + private readonly ILogger _logger; + + public ActivityManager(ILogger logger, IActivityRepository repo) + { + _logger = logger; + _repo = repo; + } + + public async Task Create(ActivityLogEntry entry) + { + entry.Id = Guid.NewGuid().ToString("N"); + entry.Date = DateTime.UtcNow; + + await _repo.Create(entry).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(EntryCreated, this, new GenericEventArgs<ActivityLogEntry>(entry), _logger); + } + + public QueryResult<ActivityLogEntry> GetActivityLogEntries(int? startIndex, int? limit) + { + return _repo.GetActivityLogEntries(startIndex, limit); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs new file mode 100644 index 000000000..787cbcc5b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs @@ -0,0 +1,293 @@ +using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Model.Activity; +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.Activity +{ + public class ActivityRepository : IActivityRepository, IDisposable + { + 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 _saveActivityCommand; + + public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths) + { + _logger = logger; + _appPaths = appPaths; + } + + public async Task Initialize() + { + var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db"); + + _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); + + string[] queries = { + + "create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY, Name TEXT, Overview TEXT, ShortOverview TEXT, Type TEXT, ItemId TEXT, UserId TEXT, DateCreated DATETIME, LogSeverity TEXT)", + "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)", + + //pragmas + "pragma temp_store = memory", + + "pragma shrink_memory" + }; + + _connection.RunQueries(queries, _logger); + + PrepareStatements(); + } + + private void PrepareStatements() + { + _saveActivityCommand = _connection.CreateCommand(); + _saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"; + + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Id"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Name"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Overview"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ShortOverview"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Type"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ItemId"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@UserId"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@DateCreated"); + _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@LogSeverity"); + } + + private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries"; + + public Task Create(ActivityLogEntry entry) + { + return Update(entry); + } + + public async Task Update(ActivityLogEntry entry) + { + if (entry == null) + { + throw new ArgumentNullException("entry"); + } + + await _writeLock.WaitAsync().ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id); + _saveActivityCommand.GetParameter(index++).Value = entry.Name; + _saveActivityCommand.GetParameter(index++).Value = entry.Overview; + _saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview; + _saveActivityCommand.GetParameter(index++).Value = entry.Type; + _saveActivityCommand.GetParameter(index++).Value = entry.ItemId; + _saveActivityCommand.GetParameter(index++).Value = entry.UserId; + _saveActivityCommand.GetParameter(index++).Value = entry.Date; + _saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString(); + + _saveActivityCommand.Transaction = transaction; + + _saveActivityCommand.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(); + } + } + + public QueryResult<ActivityLogEntry> GetActivityLogEntries(int? startIndex, int? limit) + { + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseActivitySelectText; + + var whereClauses = new List<string>(); + + if (startIndex.HasValue && startIndex.Value > 0) + { + whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries ORDER BY DateCreated DESC LIMIT {0})", + startIndex.Value.ToString(_usCulture))); + } + + if (whereClauses.Count > 0) + { + cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray()); + } + + cmd.CommandText += " ORDER BY DateCreated DESC"; + + if (limit.HasValue) + { + cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture); + } + + cmd.CommandText += "; select count (Id) from ActivityLogEntries"; + + var list = new List<ActivityLogEntry>(); + var count = 0; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess)) + { + while (reader.Read()) + { + list.Add(GetEntry(reader)); + } + + if (reader.NextResult() && reader.Read()) + { + count = reader.GetInt32(0); + } + } + + return new QueryResult<ActivityLogEntry>() + { + Items = list.ToArray(), + TotalRecordCount = count + }; + } + } + + private ActivityLogEntry GetEntry(IDataReader reader) + { + var index = 0; + + var info = new ActivityLogEntry + { + Id = reader.GetGuid(index).ToString("N") + }; + + index++; + if (!reader.IsDBNull(index)) + { + info.Name = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.Overview = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.ShortOverview = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.Type = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.ItemId = reader.GetString(index); + } + + index++; + if (!reader.IsDBNull(index)) + { + info.UserId = reader.GetString(index); + } + + index++; + info.Date = reader.GetDateTime(index).ToUniversalTime(); + + index++; + if (!reader.IsDBNull(index)) + { + info.Severity = (LogSeverity)Enum.Parse(typeof(LogSeverity), reader.GetString(index), true); + } + + 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); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs new file mode 100644 index 000000000..7af2a1c84 --- /dev/null +++ b/MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs @@ -0,0 +1,543 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Implementations.Logging; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Updates; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MediaBrowser.Server.Implementations.EntryPoints +{ + public class ActivityLogEntryPoint : IServerEntryPoint + { + private readonly IInstallationManager _installationManager; + + //private readonly ILogManager _logManager; + private readonly ILogger _logger; + private readonly ISessionManager _sessionManager; + private readonly ITaskManager _taskManager; + private readonly IActivityManager _activityManager; + private readonly ILocalizationManager _localization; + + private readonly ILibraryManager _libraryManager; + private readonly ISubtitleManager _subManager; + private readonly IUserManager _userManager; + private readonly IServerConfigurationManager _config; + private readonly IServerApplicationHost _appHost; + + public ActivityLogEntryPoint(ISessionManager sessionManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) + { + //_logger = _logManager.GetLogger("ActivityLogEntryPoint"); + _sessionManager = sessionManager; + _taskManager = taskManager; + _activityManager = activityManager; + _localization = localization; + _installationManager = installationManager; + _libraryManager = libraryManager; + _subManager = subManager; + _userManager = userManager; + _config = config; + //_logManager = logManager; + _appHost = appHost; + } + + public void Run() + { + _taskManager.TaskExecuting += _taskManager_TaskExecuting; + _taskManager.TaskCompleted += _taskManager_TaskCompleted; + + _installationManager.PluginInstalled += _installationManager_PluginInstalled; + _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; + _installationManager.PluginUpdated += _installationManager_PluginUpdated; + + _libraryManager.ItemAdded += _libraryManager_ItemAdded; + _libraryManager.ItemRemoved += _libraryManager_ItemRemoved; + + _sessionManager.SessionStarted += _sessionManager_SessionStarted; + _sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed; + _sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded; + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + + _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + + _subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded; + _subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure; + + _userManager.UserCreated += _userManager_UserCreated; + _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; + _userManager.UserDeleted += _userManager_UserDeleted; + _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated; + + _config.ConfigurationUpdated += _config_ConfigurationUpdated; + _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + + //_logManager.LoggerLoaded += _logManager_LoggerLoaded; + + _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; + } + + void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureForItem"), Notifications.Notifications.GetItemName(e.Item)), + Type = "SubtitleDownloadFailure", + ItemId = e.Item.Id.ToString("N"), + ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider), + Overview = LogHelper.GetLogMessage(e.Exception).ToString() + }); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + var item = e.MediaInfo; + + if (item == null) + { + //_logger.Warn("PlaybackStopped reported with null media info."); + return; + } + + if (e.Users.Count == 0) + { + return; + } + + var username = e.Users.First().Name; + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), username, item.Name), + Type = "PlaybackStopped", + ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), e.ClientName, e.DeviceName) + }); + } + + void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + { + var item = e.MediaInfo; + + if (item == null) + { + //_logger.Warn("PlaybackStart reported with null media info."); + return; + } + + if (e.Users.Count == 0) + { + return; + } + + var username = e.Users.First().Name; + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserStartedPlayingItemWithValues"), username, item.Name), + Type = "PlaybackStart", + ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), e.ClientName, e.DeviceName) + }); + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + string name; + var session = e.SessionInfo; + + if (string.IsNullOrWhiteSpace(session.UserName)) + { + name = string.Format(_localization.GetLocalizedString("DeviceOfflineWithName"), session.DeviceName); + } + else + { + name = string.Format(_localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName); + } + + CreateLogEntry(new ActivityLogEntry + { + Name = name, + Type = "SessionEnded", + ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint) + }); + } + + void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationRequest> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("AuthenticationSucceededWithUserName"), e.Argument.Username), + Type = "AuthenticationSucceeded" + }); + } + + void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username), + Type = "AuthenticationFailed" + }); + } + + void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = _localization.GetLocalizedString("MessageApplicationUpdated"), + Type = "ApplicationUpdated", + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr), + Overview = e.Argument.description + }); + } + + void _logManager_LoggerLoaded(object sender, EventArgs e) + { + } + + void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key), + Type = "NamedConfigurationUpdated" + }); + } + + void _config_ConfigurationUpdated(object sender, EventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"), + Type = "ServerConfigurationUpdated" + }); + } + + void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserConfigurationUpdatedWithName"), e.Argument.Name), + Type = "UserConfigurationUpdated" + }); + } + + void _userManager_UserDeleted(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name), + Type = "UserDeleted" + }); + } + + void _userManager_UserPasswordChanged(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), + Type = "UserPasswordChanged" + }); + } + + void _userManager_UserCreated(object sender, GenericEventArgs<User> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), + Type = "UserCreated" + }); + } + + void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)), + Type = "SubtitlesDownloaded", + ItemId = e.Item.Id.ToString("N"), + ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider) + }); + } + + void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + { + string name; + var session = e.SessionInfo; + + if (string.IsNullOrWhiteSpace(session.UserName)) + { + name = string.Format(_localization.GetLocalizedString("DeviceOnlineWithName"), session.DeviceName); + } + else + { + name = string.Format(_localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName); + } + + CreateLogEntry(new ActivityLogEntry + { + Name = name, + Type = "SessionStarted", + ShortOverview = string.Format(_localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint) + }); + } + + void _libraryManager_ItemRemoved(object sender, ItemChangeEventArgs e) + { + if (e.Item is LiveTvProgram || e.Item is IChannelItem) + { + return; + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ItemRemovedWithName"), Notifications.Notifications.GetItemName(e.Item)), + Type = "ItemRemoved" + }); + } + + void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e) + { + if (e.Item is LiveTvProgram || e.Item is IChannelItem) + { + return; + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ItemAddedWithName"), Notifications.Notifications.GetItemName(e.Item)), + Type = "ItemAdded", + ItemId = e.Item.Id.ToString("N") + }); + } + + void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), + Type = "PluginUpdated", + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.Item2.versionStr), + Overview = e.Argument.Item2.description + }); + } + + void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), + Type = "PluginUninstalled" + }); + } + + void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), + Type = "PluginInstalled", + ShortOverview = string.Format(_localization.GetLocalizedString("VersionNumber"), e.Argument.versionStr) + }); + } + + void _taskManager_TaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e) + { + var task = e.Argument; + + var activityTask = task.ScheduledTask as IScheduledTaskActivityLog; + if (activityTask != null && !activityTask.IsActivityLogged) + { + return; + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskStartedWithName"), task.Name), + Type = "ScheduledTaskStarted" + }); + } + + void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + { + var result = e.Result; + var task = e.Task; + + var activityTask = task.ScheduledTask as IScheduledTaskActivityLog; + if (activityTask != null && !activityTask.IsActivityLogged) + { + return; + } + + var time = result.EndTimeUtc - result.StartTimeUtc; + var runningTime = string.Format(_localization.GetLocalizedString("LabelRunningTimeValue"), ToUserFriendlyString(time)); + + if (result.Status == TaskCompletionStatus.Cancelled) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskCancelledWithName"), task.Name), + Type = "ScheduledTaskCancelled", + ShortOverview = runningTime + }); + } + else if (result.Status == TaskCompletionStatus.Completed) + { + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskCompletedWithName"), task.Name), + Type = "ScheduledTaskCompleted", + ShortOverview = runningTime + }); + } + else if (result.Status == TaskCompletionStatus.Failed) + { + var vals = new List<string>(); + + if (!string.IsNullOrWhiteSpace(e.Result.ErrorMessage)) + { + vals.Add(e.Result.ErrorMessage); + } + if (!string.IsNullOrWhiteSpace(e.Result.LongErrorMessage)) + { + vals.Add(e.Result.LongErrorMessage); + } + + CreateLogEntry(new ActivityLogEntry + { + Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), + Type = "ScheduledTaskFailed", + Overview = string.Join(Environment.NewLine, vals.ToArray()), + ShortOverview = runningTime + }); + } + } + + private async void CreateLogEntry(ActivityLogEntry entry) + { + try + { + await _activityManager.Create(entry).ConfigureAwait(false); + } + catch + { + // Logged at lower levels + } + } + + public void Dispose() + { + _taskManager.TaskExecuting -= _taskManager_TaskExecuting; + _taskManager.TaskCompleted -= _taskManager_TaskCompleted; + + _installationManager.PluginInstalled -= _installationManager_PluginInstalled; + _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; + _installationManager.PluginUpdated -= _installationManager_PluginUpdated; + + _libraryManager.ItemAdded -= _libraryManager_ItemAdded; + _libraryManager.ItemRemoved -= _libraryManager_ItemRemoved; + + _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + _sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed; + _sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded; + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + + _subManager.SubtitlesDownloaded -= _subManager_SubtitlesDownloaded; + _subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure; + + _userManager.UserCreated -= _userManager_UserCreated; + _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; + _userManager.UserDeleted -= _userManager_UserDeleted; + _userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated; + + _config.ConfigurationUpdated -= _config_ConfigurationUpdated; + _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; + + //_logManager.LoggerLoaded -= _logManager_LoggerLoaded; + + _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + } + + /// <summary> + /// Constructs a user-friendly string for this TimeSpan instance. + /// </summary> + public static string ToUserFriendlyString(TimeSpan span) + { + const int DaysInYear = 365; + const int DaysInMonth = 30; + + // Get each non-zero value from TimeSpan component + List<string> values = new List<string>(); + + // Number of years + int days = span.Days; + if (days >= DaysInYear) + { + int years = (days / DaysInYear); + values.Add(CreateValueString(years, "year")); + days = (days % DaysInYear); + } + // Number of months + if (days >= DaysInMonth) + { + int months = (days / DaysInMonth); + values.Add(CreateValueString(months, "month")); + days = (days % DaysInMonth); + } + // Number of days + if (days >= 1) + values.Add(CreateValueString(days, "day")); + // Number of hours + if (span.Hours >= 1) + values.Add(CreateValueString(span.Hours, "hour")); + // Number of minutes + if (span.Minutes >= 1) + values.Add(CreateValueString(span.Minutes, "minute")); + // Number of seconds (include when 0 if no other components included) + if (span.Seconds >= 1 || values.Count == 0) + values.Add(CreateValueString(span.Seconds, "second")); + + // Combine values into string + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < values.Count; i++) + { + if (builder.Length > 0) + builder.Append((i == (values.Count - 1)) ? " and " : ", "); + builder.Append(values[i]); + } + // Return result + return builder.ToString(); + } + + /// <summary> + /// Constructs a string description of a time-span value. + /// </summary> + /// <param name="value">The value of this item</param> + /// <param name="description">The name of this item (singular form)</param> + private static string CreateValueString(int value, string description) + { + return String.Format("{0:#,##0} {1}", + value, (value == 1) ? description : String.Format("{0}s", description)); + } + } +} diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs index b10b64c3e..6a0bb780c 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs @@ -2,14 +2,12 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; @@ -37,7 +35,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications private readonly ITaskManager _taskManager; private readonly INotificationManager _notificationManager; - private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ISessionManager _sessionManager; private readonly IServerApplicationHost _appHost; @@ -45,14 +42,13 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications private Timer LibraryUpdateTimer { get; set; } private readonly object _libraryChangedSyncLock = new object(); - public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, IServerConfigurationManager config, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost) + public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost) { _installationManager = installationManager; _userManager = userManager; _logger = logger; _taskManager = taskManager; _notificationManager = notificationManager; - _config = config; _libraryManager = libraryManager; _sessionManager = sessionManager; _appHost = appHost; @@ -317,7 +313,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications } } - private string GetItemName(BaseItem item) + public static string GetItemName(BaseItem item) { var name = item.Name; diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs index 42aadf62e..5f1db03c6 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/WebSocketNotifier.cs @@ -23,7 +23,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications public void Run() { _notificationsRepo.NotificationAdded += _notificationsRepo_NotificationAdded; - _notificationsRepo.NotificationUpdated += _notificationsRepo_NotificationUpdated; _notificationsRepo.NotificationsMarkedRead += _notificationsRepo_NotificationsMarkedRead; } @@ -40,13 +39,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications _serverManager.SendWebSocketMessage("NotificationsMarkedRead", msg); } - void _notificationsRepo_NotificationUpdated(object sender, NotificationUpdateEventArgs e) - { - var msg = e.Notification.UserId + "|" + e.Notification.Id; - - _serverManager.SendWebSocketMessage("NotificationUpdated", msg); - } - void _notificationsRepo_NotificationAdded(object sender, NotificationUpdateEventArgs e) { var msg = e.Notification.UserId + "|" + e.Notification.Id; @@ -57,7 +49,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications public void Dispose() { _notificationsRepo.NotificationAdded -= _notificationsRepo_NotificationAdded; - _notificationsRepo.NotificationUpdated -= _notificationsRepo_NotificationUpdated; } } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 305f2800f..b969d551a 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -3,11 +3,13 @@ using MediaBrowser.Common.Plugins; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Activity; using MediaBrowser.Model.Events; using System; using System.Threading; @@ -47,6 +49,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IDtoService _dtoService; private readonly ISessionManager _sessionManager; + private readonly IActivityManager _activityManager; /// <summary> /// Initializes a new instance of the <see cref="ServerEventNotifier" /> class. @@ -58,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// <param name="taskManager">The task manager.</param> /// <param name="dtoService">The dto service.</param> /// <param name="sessionManager">The session manager.</param> - public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager) + public ServerEventNotifier(IServerManager serverManager, IServerApplicationHost appHost, IUserManager userManager, IInstallationManager installationManager, ITaskManager taskManager, IDtoService dtoService, ISessionManager sessionManager, IActivityManager activityManager) { _serverManager = serverManager; _userManager = userManager; @@ -67,6 +70,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _taskManager = taskManager; _dtoService = dtoService; _sessionManager = sessionManager; + _activityManager = activityManager; } public void Run() @@ -84,6 +88,13 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; _taskManager.TaskCompleted += _taskManager_TaskCompleted; + + _activityManager.EntryCreated += _activityManager_EntryCreated; + } + + void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e) + { + _serverManager.SendWebSocketMessage("ActivityLogEntryCreated", e.Argument); } void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e) diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 1c4ccb141..8dfdfdaec 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -13,7 +13,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.FileOrganization { - public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask + public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask, IScheduledTaskActivityLog { private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; @@ -77,5 +77,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization { get { return GetTvOptions().IsEnabled; } } + + public bool IsActivityLogged + { + get { return false; } + } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index b18d0df5e..cd871d8eb 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -110,7 +110,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { try { - var webSocketContext = ctx.AcceptWebSocket(null, null); + var webSocketContext = ctx.AcceptWebSocket(null); if (WebSocketHandler != null) { diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 43c9f3b9c..139f8629c 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -48,6 +48,7 @@ namespace MediaBrowser.Server.Implementations.Library /// </summary> /// <value>The user repository.</value> private IUserRepository UserRepository { get; set; } + public event EventHandler<GenericEventArgs<User>> UserPasswordChanged; private readonly IXmlSerializer _xmlSerializer; @@ -390,7 +391,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="user">The user.</param> /// <param name="newPassword">The new password.</param> /// <returns>Task.</returns> - public Task ChangePassword(User user, string newPassword) + public async Task ChangePassword(User user, string newPassword) { if (user == null) { @@ -399,7 +400,9 @@ namespace MediaBrowser.Server.Implementations.Library user.Password = string.IsNullOrEmpty(newPassword) ? string.Empty : GetSha1String(newPassword); - return UpdateUser(user); + await UpdateUser(user).ConfigureAwait(false); + + EventHelper.FireEventIfNotNull(UserPasswordChanged, this, new GenericEventArgs<User>(user), _logger); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 605bbea0d..973089795 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -699,10 +699,10 @@ "HeaderProfileServerSettingsHelp": "These values control how Media Browser will present itself to the device.", "LabelMaxBitrate": "Max bitrate:", "LabelMaxBitrateHelp": "Specify a max bitrate in bandwidth constrained environments, or if the device imposes it's own limit.", - "LabelMaxStreamingBitrate": "Max streaming bitrate:", - "LabelMaxStreamingBitrateHelp": "Specify a max bitrate when streaming.", - "LabelMaxStaticBitrate": "Max sync bitrate:", - "LabelMaxStaticBitrateHelp": "Specify a max bitrate when syncing content at high quality.", + "LabelMaxStreamingBitrate": "Max streaming bitrate:", + "LabelMaxStreamingBitrateHelp": "Specify a max bitrate when streaming.", + "LabelMaxStaticBitrate": "Max sync bitrate:", + "LabelMaxStaticBitrateHelp": "Specify a max bitrate when syncing content at high quality.", "OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests", "OptionIgnoreTranscodeByteRangeRequestsHelp": "If enabled, these requests will be honored but will ignore the byte range header.", "LabelFriendlyName": "Friendly name", @@ -808,8 +808,8 @@ "TabNextUp": "Next Up", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the New button to start creating Collections.", - "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", - "MessageNoPlaylistItemsAvailable": "This playlist is currently empty.", + "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", + "MessageNoPlaylistItemsAvailable": "This playlist is currently empty.", "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client", "ButtonDismiss": "Dismiss", "MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.", @@ -918,52 +918,84 @@ "LabelContext": "Context:", "OptionContextStreaming": "Streaming", "OptionContextStatic": "Sync", - "ButtonAddToPlaylist": "Add to playlist", - "TabPlaylists": "Playlists", - "ButtonClose": "Close", - "LabelAllLanguages": "All languages", - "HeaderBrowseOnlineImages": "Browse Online Images", - "LabelSource": "Source:", - "OptionAll": "All", - "LabelImage": "Image:", - "ButtonUpload": "Upload", - "ButtonBrowseImages": "Browse Images", - "HeaderImages": "Images", - "HeaderBackdrops": "Backdrops", - "HeaderScreenshots": "Screenshots", - "HeaderAddUpdateImage": "Add/Update Image", - "LabelDropImageHere": "Drop image here", - "LabelJpgPngOnly": "JPG/PNG only", - "LabelImageType": "Image type:", - "OptionPrimary": "Primary", - "OptionArt": "Art", - "OptionBackdrop": "Backdrop", - "OptionBox": "Box", - "OptionBoxRear": "Box rear", - "OptionDisc": "Disc", - "OptionLogo": "Logo", - "OptionMenu": "Menu", - "OptionScreenshot": "Screenshot", - "OptionLocked": "Locked", - "OptionUnidentified": "Unidentified", - "OptionMissingParentalRating": "Missing parental rating", - "OptionStub": "Stub", - "HeaderEpisodes": "Episodes:", - "OptionSeason0": "Season 0", - "LabelReport": "Report:", - "OptionReportSongs": "Songs", - "OptionReportSeries": "Series", - "OptionReportSeasons": "Seasons", - "OptionReportTrailers": "Trailers", - "OptionReportMusicVideos": "Music videos", - "OptionReportMovies": "Movies", - "OptionReportHomeVideos": "Home videos", - "OptionReportGames": "Games", - "OptionReportEpisodes": "Episodes", - "OptionReportCollections": "Collections", - "OptionReportBooks": "Books", - "OptionReportArtists": "Artists", - "OptionReportAlbums": "Albums", + "ButtonAddToPlaylist": "Add to playlist", + "TabPlaylists": "Playlists", + "ButtonClose": "Close", + "LabelAllLanguages": "All languages", + "HeaderBrowseOnlineImages": "Browse Online Images", + "LabelSource": "Source:", + "OptionAll": "All", + "LabelImage": "Image:", + "ButtonUpload": "Upload", + "ButtonBrowseImages": "Browse Images", + "HeaderImages": "Images", + "HeaderBackdrops": "Backdrops", + "HeaderScreenshots": "Screenshots", + "HeaderAddUpdateImage": "Add/Update Image", + "LabelDropImageHere": "Drop image here", + "LabelJpgPngOnly": "JPG/PNG only", + "LabelImageType": "Image type:", + "OptionPrimary": "Primary", + "OptionArt": "Art", + "OptionBackdrop": "Backdrop", + "OptionBox": "Box", + "OptionBoxRear": "Box rear", + "OptionDisc": "Disc", + "OptionLogo": "Logo", + "OptionMenu": "Menu", + "OptionScreenshot": "Screenshot", + "OptionLocked": "Locked", + "OptionUnidentified": "Unidentified", + "OptionMissingParentalRating": "Missing parental rating", + "OptionStub": "Stub", + "HeaderEpisodes": "Episodes:", + "OptionSeason0": "Season 0", + "LabelReport": "Report:", + "OptionReportSongs": "Songs", + "OptionReportSeries": "Series", + "OptionReportSeasons": "Seasons", + "OptionReportTrailers": "Trailers", + "OptionReportMusicVideos": "Music videos", + "OptionReportMovies": "Movies", + "OptionReportHomeVideos": "Home videos", + "OptionReportGames": "Games", + "OptionReportEpisodes": "Episodes", + "OptionReportCollections": "Collections", + "OptionReportBooks": "Books", + "OptionReportArtists": "Artists", + "OptionReportAlbums": "Albums", "OptionReportAdultVideos": "Adult videos", - "ButtonMore": "More" + "ButtonMore": "More", + "HeaderActivity": "Activity", + "ScheduledTaskStartedWithName": "{0} started", + "ScheduledTaskCancelledWithName": "{0} was cancelled", + "ScheduledTaskCompletedWithName": "{0} completed", + "ScheduledTaskFailed": "Scheduled task completed", + "PluginInstalledWithName": "{0} was installed", + "PluginUpdatedWithName": "{0} was updated", + "PluginUninstalledWithName": "{0} was uninstalled", + "ScheduledTaskFailedWithName": "{0} failed", + "ItemAddedWithName": "{0} was added to the library", + "ItemRemovedWithName": "{0} was removed from the library", + "DeviceOnlineWithName": "{0} is connected", + "UserOnlineFromDevice": "{0} is online from {1}", + "DeviceOfflineWithName": "{0} has disconnected", + "UserOfflineFromDevice": "{0} has disconnected from {1}", + "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", + "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", + "LabelRunningTimeValue": "Running time: {0}", + "LabelIpAddressValue": "Ip address: {0}", + "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}", + "UserCreatedWithName": "User {0} has been created", + "UserPasswordChangedWithName": "Password has been changed for user {0}", + "UserDeletedWithName": "User {0} has been deleted", + "MessageServerConfigurationUpdated": "Server configuration has been updated", + "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", + "MessageApplicationUpdated": "Media Browser Server has been updated", + "AuthenticationSucceededWithUserName": "{0} successfully authenticated", + "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", + "UserStartedPlayingItemWithValues": "{0} has started playing {1}", + "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}", + "AppDeviceValues": "App: {0}, Device: {1}", + "ProviderValue": "Provider: {0}" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9ee00dd42..58c8ef995 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -101,6 +101,8 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="Activity\ActivityManager.cs" /> + <Compile Include="Activity\ActivityRepository.cs" /> <Compile Include="Branding\BrandingConfigurationFactory.cs" /> <Compile Include="Channels\ChannelConfigurations.cs" /> <Compile Include="Channels\ChannelDownloadScheduledTask.cs" /> @@ -117,6 +119,7 @@ <Compile Include="Drawing\PlayedIndicatorDrawer.cs" /> <Compile Include="Drawing\UnplayedCountIndicator.cs" /> <Compile Include="Dto\DtoService.cs" /> + <Compile Include="EntryPoints\ActivityLogEntryPoint.cs" /> <Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" /> <Compile Include="EntryPoints\ExternalPortForwarding.cs" /> <Compile Include="EntryPoints\LibraryChangedNotifier.cs" /> @@ -492,4 +495,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index 2424a6652..d34d5a293 100644 --- a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -207,46 +207,6 @@ namespace MediaBrowser.Server.Implementations.Notifications } /// <summary> - /// Gets the notification. - /// </summary> - /// <param name="id">The id.</param> - /// <param name="userId">The user id.</param> - /// <returns>Notification.</returns> - /// <exception cref="System.ArgumentNullException"> - /// id - /// or - /// userId - /// </exception> - public Notification GetNotification(string id, string userId) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - if (string.IsNullOrEmpty(userId)) - { - throw new ArgumentNullException("userId"); - } - - using (var cmd = _connection.CreateCommand()) - { - cmd.CommandText = "select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId where Id=@Id And UserId = @UserId"; - - cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = new Guid(id); - cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId); - - using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow)) - { - if (reader.Read()) - { - return GetNotification(reader); - } - } - return null; - } - } - - /// <summary> /// Gets the level. /// </summary> /// <param name="reader">The reader.</param> @@ -290,32 +250,6 @@ namespace MediaBrowser.Server.Implementations.Notifications } /// <summary> - /// Updates the notification. - /// </summary> - /// <param name="notification">The notification.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task UpdateNotification(Notification notification, CancellationToken cancellationToken) - { - await ReplaceNotification(notification, cancellationToken).ConfigureAwait(false); - - if (NotificationUpdated != null) - { - try - { - NotificationUpdated(this, new NotificationUpdateEventArgs - { - Notification = notification - }); - } - catch (Exception ex) - { - _logger.ErrorException("Error in NotificationUpdated event handler", ex); - } - } - } - - /// <summary> /// Replaces the notification. /// </summary> /// <param name="notification">The notification.</param> diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs index 3d0781e91..7a23d8e08 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs @@ -153,6 +153,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// <param name="result">The result.</param> private async void ProcessWebSocketMessageReceived(WebSocketMessageInfo result) { + //_logger.Debug("Websocket message received: {0}", result.MessageType); + var tasks = _webSocketListeners.Select(i => Task.Run(async () => { try diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 61c65cfd6..7db6d0191 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -14,11 +14,13 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; +using MediaBrowser.Server.Implementations.Security; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,7 +29,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.Security; namespace MediaBrowser.Server.Implementations.Session { @@ -76,6 +77,10 @@ namespace MediaBrowser.Server.Implementations.Session private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase); + public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed; + + public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationSucceeded; + /// <summary> /// Occurs when [playback start]. /// </summary> @@ -399,6 +404,11 @@ namespace MediaBrowser.Server.Implementations.Session Id = Guid.NewGuid().ToString("N") }; + sessionInfo.DeviceName = deviceName; + sessionInfo.UserId = userId; + sessionInfo.UserName = username; + sessionInfo.RemoteEndPoint = remoteEndPoint; + OnSessionStarted(sessionInfo); return sessionInfo; @@ -1191,44 +1201,37 @@ namespace MediaBrowser.Server.Implementations.Session /// <summary> /// Authenticates the new session. /// </summary> - /// <param name="username">The username.</param> - /// <param name="password">The password.</param> - /// <param name="clientType">Type of the client.</param> - /// <param name="appVersion">The application version.</param> - /// <param name="deviceId">The device identifier.</param> - /// <param name="deviceName">Name of the device.</param> - /// <param name="remoteEndPoint">The remote end point.</param> + /// <param name="request">The request.</param> /// <param name="isLocal">if set to <c>true</c> [is local].</param> /// <returns>Task{SessionInfo}.</returns> + /// <exception cref="AuthenticationException">Invalid user or password entered.</exception> /// <exception cref="System.UnauthorizedAccessException">Invalid user or password entered.</exception> /// <exception cref="UnauthorizedAccessException">Invalid user or password entered.</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(AuthenticationRequest request, bool isLocal) { - var result = (isLocal && string.Equals(clientType, "Dashboard", StringComparison.OrdinalIgnoreCase)) || - await _userManager.AuthenticateUser(username, password).ConfigureAwait(false); + var result = (isLocal && string.Equals(request.App, "Dashboard", StringComparison.OrdinalIgnoreCase)) || + await _userManager.AuthenticateUser(request.Username, request.Password).ConfigureAwait(false); if (!result) { + EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger); + throw new AuthenticationException("Invalid user or password entered."); } var user = _userManager.Users - .First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + .First(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); - var token = await GetAuthorizationToken(user.Id.ToString("N"), deviceId, clientType, deviceName).ConfigureAwait(false); + var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.DeviceName).ConfigureAwait(false); - var session = await LogSessionActivity(clientType, - appVersion, - deviceId, - deviceName, - remoteEndPoint, + EventHelper.FireEventIfNotNull(AuthenticationSucceeded, this, new GenericEventArgs<AuthenticationRequest>(request), _logger); + + var session = await LogSessionActivity(request.App, + request.AppVersion, + request.DeviceId, + request.DeviceName, + request.RemoteEndPoint, user) .ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index a50a6f02e..ed590a1f2 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Net; -using MediaBrowser.Controller; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index bb22e992b..65da74f9e 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Sync { - public class SyncRepository : ISyncRepository + public class SyncRepository : ISyncRepository, IDisposable { private IDbConnection _connection; private readonly ILogger _logger; @@ -422,8 +422,50 @@ namespace MediaBrowser.Server.Implementations.Sync } info.TargetId = reader.GetString(5); - + 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); + } + } + } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index d44ac250d..357395e72 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -9,6 +9,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; +using MediaBrowser.Controller.Activity; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; @@ -53,6 +54,7 @@ using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; using MediaBrowser.Server.Implementations; +using MediaBrowser.Server.Implementations.Activity; using MediaBrowser.Server.Implementations.Channels; using MediaBrowser.Server.Implementations.Collections; using MediaBrowser.Server.Implementations.Configuration; @@ -343,6 +345,13 @@ namespace MediaBrowser.ServerApplication saveConfig = true; } + if (ServerConfigurationManager.Configuration.SubtitleOptions != null) + { + ServerConfigurationManager.SaveConfiguration("subtitles", ServerConfigurationManager.Configuration.SubtitleOptions); + ServerConfigurationManager.Configuration.SubtitleOptions = null; + saveConfig = true; + } + if (saveConfig) { ServerConfigurationManager.SaveConfiguration(); @@ -641,6 +650,10 @@ namespace MediaBrowser.ServerApplication MediaEncoder, ChapterManager); RegisterSingleInstance(EncodingManager); + var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false); + RegisterSingleInstance(activityLogRepo); + RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo)); + var authContext = new AuthorizationContext(); RegisterSingleInstance<IAuthorizationContext>(authContext); RegisterSingleInstance<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); @@ -730,6 +743,15 @@ namespace MediaBrowser.ServerApplication return repo; } + private async Task<IActivityRepository> GetActivityLogRepository() + { + var repo = new ActivityRepository(LogManager.GetLogger("ActivityRepository"), ServerConfigurationManager.ApplicationPaths); + + await repo.Initialize().ConfigureAwait(false); + + return repo; + } + private async Task<ISyncRepository> GetSyncRepository() { var repo = new SyncRepository(LogManager.GetLogger("SyncRepository"), ServerConfigurationManager.ApplicationPaths); |
