add new notification features

pull/702/head
Luke Pulverenti 10 years ago
parent bdffaf22c9
commit fadda8ef56

@ -37,23 +37,6 @@ namespace MediaBrowser.Api.Images
public string Id { get; set; }
}
[Route("/Artists/{Name}/Images", "GET")]
[Route("/Genres/{Name}/Images", "GET")]
[Route("/GameGenres/{Name}/Images", "GET")]
[Route("/MusicGenres/{Name}/Images", "GET")]
[Route("/Persons/{Name}/Images", "GET")]
[Route("/Studios/{Name}/Images", "GET")]
[Api(Description = "Gets information about an item's images")]
public class GetItemByNameImageInfos : IReturn<List<ImageInfo>>
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Name { get; set; }
}
[Route("/Items/{Id}/Images/{Type}", "GET")]
[Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
[Api(Description = "Gets an item image")]
@ -103,45 +86,6 @@ namespace MediaBrowser.Api.Images
public int NewIndex { get; set; }
}
[Route("/Artists/{Name}/Images/{Type}/{Index}/Index", "POST")]
[Route("/Genres/{Name}/Images/{Type}/{Index}/Index", "POST")]
[Route("/GameGenres/{Name}/Images/{Type}/{Index}/Index", "POST")]
[Route("/MusicGenres/{Name}/Images/{Type}/{Index}/Index", "POST")]
[Route("/Persons/{Name}/Images/{Type}/{Index}/Index", "POST")]
[Route("/Studios/{Name}/Images/{Type}/{Index}/Index", "POST")]
[Route("/Years/{Year}/Images/{Type}/{Index}/Index", "POST")]
[Api(Description = "Updates the index for an item image")]
public class UpdateItemByNameImageIndex : IReturnVoid
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the type of the image.
/// </summary>
/// <value>The type of the image.</value>
[ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public ImageType Type { get; set; }
/// <summary>
/// Gets or sets the index.
/// </summary>
/// <value>The index.</value>
[ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public int Index { get; set; }
/// <summary>
/// Gets or sets the new index.
/// </summary>
/// <value>The new index.</value>
[ApiMember(Name = "NewIndex", Description = "The new image index", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public int NewIndex { get; set; }
}
/// <summary>
/// Class GetPersonImage
/// </summary>
@ -202,31 +146,6 @@ namespace MediaBrowser.Api.Images
public Guid Id { get; set; }
}
[Route("/Artists/{Name}/Images/{Type}", "DELETE")]
[Route("/Artists/{Name}/Images/{Type}/{Index}", "DELETE")]
[Route("/Genres/{Name}/Images/{Type}", "DELETE")]
[Route("/Genres/{Name}/Images/{Type}/{Index}", "DELETE")]
[Route("/GameGenres/{Name}/Images/{Type}", "DELETE")]
[Route("/GameGenres/{Name}/Images/{Type}/{Index}", "DELETE")]
[Route("/MusicGenres/{Name}/Images/{Type}", "DELETE")]
[Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "DELETE")]
[Route("/Persons/{Name}/Images/{Type}", "DELETE")]
[Route("/Persons/{Name}/Images/{Type}/{Index}", "DELETE")]
[Route("/Studios/{Name}/Images/{Type}", "DELETE")]
[Route("/Studios/{Name}/Images/{Type}/{Index}", "DELETE")]
[Route("/Years/{Year}/Images/{Type}", "DELETE")]
[Route("/Years/{Year}/Images/{Type}/{Index}", "DELETE")]
[Api(Description = "Deletes an item image")]
public class DeleteItemByNameImage : DeleteImageRequest, IReturnVoid
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public string Name { get; set; }
}
/// <summary>
/// Class DeleteUserImage
/// </summary>
@ -287,37 +206,6 @@ namespace MediaBrowser.Api.Images
public Stream RequestStream { get; set; }
}
[Route("/Artists/{Name}/Images/{Type}", "POST")]
[Route("/Artists/{Name}/Images/{Type}/{Index}", "POST")]
[Route("/Genres/{Name}/Images/{Type}", "POST")]
[Route("/Genres/{Name}/Images/{Type}/{Index}", "POST")]
[Route("/GameGenres/{Name}/Images/{Type}", "POST")]
[Route("/GameGenres/{Name}/Images/{Type}/{Index}", "POST")]
[Route("/MusicGenres/{Name}/Images/{Type}", "POST")]
[Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "POST")]
[Route("/Persons/{Name}/Images/{Type}", "POST")]
[Route("/Persons/{Name}/Images/{Type}/{Index}", "POST")]
[Route("/Studios/{Name}/Images/{Type}", "POST")]
[Route("/Studios/{Name}/Images/{Type}/{Index}", "POST")]
[Route("/Years/{Year}/Images/{Type}", "POST")]
[Route("/Years/{Year}/Images/{Type}/{Index}", "POST")]
[Api(Description = "Posts an item image")]
public class PostItemByNameImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
/// <summary>
/// The raw Http Request Input Stream
/// </summary>
/// <value>The request stream.</value>
public Stream RequestStream { get; set; }
}
/// <summary>
/// Class ImageService
/// </summary>
@ -327,26 +215,21 @@ namespace MediaBrowser.Api.Images
private readonly ILibraryManager _libraryManager;
private readonly IApplicationPaths _appPaths;
private readonly IProviderManager _providerManager;
private readonly IItemRepository _itemRepo;
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="ImageService" /> class.
/// </summary>
public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
public ImageService(IUserManager userManager, ILibraryManager libraryManager, IProviderManager providerManager, IItemRepository itemRepo, IImageProcessor imageProcessor, IFileSystem fileSystem)
{
_userManager = userManager;
_libraryManager = libraryManager;
_appPaths = appPaths;
_providerManager = providerManager;
_itemRepo = itemRepo;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_fileSystem = fileSystem;
}
@ -365,28 +248,6 @@ namespace MediaBrowser.Api.Images
return ToOptimizedSerializedResultUsingCache(result);
}
public object Get(GetItemByNameImageInfos request)
{
var result = GetItemByNameImageInfos(request);
return ToOptimizedSerializedResultUsingCache(result);
}
/// <summary>
/// Gets the item by name image infos.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task{List{ImageInfo}}.</returns>
private List<ImageInfo> GetItemByNameImageInfos(GetItemByNameImageInfos request)
{
var pathInfo = PathInfo.Parse(Request.PathInfo);
var type = pathInfo.GetArgumentValue<string>(0);
var item = GetItemByName(request.Name, type, _libraryManager);
return GetItemImageInfos(item);
}
/// <summary>
/// Gets the item image infos.
/// </summary>
@ -540,21 +401,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
public void Post(PostItemByNameImage request)
{
var pathInfo = PathInfo.Parse(Request.PathInfo);
var type = pathInfo.GetArgumentValue<string>(0);
var name = pathInfo.GetArgumentValue<string>(1);
request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(3), true);
var item = GetItemByName(name, type, _libraryManager);
var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType);
Task.WaitAll(task);
}
/// <summary>
/// Posts the specified request.
/// </summary>
@ -599,22 +445,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
/// <summary>
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
public void Delete(DeleteItemByNameImage request)
{
var pathInfo = PathInfo.Parse(Request.PathInfo);
var type = pathInfo.GetArgumentValue<string>(0);
var item = GetItemByName(request.Name, type, _libraryManager);
var task = item.DeleteImage(request.Type, request.Index ?? 0);
Task.WaitAll(task);
}
/// <summary>
/// Posts the specified request.
/// </summary>
@ -628,22 +458,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
/// <summary>
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
public void Post(UpdateItemByNameImageIndex request)
{
var pathInfo = PathInfo.Parse(Request.PathInfo);
var type = pathInfo.GetArgumentValue<string>(0);
var item = GetItemByName(request.Name, type, _libraryManager);
var task = UpdateItemIndex(item, request.Type, request.Index, request.NewIndex);
Task.WaitAll(task);
}
/// <summary>
/// Updates the index of the item.
/// </summary>

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@ -28,76 +27,17 @@ namespace MediaBrowser.Api
public string Id { get; set; }
}
[Route("/Artists/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for an artist")]
public class RefreshArtist : BaseRefreshRequest
{
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/Genres/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a genre")]
public class RefreshGenre : BaseRefreshRequest
{
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/MusicGenres/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a music genre")]
public class RefreshMusicGenre : BaseRefreshRequest
{
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/GameGenres/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a game genre")]
public class RefreshGameGenre : BaseRefreshRequest
{
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/Persons/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a person")]
public class RefreshPerson : BaseRefreshRequest
{
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
[Route("/Studios/{Name}/Refresh", "POST")]
[Api(Description = "Refreshes metadata for a studio")]
public class RefreshStudio : BaseRefreshRequest
{
[ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Name { get; set; }
}
public class ItemRefreshService : BaseApiService
{
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
public ItemRefreshService(ILibraryManager libraryManager, IDtoService dtoService)
public ItemRefreshService(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
_dtoService = dtoService;
}
public void Post(RefreshArtist request)
private async Task RefreshArtist(RefreshItem request, MusicArtist item)
{
var task = RefreshArtist(request);
Task.WaitAll(task);
}
private async Task RefreshArtist(RefreshArtist request)
{
var item = GetArtist(request.Name, _libraryManager);
var cancellationToken = CancellationToken.None;
var albums = _libraryManager.RootFolder
@ -127,118 +67,15 @@ namespace MediaBrowser.Api
}
}
public void Post(RefreshGenre request)
{
var task = RefreshGenre(request);
Task.WaitAll(task);
}
private async Task RefreshGenre(RefreshGenre request)
{
var item = GetGenre(request.Name, _libraryManager);
try
{
await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
}
public void Post(RefreshMusicGenre request)
{
var task = RefreshMusicGenre(request);
Task.WaitAll(task);
}
private async Task RefreshMusicGenre(RefreshMusicGenre request)
{
var item = GetMusicGenre(request.Name, _libraryManager);
try
{
await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
}
public void Post(RefreshGameGenre request)
{
var task = RefreshGameGenre(request);
Task.WaitAll(task);
}
private async Task RefreshGameGenre(RefreshGameGenre request)
{
var item = GetGameGenre(request.Name, _libraryManager);
try
{
await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
}
public void Post(RefreshPerson request)
{
var task = RefreshPerson(request);
Task.WaitAll(task);
}
private async Task RefreshPerson(RefreshPerson request)
{
var item = GetPerson(request.Name, _libraryManager);
try
{
await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
}
public void Post(RefreshStudio request)
{
var task = RefreshStudio(request);
Task.WaitAll(task);
}
private async Task RefreshStudio(RefreshStudio request)
{
var item = GetStudio(request.Name, _libraryManager);
try
{
await item.RefreshMetadata(GetRefreshOptions(request), CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.ErrorException("Error refreshing library", ex);
}
}
/// <summary>
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
public void Post(RefreshItem request)
{
var task = RefreshItem(request);
var item = _libraryManager.GetItemById(request.Id);
var task = item is MusicArtist ? RefreshArtist(request, (MusicArtist)item) : RefreshItem(request, item);
Task.WaitAll(task);
}
@ -248,10 +85,8 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task.</returns>
private async Task RefreshItem(RefreshItem request)
private async Task RefreshItem(RefreshItem request, BaseItem item)
{
var item = _libraryManager.GetItemById(request.Id);
var options = GetRefreshOptions(request);
try

@ -1,9 +1,7 @@
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
using ServiceStack;
using System;
@ -13,73 +11,20 @@ using System.Threading.Tasks;
namespace MediaBrowser.Api
{
[Route("/Items/{ItemId}", "POST")]
[Api(("Updates an item"))]
[Route("/Items/{ItemId}", "POST", Summary = "Updates an item")]
public class UpdateItem : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string ItemId { get; set; }
}
[Route("/Artists/{ArtistName}", "POST")]
[Api(("Updates an artist"))]
public class UpdateArtist : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "ArtistName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string ArtistName { get; set; }
}
[Route("/Studios/{StudioName}", "POST")]
[Api(("Updates a studio"))]
public class UpdateStudio : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "StudioName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string StudioName { get; set; }
}
[Route("/Persons/{PersonName}", "POST")]
[Api(("Updates a person"))]
public class UpdatePerson : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "PersonName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string PersonName { get; set; }
}
[Route("/MusicGenres/{GenreName}", "POST")]
[Api(("Updates a music genre"))]
public class UpdateMusicGenre : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "GenreName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string GenreName { get; set; }
}
[Route("/GameGenres/{GenreName}", "POST")]
[Api(("Updates a game genre"))]
public class UpdateGameGenre : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "GenreName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string GenreName { get; set; }
}
[Route("/Genres/{GenreName}", "POST")]
[Api(("Updates a genre"))]
public class UpdateGenre : BaseItemDto, IReturnVoid
{
[ApiMember(Name = "GenreName", Description = "The name of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string GenreName { get; set; }
}
public class ItemUpdateService : BaseApiService
{
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
private readonly ILiveTvManager _liveTv;
public ItemUpdateService(ILibraryManager libraryManager, IDtoService dtoService, ILiveTvManager liveTv)
public ItemUpdateService(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
_dtoService = dtoService;
_liveTv = liveTv;
}
public void Post(UpdateItem request)
@ -94,120 +39,29 @@ namespace MediaBrowser.Api
var item = _libraryManager.GetItemById(request.ItemId);
var newLockData = request.LockData ?? false;
var dontFetchMetaChanged = item.IsLocked != newLockData;
var isLockedChanged = item.IsLocked != newLockData;
UpdateItem(request, item);
if (isLockedChanged && item.IsLocked)
{
item.IsUnidentified = false;
}
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
if (dontFetchMetaChanged && item.IsFolder)
if (isLockedChanged && item.IsFolder)
{
var folder = (Folder)item;
foreach (var child in folder.RecursiveChildren.ToList())
{
child.DontFetchMeta = newLockData;
child.IsLocked = newLockData;
await child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
}
}
public void Post(UpdatePerson request)
{
var task = UpdateItem(request);
Task.WaitAll(task);
}
private async Task UpdateItem(UpdatePerson request)
{
var item = GetPerson(request.PersonName, _libraryManager);
UpdateItem(request, item);
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
public void Post(UpdateArtist request)
{
var task = UpdateItem(request);
Task.WaitAll(task);
}
private async Task UpdateItem(UpdateArtist request)
{
var item = GetArtist(request.ArtistName, _libraryManager);
UpdateItem(request, item);
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
public void Post(UpdateStudio request)
{
var task = UpdateItem(request);
Task.WaitAll(task);
}
private async Task UpdateItem(UpdateStudio request)
{
var item = GetStudio(request.StudioName, _libraryManager);
UpdateItem(request, item);
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
public void Post(UpdateMusicGenre request)
{
var task = UpdateItem(request);
Task.WaitAll(task);
}
private async Task UpdateItem(UpdateMusicGenre request)
{
var item = GetMusicGenre(request.GenreName, _libraryManager);
UpdateItem(request, item);
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
public void Post(UpdateGameGenre request)
{
var task = UpdateItem(request);
Task.WaitAll(task);
}
private async Task UpdateItem(UpdateGameGenre request)
{
var item = GetGameGenre(request.GenreName, _libraryManager);
UpdateItem(request, item);
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
public void Post(UpdateGenre request)
{
var task = UpdateItem(request);
Task.WaitAll(task);
}
private async Task UpdateItem(UpdateGenre request)
{
var item = GetGenre(request.GenreName, _libraryManager);
UpdateItem(request, item);
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
private void UpdateItem(BaseItemDto request, BaseItem item)
{
item.Name = request.Name;
@ -246,7 +100,7 @@ namespace MediaBrowser.Api
episode.AirsBeforeSeasonNumber = request.AirsBeforeSeasonNumber;
episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
}
var hasTags = item as IHasTags;
if (hasTags != null)
{
@ -295,14 +149,14 @@ namespace MediaBrowser.Api
{
hasDisplayOrder.DisplayOrder = request.DisplayOrder;
}
var hasAspectRatio = item as IHasAspectRatio;
if (hasAspectRatio != null)
{
hasAspectRatio.AspectRatio = request.AspectRatio;
}
item.DontFetchMeta = (request.LockData ?? false);
item.IsLocked = (request.LockData ?? false);
if (request.LockedFields != null)
{

@ -231,6 +231,14 @@ namespace MediaBrowser.Api.Library
}
[Route("/Library/Series/Updated", "POST")]
[Api(Description = "Reports that new episodes of a series have been added by an external source")]
public class PostUpdatedSeries : IReturnVoid
{
[ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string TvdbId { get; set; }
}
/// <summary>
/// Class LibraryService
/// </summary>

@ -10,8 +10,7 @@ using System.Linq;
namespace MediaBrowser.Api.Music
{
[Route("/Albums/{Id}/Similar", "GET")]
[Api(Description = "Finds albums similar to a given album.")]
[Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")]
public class GetSimilarAlbums : BaseGetSimilarItemsFromItem
{
}

@ -1,8 +1,10 @@
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Notifications;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -31,12 +33,38 @@ namespace MediaBrowser.Api
public string UserId { get; set; }
}
[Route("/Notifications/{UserId}", "POST", Summary = "Adds a notifications")]
public class AddUserNotification : IReturnVoid
[Route("/Notifications/Types", "GET", Summary = "Gets notification types")]
public class GetNotificationTypes : IReturn<List<NotificationTypeInfo>>
{
}
[Route("/Notifications/Services", "GET", Summary = "Gets notification types")]
public class GetNotificationServices : IReturn<List<NotificationServiceInfo>>
{
}
[Route("/Notifications", "POST", Summary = "Sends a notification to all admin users")]
public class AddAdminNotification : IReturnVoid
{
[ApiMember(Name = "Id", Description = "The Id of the new notification. If unspecified one will be provided.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Id { get; set; }
[ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Name { get; set; }
[ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Description { get; set; }
[ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string ImageUrl { get; set; }
[ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Url { get; set; }
[ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public NotificationLevel Level { get; set; }
}
[Route("/Notifications/{UserId}", "POST", Summary = "Sends a notification to a user")]
public class AddUserNotification : IReturnVoid
{
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string UserId { get; set; }
@ -77,11 +105,13 @@ namespace MediaBrowser.Api
{
private readonly INotificationsRepository _notificationsRepo;
private readonly INotificationManager _notificationManager;
private readonly IUserManager _userManager;
public NotificationsService(INotificationsRepository notificationsRepo, INotificationManager notificationManager)
public NotificationsService(INotificationsRepository notificationsRepo, INotificationManager notificationManager, IUserManager userManager)
{
_notificationsRepo = notificationsRepo;
_notificationManager = notificationManager;
_userManager = userManager;
}
public void Post(AddUserNotification request)
@ -91,11 +121,25 @@ namespace MediaBrowser.Api
Task.WaitAll(task);
}
public object Get(GetNotificationTypes request)
{
var result = _notificationManager.GetNotificationTypes().ToList();
return ToOptimizedResult(result);
}
public object Get(GetNotificationServices request)
{
var result = _notificationManager.GetNotificationServices().ToList();
return ToOptimizedResult(result);
}
public object Get(GetNotificationsSummary request)
{
var result = _notificationsRepo.GetNotificationsSummary(request.UserId);
return result;
return ToOptimizedResult(result);
}
private async Task AddNotification(AddUserNotification request)
@ -113,6 +157,29 @@ namespace MediaBrowser.Api
await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
}
public void Post(AddAdminNotification request)
{
// This endpoint really just exists as post of a real with sickbeard
var task = AddNotification(request);
Task.WaitAll(task);
}
private async Task AddNotification(AddAdminNotification request)
{
var notification = new NotificationRequest
{
Date = DateTime.UtcNow,
Description = request.Description,
Level = request.Level,
Name = request.Name,
Url = request.Url,
UserIds = _userManager.Users.Where(i => i.Configuration.IsAdministrator).Select(i => i.Id.ToString("N")).ToList()
};
await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);
}
public void Post(MarkRead request)
{
var task = MarkRead(request.Ids, request.UserId, true);

@ -301,9 +301,10 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>Task{ItemsResult}.</returns>
private ItemsResult GetItems(GetItems request)
{
var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId);
var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
var items = GetItemsToSerialize(request, user);
var items = GetItemsToSerialize(request, user, parentItem);
items = items.AsParallel();
@ -320,7 +321,7 @@ namespace MediaBrowser.Api.UserLibrary
items = items.AsEnumerable();
if (CollapseBoxSetItems(request))
if (CollapseBoxSetItems(request, parentItem))
{
items = _collectionManager.CollapseItemsWithinBoxSets(items, user);
}
@ -348,8 +349,14 @@ namespace MediaBrowser.Api.UserLibrary
};
}
private bool CollapseBoxSetItems(GetItems request)
private bool CollapseBoxSetItems(GetItems request, BaseItem parentItem)
{
// Could end up stuck in a loop like this
if (parentItem is BoxSet)
{
return false;
}
var param = request.CollapseBoxSetItems;
if (!param.HasValue)
@ -369,13 +376,14 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
/// <param name="request">The request.</param>
/// <param name="user">The user.</param>
/// <param name="parentItem">The parent item.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
/// <exception cref="System.InvalidOperationException"></exception>
private IEnumerable<BaseItem> GetItemsToSerialize(GetItems request, User user)
private IEnumerable<BaseItem> GetItemsToSerialize(GetItems request, User user, BaseItem parentItem)
{
var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder :
_libraryManager.GetItemById(request.ParentId);
parentItem;
// Default list type = children
IEnumerable<BaseItem> items;
@ -1382,7 +1390,7 @@ namespace MediaBrowser.Api.UserLibrary
{
return false;
}
return true;
}

@ -236,6 +236,10 @@ namespace MediaBrowser.Controller.Entities
{
return DontFetchMeta;
}
set
{
DontFetchMeta = value;
}
}
public bool IsUnidentified { get; set; }

@ -16,6 +16,9 @@ namespace MediaBrowser.Controller.Library
public BaseItemInfo MediaInfo { get; set; }
public string MediaSourceId { get; set; }
public string DeviceName { get; set; }
public string ClientName { get; set; }
public PlaybackProgressEventArgs()
{
Users = new List<User>();

@ -168,7 +168,9 @@
<Compile Include="Net\IRestfulService.cs" />
<Compile Include="News\INewsService.cs" />
<Compile Include="Notifications\INotificationManager.cs" />
<Compile Include="Notifications\INotificationService.cs" />
<Compile Include="Notifications\INotificationsRepository.cs" />
<Compile Include="Notifications\INotificationTypeFactory.cs" />
<Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
<Compile Include="Notifications\UserNotification.cs" />
<Compile Include="Persistence\IFileOrganizationRepository.cs" />

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Notifications;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -20,30 +19,19 @@ namespace MediaBrowser.Controller.Notifications
/// Adds the parts.
/// </summary>
/// <param name="services">The services.</param>
void AddParts(IEnumerable<INotificationService> services);
}
/// <param name="notificationTypeFactories">The notification type factories.</param>
void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories);
public interface INotificationService
{
/// <summary>
/// Gets the name.
/// Gets the notification types.
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Sends the notification.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendNotification(UserNotification request, CancellationToken cancellationToken);
/// <returns>IEnumerable{NotificationTypeInfo}.</returns>
IEnumerable<NotificationTypeInfo> GetNotificationTypes();
/// <summary>
/// Determines whether [is enabled for user] [the specified user identifier].
/// Gets the notification services.
/// </summary>
/// <param name="user">The user.</param>
/// <returns><c>true</c> if [is enabled for user] [the specified user identifier]; otherwise, <c>false</c>.</returns>
bool IsEnabledForUser(User user);
/// <returns>IEnumerable{NotificationServiceInfo}.</returns>
IEnumerable<NotificationServiceInfo> GetNotificationServices();
}
}

@ -0,0 +1,30 @@
using MediaBrowser.Controller.Entities;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Notifications
{
public interface INotificationService
{
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Sends the notification.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SendNotification(UserNotification request, CancellationToken cancellationToken);
/// <summary>
/// Determines whether [is enabled for user] [the specified user identifier].
/// </summary>
/// <param name="user">The user.</param>
/// <returns><c>true</c> if [is enabled for user] [the specified user identifier]; otherwise, <c>false</c>.</returns>
bool IsEnabledForUser(User user);
}
}

@ -0,0 +1,14 @@
using MediaBrowser.Model.Notifications;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Notifications
{
public interface INotificationTypeFactory
{
/// <summary>
/// Gets the notification types.
/// </summary>
/// <returns>IEnumerable{NotificationTypeInfo}.</returns>
IEnumerable<NotificationTypeInfo> GetNotificationTypes();
}
}

@ -429,7 +429,7 @@ namespace MediaBrowser.Controller.Providers
if (!string.IsNullOrWhiteSpace(val))
{
item.DontFetchMeta = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
break;
}

@ -1,8 +1,15 @@
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Dlna.PlayTo;
using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Logging;
using System;
@ -15,19 +22,39 @@ namespace MediaBrowser.Dlna.Main
{
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly IApplicationHost _appHost;
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _network;
private PlayToManager _manager;
private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient;
private readonly IItemRepository _itemRepo;
private readonly ILibraryManager _libraryManager;
private readonly INetworkManager _networkManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
private SsdpHandler _ssdpHandler;
private readonly List<Guid> _registeredServerIds = new List<Guid>();
private bool _dlnaServerStarted;
public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IDtoService dtoService, IImageProcessor imageProcessor)
{
_config = config;
_appHost = appHost;
_network = network;
_sessionManager = sessionManager;
_httpClient = httpClient;
_itemRepo = itemRepo;
_libraryManager = libraryManager;
_networkManager = networkManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_logger = logManager.GetLogger("Dlna");
}
@ -46,16 +73,27 @@ namespace MediaBrowser.Dlna.Main
private void ReloadComponents()
{
var isStarted = _dlnaServerStarted;
var isServerStarted = _dlnaServerStarted;
if (_config.Configuration.DlnaOptions.EnableServer && !isStarted)
if (_config.Configuration.DlnaOptions.EnableServer && !isServerStarted)
{
StartDlnaServer();
}
else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted)
else if (!_config.Configuration.DlnaOptions.EnableServer && isServerStarted)
{
DisposeDlnaServer();
}
var isPlayToStarted = _manager != null;
if (_config.Configuration.DlnaOptions.EnablePlayTo && !isPlayToStarted)
{
StartPlayToManager();
}
else if (!_config.Configuration.DlnaOptions.EnablePlayTo && isPlayToStarted)
{
DisposePlayToManager();
}
}
private void StartSsdpHandler()
@ -145,9 +183,59 @@ namespace MediaBrowser.Dlna.Main
);
}
private readonly object _syncLock = new object();
private void StartPlayToManager()
{
lock (_syncLock)
{
try
{
_manager = new PlayToManager(_logger,
_config,
_sessionManager,
_httpClient,
_itemRepo,
_libraryManager,
_networkManager,
_userManager,
_dlnaManager,
_appHost,
_dtoService,
_imageProcessor,
_ssdpHandler);
_manager.Start();
}
catch (Exception ex)
{
_logger.ErrorException("Error starting PlayTo manager", ex);
}
}
}
private void DisposePlayToManager()
{
lock (_syncLock)
{
if (_manager != null)
{
try
{
_manager.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing PlayTo manager", ex);
}
_manager = null;
}
}
}
public void Dispose()
{
DisposeDlnaServer();
DisposePlayToManager();
DisposeSsdpHandler();
}

@ -70,7 +70,6 @@
</Compile>
<Compile Include="PlayTo\PlaylistItemFactory.cs" />
<Compile Include="PlayTo\PlayToManager.cs" />
<Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
<Compile Include="Common\ServiceAction.cs" />
<Compile Include="Profiles\Foobar2000Profile.cs" />
<Compile Include="Profiles\Windows81Profile.cs" />

@ -409,30 +409,30 @@ namespace MediaBrowser.Dlna.PlayTo
UpdateMediaInfo(currentObject, transportState.Value);
}
}
}
}
catch (Exception ex)
{
_logger.ErrorException("Error updating device info", ex);
}
if (_disposed)
return;
if (_disposed)
return;
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if (TransportState == TRANSPORTSTATE.STOPPED)
{
_successiveStopCount++;
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if (transportState.Value == TRANSPORTSTATE.STOPPED)
{
_successiveStopCount++;
if (_successiveStopCount >= 10)
{
RestartTimerInactive();
if (_successiveStopCount >= 10)
{
RestartTimerInactive();
}
}
else
{
_successiveStopCount = 0;
RestartTimer();
}
}
}
else
catch (Exception ex)
{
_successiveStopCount = 0;
RestartTimer();
_logger.ErrorException("Error updating device info", ex);
}
}

@ -9,6 +9,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
using MediaBrowser.Dlna.Didl;
using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@ -38,6 +39,8 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
private readonly SsdpHandler _ssdpHandler;
public bool SupportsMediaRemoteControl
{
get { return true; }
@ -51,7 +54,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor)
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor, SsdpHandler ssdpHandler)
{
_session = session;
_itemRepository = itemRepository;
@ -63,6 +66,7 @@ namespace MediaBrowser.Dlna.PlayTo
_appHost = appHost;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_ssdpHandler = ssdpHandler;
_logger = logger;
}
@ -74,7 +78,35 @@ namespace MediaBrowser.Dlna.PlayTo
_device.PlaybackStopped += _device_PlaybackStopped;
_device.Start();
_updateTimer = new Timer(updateTimer_Elapsed, null, 30000, 30000);
_ssdpHandler.MessageReceived += _SsdpHandler_MessageReceived;
}
async void _SsdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
{
string nts;
e.Headers.TryGetValue("NTS", out nts);
string usn;
if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
if (string.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
{
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1)
{
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
{
if (!_disposed)
{
await _sessionManager.ReportSessionEnded(_session.Id).ConfigureAwait(false);
}
}
}
}
}
async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
@ -165,34 +197,6 @@ namespace MediaBrowser.Dlna.PlayTo
};
}
#region Device EventHandlers & Update Timer
Timer _updateTimer;
/// <summary>
/// Handles the Elapsed event of the updateTimer control.
/// </summary>
/// <param name="state">The state.</param>
private async void updateTimer_Elapsed(object state)
{
if (!IsSessionActive)
{
_updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
try
{
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
await _sessionManager.ReportSessionEnded(_session.Id);
}
catch (Exception ex)
{
_logger.ErrorException("Error in ReportSessionEnded", ex);
}
}
}
#endregion
#region SendCommands
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
@ -571,8 +575,8 @@ namespace MediaBrowser.Dlna.PlayTo
_device.PlaybackStart -= _device_PlaybackStart;
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
_ssdpHandler.MessageReceived -= _SsdpHandler_MessageReceived;
_updateTimer.Dispose();
_device.Dispose();
}
}

@ -1,5 +1,4 @@
using System.Text;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -12,12 +11,12 @@ using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -30,7 +29,6 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient;
private readonly CancellationTokenSource _tokenSource;
private ConcurrentDictionary<string, DateTime> _locations;
private readonly IItemRepository _itemRepository;
private readonly ILibraryManager _libraryManager;
@ -42,9 +40,10 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor)
private readonly SsdpHandler _ssdpHandler;
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor, SsdpHandler ssdpHandler)
{
_locations = new ConcurrentDictionary<string, DateTime>();
_tokenSource = new CancellationTokenSource();
_logger = logger;
@ -58,13 +57,12 @@ namespace MediaBrowser.Dlna.PlayTo
_appHost = appHost;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_ssdpHandler = ssdpHandler;
_config = config;
}
public void Start()
{
_locations = new ConcurrentDictionary<string, DateTime>();
foreach (var network in GetNetworkInterfaces())
{
_logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
@ -121,7 +119,9 @@ namespace MediaBrowser.Dlna.PlayTo
{
var socket = GetMulticastSocket(networkInterfaceIndex);
socket.Bind(new IPEndPoint(localIp, 1900));
var endPoint = new IPEndPoint(localIp, 1900);
socket.Bind(endPoint);
_logger.Info("Creating SSDP listener");
@ -135,9 +135,9 @@ namespace MediaBrowser.Dlna.PlayTo
if (receivedBytes > 0)
{
var headers = SsdpHelper.ParseSsdpResponse(receiveBuffer);
var args = SsdpHelper.ParseSsdpResponse(receiveBuffer, endPoint);
TryCreateController(headers);
TryCreateController(args);
}
}
@ -154,11 +154,47 @@ namespace MediaBrowser.Dlna.PlayTo
}, _tokenSource.Token, TaskCreationOptions.LongRunning);
}
private void TryCreateController(IDictionary<string, string> headers)
private void TryCreateController(SsdpMessageEventArgs args)
{
string nts;
args.Headers.TryGetValue("NTS", out nts);
string usn;
if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
// Don't create a new controller when a device is indicating it's shutting down
if (string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
{
return;
}
// It has to report that it's a media renderer
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{
return;
}
// Need to be able to download device description
string location;
if (!args.Headers.TryGetValue("Location", out location) ||
string.IsNullOrEmpty(location))
{
return;
}
if (!headers.TryGetValue("Location", out location))
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
var headerText = string.Join(",", headerTexts.ToArray());
_logger.Debug("{0} PlayTo message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText);
}
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
{
return;
}
@ -230,12 +266,9 @@ namespace MediaBrowser.Dlna.PlayTo
/// <returns></returns>
private async Task CreateController(Uri uri)
{
if (!IsUriValid(uri))
return;
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
if (device != null && device.RendererCommands != null && !_sessionManager.Sessions.Any(s => string.Equals(s.DeviceId, device.Properties.UUID) && s.IsActive))
if (device != null && device.RendererCommands != null)
{
var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null)
.ConfigureAwait(false);
@ -244,7 +277,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null)
{
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService, _imageProcessor);
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService, _imageProcessor, _ssdpHandler);
controller.Init(device);
@ -271,33 +304,6 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
/// <summary>
/// Determines if the Uri is valid for further inspection or not.
/// (the limit for reinspection is 5 minutes)
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>Returns <b>True</b> if the Uri is valid for further inspection</returns>
private bool IsUriValid(Uri uri)
{
if (uri == null)
return false;
if (!_locations.ContainsKey(uri.OriginalString))
{
_locations.AddOrUpdate(uri.OriginalString, DateTime.UtcNow, (key, existingVal) => existingVal);
return true;
}
var time = _locations[uri.OriginalString];
if ((DateTime.UtcNow - time).TotalMinutes <= 5)
{
return false;
}
return _locations.TryUpdate(uri.OriginalString, DateTime.UtcNow, time);
}
public void Dispose()
{
if (!_disposed)

@ -1,127 +0,0 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using System;
namespace MediaBrowser.Dlna.PlayTo
{
public class PlayToServerEntryPoint : IServerEntryPoint
{
private PlayToManager _manager;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient;
private readonly IItemRepository _itemRepo;
private readonly ILibraryManager _libraryManager;
private readonly INetworkManager _networkManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
private readonly IServerApplicationHost _appHost;
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor)
{
_config = config;
_sessionManager = sessionManager;
_httpClient = httpClient;
_itemRepo = itemRepo;
_libraryManager = libraryManager;
_networkManager = networkManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
_appHost = appHost;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_logger = logManager.GetLogger("PlayTo");
}
public void Run()
{
_config.ConfigurationUpdated += ConfigurationUpdated;
ReloadPlayToManager();
}
void ConfigurationUpdated(object sender, EventArgs e)
{
ReloadPlayToManager();
}
private void ReloadPlayToManager()
{
var isStarted = _manager != null;
if (_config.Configuration.DlnaOptions.EnablePlayTo && !isStarted)
{
StartPlayToManager();
}
else if (!_config.Configuration.DlnaOptions.EnablePlayTo && isStarted)
{
DisposePlayToManager();
}
}
private readonly object _syncLock = new object();
private void StartPlayToManager()
{
lock (_syncLock)
{
try
{
_manager = new PlayToManager(_logger,
_config,
_sessionManager,
_httpClient,
_itemRepo,
_libraryManager,
_networkManager,
_userManager,
_dlnaManager,
_appHost,
_dtoService,
_imageProcessor);
_manager.Start();
}
catch (Exception ex)
{
_logger.ErrorException("Error starting PlayTo manager", ex);
}
}
}
private void DisposePlayToManager()
{
lock (_syncLock)
{
if (_manager != null)
{
try
{
_manager.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing PlayTo manager", ex);
}
_manager = null;
}
}
}
public void Dispose()
{
DisposePlayToManager();
}
}
}

@ -1,14 +1,13 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Dlna.Server;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace MediaBrowser.Dlna.Ssdp
@ -29,13 +28,13 @@ namespace MediaBrowser.Dlna.Ssdp
private Timer _queueTimer;
private Timer _notificationTimer;
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
private bool _isDisposed;
private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature)
{
_logger = logger;
@ -51,6 +50,8 @@ namespace MediaBrowser.Dlna.Ssdp
{
RespondToSearch(args.EndPoint, args.Headers["st"]);
}
EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
}
public IEnumerable<UpnpDevice> RegisteredDevices
@ -60,7 +61,7 @@ namespace MediaBrowser.Dlna.Ssdp
return _devices.Values.SelectMany(i => i).ToList();
}
}
public void Start()
{
_socket = CreateMulticastSocket();
@ -79,8 +80,8 @@ namespace MediaBrowser.Dlna.Ssdp
SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
}
public void SendDatagram(string header,
Dictionary<string, string> values,
public void SendDatagram(string header,
Dictionary<string, string> values,
IPEndPoint endpoint,
IPAddress localAddress,
int sendCount = 1)
@ -105,7 +106,7 @@ namespace MediaBrowser.Dlna.Ssdp
{
foreach (var d in RegisteredDevices)
{
if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
{
SendDatagram(header, values, endpoint, d.Address);
@ -141,7 +142,7 @@ namespace MediaBrowser.Dlna.Ssdp
_logger.Info("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
}
}
}
}
private readonly object _queueTimerSyncLock = new object();
@ -223,43 +224,17 @@ namespace MediaBrowser.Dlna.Ssdp
var receivedCount = _socket.EndReceiveFrom(result, ref endpoint);
var received = (byte[])result.AsyncState;
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
_logger.Debug("{0} - SSDP Received a datagram", endpoint);
}
var args = SsdpHelper.ParseSsdpResponse(received, (IPEndPoint)endpoint);
using (var reader = new StreamReader(new MemoryStream(received), Encoding.ASCII))
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
var proto = (reader.ReadLine() ?? string.Empty).Trim();
var method = proto.Split(new[] { ' ' }, 2)[0];
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
line = line.Trim();
if (string.IsNullOrEmpty(line))
{
break;
}
var parts = line.Split(new[] { ':' }, 2);
if (parts.Length >= 2)
{
headers[parts[0]] = parts[1].Trim();
}
}
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
var headerText = string.Join(",", headerTexts.ToArray());
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
_logger.Debug("{0} - Datagram method: {1}", endpoint, method);
}
OnMessageReceived(new SsdpMessageEventArgs
{
Method = method,
Headers = headers,
EndPoint = (IPEndPoint)endpoint
});
_logger.Debug("{0} message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText);
}
OnMessageReceived(args);
}
catch (Exception ex)
{

@ -1,40 +1,45 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
namespace MediaBrowser.Dlna.Ssdp
{
public class SsdpHelper
{
/// <summary>
/// Parses the socket response into a location Uri for the DeviceDescription.xml.
/// </summary>
/// <param name="data">The data.</param>
/// <returns></returns>
public static Dictionary<string,string> ParseSsdpResponse(byte[] data)
public static SsdpMessageEventArgs ParseSsdpResponse(byte[] data, IPEndPoint endpoint)
{
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var reader = new StreamReader(new MemoryStream(data), Encoding.ASCII))
using (var ms = new MemoryStream(data))
{
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
using (var reader = new StreamReader(ms, Encoding.ASCII))
{
line = line.Trim();
if (string.IsNullOrEmpty(line))
var proto = (reader.ReadLine() ?? string.Empty).Trim();
var method = proto.Split(new[] { ' ' }, 2)[0];
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
break;
line = line.Trim();
if (string.IsNullOrEmpty(line))
{
break;
}
var parts = line.Split(new[] { ':' }, 2);
if (parts.Length >= 2)
{
headers[parts[0]] = parts[1].Trim();
}
}
var parts = line.Split(new[] { ':' }, 2);
if (parts.Length == 2)
return new SsdpMessageEventArgs
{
headers[parts[0]] = parts[1].Trim();
}
Method = method,
Headers = headers,
EndPoint = endpoint
};
}
}
return headers;
}
}
}

@ -31,7 +31,7 @@ namespace MediaBrowser.Dlna.Ssdp
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
values["HOST"] = "239.255.255.250:1900";
values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/0.6.9.1";
values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
values["ST"] = deviceSearchType;
values["MAN"] = "\"ssdp:discover\"";
values["MX"] = mx;

@ -80,13 +80,7 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -92,6 +92,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\MetadataPlugin.cs">
<Link>Configuration\MetadataPlugin.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\NotificationOptions.cs">
<Link>Configuration\NotificationOptions.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs">
<Link>Configuration\ServerConfiguration.cs</Link>
</Compile>

@ -79,6 +79,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\MetadataPlugin.cs">
<Link>Configuration\MetadataPlugin.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\NotificationOptions.cs">
<Link>Configuration\NotificationOptions.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\ServerConfiguration.cs">
<Link>Configuration\ServerConfiguration.cs</Link>
</Compile>

@ -0,0 +1,149 @@
using System;
using System.Linq;
namespace MediaBrowser.Model.Configuration
{
public class NotificationOptions
{
public NotificationOption[] Options { get; set; }
public NotificationOptions()
{
Options = new[]
{
new NotificationOption
{
Type = NotificationType.TaskFailed.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.ServerRestartRequired.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.ApplicationUpdateAvailable.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.PluginUpdateInstalled.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.PluginUninstalled.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.InstallationFailed.ToString(),
Enabled = true
},
new NotificationOption
{
Type = NotificationType.PluginInstalled.ToString(),
Enabled = true
}
};
}
public NotificationOption GetOptions(string type)
{
return Options.FirstOrDefault(i => string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase));
}
public bool IsEnabled(string type)
{
var opt = GetOptions(type);
return opt != null && opt.Enabled;
}
public bool IsServiceEnabled(string service, string notificationType)
{
var opt = GetOptions(notificationType);
return opt == null ||
!opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToMonitorUser(string type, string userId)
{
var opt = GetOptions(type);
return opt != null && opt.Enabled &&
!opt.DisabledMonitorUsers.Contains(userId, StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToSendToUser(string type, string userId)
{
var opt = GetOptions(type);
return opt != null && opt.Enabled &&
!opt.DisabledSendToUsers.Contains(userId, StringComparer.OrdinalIgnoreCase);
}
}
public class NotificationOption
{
public string Type { get; set; }
/// <summary>
/// User Ids to not monitor (it's opt out)
/// </summary>
public string[] DisabledMonitorUsers { get; set; }
/// <summary>
/// User Ids to not send to (it's opt out)
/// </summary>
public string[] DisabledSendToUsers { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="NotificationOption"/> is enabled.
/// </summary>
/// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the title format string.
/// </summary>
/// <value>The title format string.</value>
public string Title { get; set; }
/// <summary>
/// Gets or sets the disabled services.
/// </summary>
/// <value>The disabled services.</value>
public string[] DisabledServices { get; set; }
public NotificationOption()
{
DisabledServices = new string[] { };
DisabledMonitorUsers = new string[] { };
DisabledSendToUsers = new string[] { };
}
}
public enum NotificationType
{
TaskFailed,
InstallationFailed,
NewLibraryContent,
ServerRestartRequired,
ApplicationUpdateAvailable,
ApplicationUpdateInstalled,
PluginInstalled,
PluginUpdateInstalled,
PluginUninstalled,
AudioPlayback,
GamePlayback,
VideoPlayback
}
}

@ -319,22 +319,4 @@ namespace MediaBrowser.Model.Configuration
public string From { get; set; }
public string To { get; set; }
}
public class NotificationOptions
{
public bool SendOnUpdates { get; set; }
public bool SendOnVideoPlayback { get; set; }
public bool SendOnAudioPlayback { get; set; }
public bool SendOnGamePlayback { get; set; }
public bool SendOnFailedTasks { get; set; }
public bool SendOnNewLibraryContent { get; set; }
public bool SendOnServerRestartRequired { get; set; }
public NotificationOptions()
{
SendOnUpdates = true;
SendOnFailedTasks = true;
SendOnServerRestartRequired = true;
}
}
}

@ -1,9 +1,8 @@
using System;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
{

@ -113,6 +113,8 @@ namespace MediaBrowser.Model.Dto
/// <value>The critic rating summary.</value>
public string CriticRatingSummary { get; set; }
public List<string> MultiPartGameFiles { get; set; }
/// <summary>
/// Gets or sets the path.
/// </summary>

@ -64,6 +64,7 @@
<Compile Include="Configuration\DlnaOptions.cs" />
<Compile Include="Configuration\MetadataPlugin.cs" />
<Compile Include="Configuration\MetadataOptions.cs" />
<Compile Include="Configuration\NotificationOptions.cs" />
<Compile Include="Configuration\ServerConfiguration.cs" />
<Compile Include="Dlna\CodecProfile.cs" />
<Compile Include="Dlna\ConditionProcessor.cs" />

@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Notifications
public string Description { get; set; }
public string Url { get; set; }
public NotificationLevel Level { get; set; }
public Notification()
@ -40,11 +40,48 @@ namespace MediaBrowser.Model.Notifications
public List<string> UserIds { get; set; }
public DateTime Date { get; set; }
/// <summary>
/// The corresponding type name used in configuration. Not for display.
/// </summary>
public string NotificationType { get; set; }
public Dictionary<string, string> Variables { get; set; }
public NotificationRequest()
{
UserIds = new List<string>();
Date = DateTime.UtcNow;
Variables = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
public class NotificationTypeInfo
{
public string Type { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public string Category { get; set; }
public bool IsBasedOnUserEvent { get; set; }
public string DefaultTitle { get; set; }
public List<string> Variables { get; set; }
public NotificationTypeInfo()
{
Variables = new List<string>();
}
}
public class NotificationServiceInfo
{
public string Name { get; set; }
public string Id { get; set; }
}
}

@ -183,7 +183,7 @@ namespace MediaBrowser.Providers.Manager
{
target.ForcedSortName = source.ForcedSortName;
target.LockedFields = source.LockedFields;
target.DontFetchMeta = source.DontFetchMeta;
target.IsLocked = source.IsLocked;
target.DisplayMediaType = source.DisplayMediaType;
var sourceHasLanguageSettings = source as IHasPreferredMetadataLanguage;

@ -56,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.Collections
Parent = parentFolder,
DisplayMediaType = "Collection",
Path = path,
DontFetchMeta = options.IsLocked,
IsLocked = options.IsLocked,
ProviderIds = options.ProviderIds
};

@ -313,6 +313,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.Players = item.PlayersSupported;
dto.GameSystem = item.GameSystem;
dto.MultiPartGameFiles = item.MultiPartGameFiles;
}
private void SetGameSystemProperties(BaseItemDto dto, GameSystem item)

@ -9,14 +9,17 @@ 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.Logging;
using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Updates;
namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
{
@ -52,7 +55,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
public void Run()
{
_installationManager.PackageInstallationCompleted += _installationManager_PackageInstallationCompleted;
_installationManager.PluginInstalled += _installationManager_PluginInstalled;
_installationManager.PluginUpdated += _installationManager_PluginUpdated;
_installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed;
_installationManager.PluginUninstalled += _installationManager_PluginUninstalled;
@ -65,98 +69,192 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
}
async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
async void _installationManager_PluginUpdated(object sender, GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> e)
{
// This notification is for users who can't auto-update (aka running as service)
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate || !_config.Configuration.NotificationOptions.SendOnUpdates)
var type = NotificationType.PluginUpdateInstalled.ToString();
var installationInfo = e.Argument.Item1;
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
return;
}
UserIds = userIds,
Description = installationInfo.Description,
NotificationType = type
};
notification.Variables["Name"] = installationInfo.Name;
notification.Variables["Version"] = installationInfo.Version.ToString();
await SendNotification(notification).ConfigureAwait(false);
}
async void _installationManager_PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
{
var type = NotificationType.PluginInstalled.ToString();
var installationInfo = e.Argument;
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
UserIds = userIds,
Name = "A new version of Media Browser is available.",
Description = "Please see mediabrowser3.com for details."
Description = installationInfo.description,
NotificationType = type
};
notification.Variables["Name"] = installationInfo.name;
notification.Variables["Version"] = installationInfo.versionStr;
await SendNotification(notification).ConfigureAwait(false);
}
async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
{
if (!_appHost.HasPendingRestart || !_config.Configuration.NotificationOptions.SendOnUpdates)
// This notification is for users who can't auto-update (aka running as service)
if (!_appHost.HasUpdateAvailable || _appHost.CanSelfUpdate)
{
return;
}
var type = NotificationType.ApplicationUpdateAvailable.ToString();
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
UserIds = userIds,
Name = "Please restart Media Browser to finish updating"
Description = "Please see mediabrowser3.com for details.",
NotificationType = type
};
await SendNotification(notification).ConfigureAwait(false);
}
async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
{
if (!NotifyOnPlayback(e.MediaInfo.MediaType))
if (!_appHost.HasPendingRestart)
{
return;
}
var type = NotificationType.ServerRestartRequired.ToString();
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
var item = e.MediaInfo;
var notification = new NotificationRequest
{
UserIds = userIds,
NotificationType = type
};
var msgName = "playing " + item.Name;
await SendNotification(notification).ConfigureAwait(false);
}
async void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
var user = e.Users.FirstOrDefault();
if (user != null)
{
msgName = user.Name + " " + msgName;
}
var userIds = _userManager
.Users
.Where(i => NotifyOnPlayback(e.MediaInfo.MediaType, user, i))
.Select(i => i.Id.ToString("N"))
.ToList();
var item = e.MediaInfo;
var notification = new NotificationRequest
{
UserIds = userIds,
Name = msgName
UserIds = userIds
};
notification.Variables["ItemName"] = item.Name;
notification.Variables["UserName"] = user == null ? "Unknown user" : user.Name;
notification.Variables["AppName"] = e.ClientName;
notification.Variables["DeviceName"] = e.DeviceName;
await SendNotification(notification).ConfigureAwait(false);
}
private bool NotifyOnPlayback(string mediaType)
private bool NotifyOnPlayback(string mediaType, User playingUser, User notifiedUser)
{
if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
return _config.Configuration.NotificationOptions.SendOnAudioPlayback;
var type = NotificationType.AudioPlayback.ToString();
if (playingUser != null)
{
if (!_config.Configuration.NotificationOptions.IsEnabledToMonitorUser(
type, playingUser.Id.ToString("N")))
{
return false;
}
if (playingUser.Id == notifiedUser.Id)
{
return false;
}
}
return _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, notifiedUser.Id.ToString("N"));
}
if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase))
{
return _config.Configuration.NotificationOptions.SendOnGamePlayback;
var type = NotificationType.GamePlayback.ToString();
if (playingUser != null)
{
if (!_config.Configuration.NotificationOptions.IsEnabledToMonitorUser(
type, playingUser.Id.ToString("N")))
{
return false;
}
if (playingUser.Id == notifiedUser.Id)
{
return false;
}
}
return _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, notifiedUser.Id.ToString("N"));
}
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
return _config.Configuration.NotificationOptions.SendOnVideoPlayback;
var type = NotificationType.VideoPlayback.ToString();
if (playingUser != null)
{
if (!_config.Configuration.NotificationOptions.IsEnabledToMonitorUser(
type, playingUser.Id.ToString("N")))
{
return false;
}
if (playingUser.Id == notifiedUser.Id)
{
return false;
}
}
return _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, notifiedUser.Id.ToString("N"));
}
return false;
@ -164,12 +262,13 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
async void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
{
if (_config.Configuration.NotificationOptions.SendOnNewLibraryContent &&
e.Item.LocationType == LocationType.FileSystem)
if (e.Item.LocationType == LocationType.FileSystem)
{
var type = NotificationType.NewLibraryContent.ToString();
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
@ -178,23 +277,20 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
var notification = new NotificationRequest
{
UserIds = userIds,
Name = item.Name + " added to library."
NotificationType = type
};
notification.Variables["Name"] = item.Name;
await SendNotification(notification).ConfigureAwait(false);
}
}
async void _userManager_UserCreated(object sender, GenericEventArgs<User> e)
{
var userIds = _userManager
.Users
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
UserIds = userIds,
UserIds = new List<string> { e.Argument.Id.ToString("N") },
Name = "Welcome to Media Browser!",
Description = "Check back here for more notifications."
};
@ -206,68 +302,51 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
{
var result = e.Argument;
if (result.Status == TaskCompletionStatus.Failed &&
_config.Configuration.NotificationOptions.SendOnFailedTasks)
if (result.Status == TaskCompletionStatus.Failed)
{
var type = NotificationType.TaskFailed.ToString();
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
UserIds = userIds,
Name = result.Name + " failed",
Description = result.ErrorMessage,
Level = NotificationLevel.Error
Level = NotificationLevel.Error,
NotificationType = type
};
notification.Variables["Name"] = e.Argument.Name;
await SendNotification(notification).ConfigureAwait(false);
}
}
async void _installationManager_PluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
{
var type = NotificationType.PluginUninstalled.ToString();
var plugin = e.Argument;
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
UserIds = userIds,
Name = plugin.Name + " has been uninstalled"
};
await SendNotification(notification).ConfigureAwait(false);
}
async void _installationManager_PackageInstallationCompleted(object sender, InstallationEventArgs e)
{
if (!_config.Configuration.NotificationOptions.SendOnUpdates)
{
return;
}
var installationInfo = e.InstallationInfo;
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
var notification = new NotificationRequest
{
UserIds = userIds,
Name = installationInfo.Name + " " + installationInfo.Version + " was installed",
Description = e.PackageVersionInfo.description
NotificationType = type
};
notification.Variables["Name"] = plugin.Name;
notification.Variables["Version"] = plugin.Version.ToString();
await SendNotification(notification).ConfigureAwait(false);
}
@ -275,9 +354,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
{
var installationInfo = e.InstallationInfo;
var type = NotificationType.InstallationFailed.ToString();
var userIds = _userManager
.Users
.Where(i => i.Configuration.IsAdministrator)
.Where(i => i.Configuration.IsAdministrator && _config.Configuration.NotificationOptions.IsEnabledToSendToUser(type, i.Id.ToString("N")))
.Select(i => i.Id.ToString("N"))
.ToList();
@ -285,10 +366,13 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
{
UserIds = userIds,
Level = NotificationLevel.Error,
Name = installationInfo.Name + " " + installationInfo.Version + " installation failed",
Description = e.Exception.Message
Description = e.Exception.Message,
NotificationType = type
};
notification.Variables["Name"] = installationInfo.Name;
notification.Variables["Version"] = installationInfo.Version;
await SendNotification(notification).ConfigureAwait(false);
}
@ -306,7 +390,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
public void Dispose()
{
_installationManager.PackageInstallationCompleted -= _installationManager_PackageInstallationCompleted;
_installationManager.PluginInstalled -= _installationManager_PluginInstalled;
_installationManager.PluginUpdated -= _installationManager_PluginUpdated;
_installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed;
_installationManager.PluginUninstalled -= _installationManager_PluginUninstalled;

@ -46,7 +46,7 @@ namespace MediaBrowser.Server.Implementations.Library
// Make sure the item has a name
EnsureName(item, args);
item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
item.Parents.Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "Cuando se encuentra deshabilitado los clientes podr\u00edan mostrar una pantalla de inicio de sesi\u00f3n con una selecci\u00f3n visual de los usuarios.",
"OptionOtherApps": "Otras applicaciones",
"OptionMobileApps": "Apps m\u00f3viles",
"HeaderEnableNotificationForEvents": "Enviar notificaciones para los siguientes eventos:",
"OptionNotifyOnUpdates": "Cuando se encuentren disponibles actualizaciones",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Cuando las tareas programadas fallen",
"OptionNotifyOnNewLibraryContent": "Cuando se a\u00f1adan nuevos contenidos a la biblioteca",
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "Las notificaciones son enviadas a la bandeja de entrada del panel de control. Navegue el cat\u00e1logo de complementos para instalar opciones de notificaci\u00f3n adiconales.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -60,7 +60,7 @@
"LabelDisplayMissingEpisodesWithinSeasons": "Afficher les \u00e9pisodes manquants dans les saisons",
"LabelUnairedMissingEpisodesWithinSeasons": "Afficher les \u00e9pisodes non diffus\u00e9s dans les saisons",
"HeaderVideoPlaybackSettings": "Param\u00e8tres de lecture video",
"HeaderPlaybackSettings": "Playback Settings",
"HeaderPlaybackSettings": "Param\u00e8tres de lecture",
"LabelAudioLanguagePreference": "Param\u00e8tres de langue audio:",
"LabelSubtitleLanguagePreference": "Param\u00e8tres de langue de sous-titre",
"LabelDisplayForcedSubtitlesOnly": "Afficher seulement les sous-titres forc\u00e9s",
@ -529,7 +529,7 @@
"ButtonRetrieveKey": "obtenir la cl\u00e9",
"LabelSupporterKey": "Cl\u00e9 de supporteur (coller du courriel)",
"LabelSupporterKeyHelp": "Enter your supporter key to start enjoying additional benefits the community has developed for Media Browser.",
"MessageInvalidKey": "MB3 Key Missing or Invalid",
"MessageInvalidKey": "Cl\u00e9 MB3 manquante ou invalide",
"ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
"HeaderDisplaySettings": "Param\u00e8tres d'affichage",
"TabPlayTo": "Lire sur",
@ -542,24 +542,37 @@
"LabelDefaultUser": "Utilisateur par d\u00e9faut:",
"LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.",
"TitleDlna": "DLNA",
"HeaderServerSettings": "Server Settings",
"LabelWeatherDisplayLocation": "Weather display location:",
"LabelWeatherDisplayLocationHelp": "US zip code \/ City, State, Country \/ City, Country",
"LabelWeatherDisplayUnit": "Weather display unit:",
"HeaderServerSettings": "Param\u00e8tres du serveur",
"LabelWeatherDisplayLocation": "Emplacement de l'affichage de la m\u00e9t\u00e9o:",
"LabelWeatherDisplayLocationHelp": "Code ZIP US \/ Ville, \u00c9tat, Pays \/ Ville, Pays",
"LabelWeatherDisplayUnit": "Unit\u00e9 d'affichage de la m\u00e9t\u00e9o:",
"OptionCelsius": "Celsius",
"OptionFahrenheit": "Fahrenheit",
"HeaderRequireManualLogin": "Require manual username entry for:",
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderRequireManualLogin": "Exiger l'entr\u00e9e manuelle du nom d'utilisateur pour:",
"HeaderRequireManualLoginHelp": "Lorsque d\u00e9sactiv\u00e9, le clients auront un \u00e9cran de connexion avec une s\u00e9lection visuelle des utilisateurs.",
"OptionOtherApps": "Autres applications",
"OptionMobileApps": "Applications mobiles",
"HeaderNotificationList": "Cliquez sur une notification pour configurer ses options d'envois",
"NotificationOptionApplicationUpdateAvailable": "Mise \u00e0 jour d'application disponible",
"NotificationOptionApplicationUpdateInstalled": "Mise \u00e0 jour d'application mis \u00e0 jour",
"NotificationOptionPluginUpdateInstalled": "Mise \u00e0 jour de plugin install\u00e9e",
"NotificationOptionPluginInstalled": "Plugin install\u00e9",
"NotificationOptionPluginUninstalled": "Plugin d\u00e9sinstall\u00e9",
"NotificationOptionVideoPlayback": "Lecture vid\u00e9o",
"NotificationOptionAudioPlayback": "Lecture audio",
"NotificationOptionGamePlayback": "Lecture des jeux",
"NotificationOptionTaskFailed": "\u00c9chec de t\u00e2che programm\u00e9e",
"NotificationOptionInstallationFailed": "\u00c9chec d'installation",
"NotificationOptionNewLibraryContent": "Nouveau contenu ajout\u00e9",
"SendNotificationHelp": "Par d\u00e9faut, les notifications sont d\u00e9livr\u00e9es dans la bo\u00eete de r\u00e9ception du tableau de bord. Consultez le catalogue de plugins pour installer des options de notifications suppl\u00e9mentaires.",
"NotificationOptionServerRestartRequired": "Un red\u00e9marrage du serveur est requis",
"LabelNotificationEnabled": "Activer cette notification",
"LabelMonitorUsers": "Surveiller les activit\u00e9s de:",
"LabelSendNotificationToUsers": "Envoyer la notification \u00e0:",
"UsersNotNotifiedAboutSelfActivity": "Les utilisateurs ne seront pas notifi\u00e9s de leurs propres activit\u00e9s.",
"LabelUseNotificationServices": "Utiliser les services suivants:",
"CategoryUser": "Utilisateur",
"CategorySystem": "Syst\u00e8me",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "\u05db\u05d0\u05e9\u05e8 \u05de\u05d1\u05d5\u05d8\u05dc, \u05d9\u05d5\u05e6\u05d2 \u05dc\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05dc\u05d5\u05d7 \u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e2\u05dd \u05d1\u05d7\u05d9\u05e8\u05ea \u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd.",
"OptionOtherApps": "\u05ea\u05d5\u05db\u05e0\u05d5\u05ea \u05d0\u05d7\u05e8\u05d5\u05ea",
"OptionMobileApps": "\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d5\u05ea \u05dc\u05e0\u05d9\u05d9\u05d3",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -85,7 +85,7 @@
"MessagePleaseEnsureInternetMetadata": "\u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0442\u0435\u043d \u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0434\u0456 \u0436\u04af\u043a\u0442\u0435\u043f \u0430\u043b\u0443\u044b \u049b\u043e\u0441\u044b\u043b\u0493\u0430\u043d\u044b\u043d \u0442\u0435\u043a\u0441\u0435\u0440\u0456\u04a3\u0456\u0437.",
"TabSuggested": "\u04b0\u0441\u044b\u043d\u044b\u043b\u0493\u0430\u043d",
"TabLatest": "\u0415\u04a3 \u043a\u0435\u0439\u0456\u043d\u0433\u0456",
"TabUpcoming": "\u0410\u043b\u0434\u0430\u0493\u044b",
"TabUpcoming": "\u041a\u04af\u0442\u0456\u043b\u0433\u0435\u043d",
"TabShows": "\u0421\u0435\u0440\u0438\u0430\u043b\u0434\u0430\u0440",
"TabEpisodes": "\u042d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440",
"TabGenres": "\u0416\u0430\u043d\u0440\u043b\u0430\u0440",
@ -103,7 +103,7 @@
"OptionWriters": "\u0416\u0430\u0437\u0443\u0448\u044b\u043b\u0430\u0440",
"OptionProducers": "\u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u043b\u0435\u0440",
"HeaderResume": "\u0416\u0430\u043b\u0493\u0430\u0441\u0442\u044b\u0440\u0443",
"HeaderNextUp": "\u0416\u0430\u043b\u0493\u0430\u0441\u044b \u0431\u0430\u0440",
"HeaderNextUp": "\u0410\u043b\u0434\u0430\u0493\u044b",
"NoNextUpItemsMessage": "\u0415\u0448\u0442\u0435\u04a3\u0435 \u0442\u0430\u0431\u044b\u043b\u043c\u0430\u0434\u044b. \u041a\u04e9\u0440\u0441\u0435\u0442\u0456\u043c\u0434\u0435\u0440\u0456\u04a3\u0456\u0437\u0434\u0456 \u049b\u0430\u0440\u0430\u0439 \u0431\u0430\u0441\u0442\u0430\u04a3\u044b\u0437!",
"HeaderLatestEpisodes": "\u0415\u04a3 \u043a\u0435\u0439\u0456\u043d\u0433\u0456 \u044d\u043f\u0438\u0437\u043e\u0434\u0442\u0430\u0440",
"HeaderPersonTypes": "\u0410\u0434\u0430\u043c \u0442\u04af\u0440\u043b\u0435\u0440\u0456:",
@ -291,8 +291,8 @@
"OptionPrePaddingRequired": "\u0410\u043b\u0493\u0430 \u0448\u0435\u0433\u0456\u043d\u0456\u0441 \u0436\u0430\u0437\u0443 \u04af\u0448\u0456\u043d \u043a\u0435\u0440\u0435\u043a.",
"LabelPostPaddingMinutes": "\u0410\u0440\u0442\u049b\u0430 \u0448\u0435\u0433\u0456\u043d\u0456\u0441, \u043c\u0438\u043d:",
"OptionPostPaddingRequired": "\u0410\u0440\u0442\u049b\u0430 \u0448\u0435\u0433\u0456\u043d\u0456\u0441 \u0436\u0430\u0437\u0443 \u04af\u0448\u0456\u043d \u043a\u0435\u0440\u0435\u043a.",
"HeaderWhatsOnTV": "\u0410\u0493\u044b\u043c\u0434\u0430\u0493\u044b",
"HeaderUpcomingTV": "\u0410\u043b\u0434\u0430\u0493\u044b \u0422\u0414",
"HeaderWhatsOnTV": "\u042d\u0444\u0438\u0440\u0434\u0435",
"HeaderUpcomingTV": "\u041a\u04af\u0442\u0456\u043b\u0433\u0435\u043d \u0422\u0414",
"TabStatus": "\u041a\u04af\u0439",
"TabSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440",
"ButtonRefreshGuideData": "\u0422\u0414 \u043a\u0435\u0441\u0442\u0435\u0441\u0456 \u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0456\u043d \u0436\u0430\u04a3\u0430\u0440\u0442\u0443",
@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "\u0410\u0436\u044b\u0440\u0430\u0442\u044b\u043b\u0493\u0430\u043d\u0434\u0430, \u043a\u043b\u0438\u0435\u043d\u0442\u0442\u0435\u0440 \u043f\u0430\u0439\u0434\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440\u0434\u044b \u043a\u04e9\u0440\u043d\u0435\u043a\u0456 \u0442\u0430\u04a3\u0434\u0430\u0443\u044b \u0431\u0430\u0440 \u043a\u0456\u0440\u0443 \u044d\u043a\u0440\u0430\u043d\u044b\u043d \u043a\u04e9\u0440\u0441\u0435\u0442\u0435 \u0430\u043b\u0430\u0434\u044b.",
"OptionOtherApps": "\u0411\u0430\u0441\u049b\u0430 \u049b\u043e\u043b\u0434\u0430\u043d\u0431\u0430\u043b\u0430\u0440",
"OptionMobileApps": "\u04b0\u0442\u049b\u044b\u0440 \u049b\u043e\u043b\u0434\u0430\u043d\u0431\u0430\u043b\u0430\u0440",
"HeaderEnableNotificationForEvents": "\u041a\u0435\u043b\u0435\u0441\u0456 \u043e\u049b\u0438\u0493\u0430\u043b\u0430\u0440 \u04af\u0448\u0456\u043d \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u044b\u0440\u0443\u043b\u0430\u0440\u0434\u044b \u0436\u0456\u0431\u0435\u0440\u0443:",
"OptionNotifyOnUpdates": "\u0416\u0430\u04a3\u0430\u0440\u0442\u0443\u043b\u0430\u0440 \u049b\u043e\u043b \u0436\u0435\u0442\u0456\u043c\u0434\u0456 \u0431\u043e\u043b\u0493\u0430\u043d\u0434\u0430",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "\u0416\u043e\u0441\u043f\u0430\u0440\u043b\u0430\u0493\u0430\u043d \u0442\u0430\u043f\u0441\u044b\u0440\u043c\u0430\u043b\u0430\u0440 \u0430\u049b\u0430\u0443\u043b\u044b\u049b\u0442\u0430\u0440\u044b\u043d\u0434\u0430",
"OptionNotifyOnNewLibraryContent": "\u0422\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0493\u0430 \u0436\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u04af\u0441\u0442\u0435\u043b\u0433\u0435\u043d\u0434\u0435",
"SendNotificationHelp": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440 \u0431\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u0441\u044b\u043d\u0434\u0430\u0493\u044b \u043a\u0456\u0440\u0456\u0441 \u0436\u04d9\u0448\u0456\u0433\u0456\u043d\u0435 \u0436\u0435\u0442\u043a\u0456\u0437\u0456\u043b\u0435\u0434\u0456. \u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0434\u0456 \u043e\u0440\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0456\u043d \u0448\u0430\u0440\u043b\u0430\u04a3\u044b\u0437.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "\u0416\u0456\u0431\u0435\u0440\u0443 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043b\u0435\u0440\u0456\u043d \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u043b\u0430\u0443 \u04af\u0448\u0456\u043d \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u0493\u0430 \u0431\u0430\u0441\u044b\u04a3\u044b\u0437.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "\u0411\u0435\u0439\u043d\u0435 \u043e\u0439\u043d\u0430\u0442\u0443",
"NotificationOptionAudioPlayback": "\u0414\u044b\u0431\u044b\u0441 \u043e\u0439\u043d\u0430\u0442\u0443",
"NotificationOptionGamePlayback": "\u041e\u0439\u044b\u043d \u043e\u0439\u043d\u0430\u0442\u0443",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "\u0422\u0430\u0441\u0443\u0448\u044b\u0445\u0430\u043d\u0430\u0493\u0430 \u0436\u0430\u04a3\u0430 \u043c\u0430\u0437\u043c\u04b1\u043d \u04af\u0441\u0442\u0435\u043b\u0433\u0435\u043d",
"SendNotificationHelp": "\u04d8\u0434\u0435\u043f\u043a\u0456\u0434\u0435, \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u043b\u0430\u0440 \u0431\u0430\u049b\u044b\u043b\u0430\u0443 \u0442\u0430\u049b\u0442\u0430\u0441\u044b\u043d\u0434\u0430\u0493\u044b \u043a\u0456\u0440\u0456\u0441 \u0436\u04d9\u0448\u0456\u0433\u0456\u043d\u0435 \u0436\u0435\u0442\u043a\u0456\u0437\u0456\u043b\u0435\u0434\u0456. \u049a\u043e\u0441\u044b\u043c\u0448\u0430 \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443 \u049b\u04b1\u0440\u0430\u043b\u0434\u0430\u0440\u044b\u043d \u043e\u0440\u043d\u0430\u0442\u0443 \u04af\u0448\u0456\u043d \u043f\u043b\u0430\u0433\u0438\u043d\u0434\u0435\u0440 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0456\u043d \u0448\u0430\u0440\u043b\u0430\u04a3\u044b\u0437.",
"NotificationOptionServerRestartRequired": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456 \u049b\u0430\u0439\u0442\u0430 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u0443 \u049b\u0430\u0436\u0435\u0442",
"LabelNotificationEnabled": "\u0411\u04b1\u043b \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u0434\u044b \u049b\u043e\u0441\u0443",
"LabelMonitorUsers": "\u041c\u044b\u043d\u0430 \u0436\u0435\u0440\u0434\u0435\u043d \u04d9\u0440\u0435\u043a\u0435\u0442\u0442\u0435\u0440\u0434\u0456 \u0431\u0430\u049b\u044b\u043b\u0430\u0443:",
"LabelSendNotificationToUsers": "\u0425\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u0443\u0434\u044b \u043c\u044b\u043d\u0430 \u0436\u0435\u0440\u0433\u0435 \u0436\u0456\u0431\u0435\u0440\u0443:",
"UsersNotNotifiedAboutSelfActivity": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440\u0493\u0430 \u04e9\u0437\u0456\u043d\u0456\u04a3 \u04d9\u0440\u0435\u043a\u0435\u0442\u0442\u0435\u0440\u0456 \u0442\u0443\u0440\u0430\u043b\u044b \u0445\u0430\u0431\u0430\u0440\u043b\u0430\u043d\u0434\u044b\u0440\u044b\u043b\u043c\u0430\u0439\u0434\u044b.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -37,7 +37,7 @@
"ButtonCancel": "Annuleren",
"ButtonNew": "Nieuw",
"HeaderSetupLibrary": "Stel uw mediabibliotheek in",
"ButtonAddMediaFolder": "Voeg mediamap toe",
"ButtonAddMediaFolder": "Mediamap toevoegen",
"LabelFolderType": "Maptype:",
"MediaFolderHelpPluginRequired": "* Hiervoor is het gebruik van een plug-in vereist, bijvoorbeeld GameBrowser of MB Bookshelf.",
"ReferToMediaLibraryWiki": "Raadpleeg de mediabibliotheek wiki.",
@ -45,9 +45,9 @@
"LabelLanguage": "Taal:",
"HeaderPreferredMetadataLanguage": "Gewenste metagegevens taal:",
"LabelSaveLocalMetadata": "Sla afbeeldingen en metagegevens op in de mediamappen",
"LabelSaveLocalMetadataHelp": "Afbeeldingen en metagegevens opslaan in mediamappen zorgt er voor dat ze makkelijk vindbaar zijn en gemakkelijk kunnen worden bewerkt.",
"LabelSaveLocalMetadataHelp": "Door afbeeldingen en metagegevens op te slaan in de mediamappen kunnen ze makkelijker worden gevonden en bewerkt.",
"LabelDownloadInternetMetadata": "Download afbeeldingen en metagegevens van het internet",
"LabelDownloadInternetMetadataHelp": "Media Browser kan informatie downloaden over uw media om mooie rijkelijke weergave mogelijk te maken.",
"LabelDownloadInternetMetadataHelp": "Media Browser kan informatie en afbeeldingen van uw media downloaden, om zo een mooie en uitgebreide weergave mogelijk te maken.",
"TabPreferences": "Voorkeuren",
"TabPassword": "Wachtwoord",
"TabLibraryAccess": "Bibliotheek toegang",
@ -55,7 +55,7 @@
"TabProfile": "Profiel",
"TabMetadata": "Metagegevens",
"TabImages": "Afbeeldingen",
"TabNotifications": "Notifications",
"TabNotifications": "Meldingen",
"TabCollectionTitles": "Titels",
"LabelDisplayMissingEpisodesWithinSeasons": "Toon ontbrekende afleveringen binnen een seizoen",
"LabelUnairedMissingEpisodesWithinSeasons": "Toon toekomstige afleveringen binnen een seizoen",
@ -75,7 +75,7 @@
"LabelCurrentPassword": "Huidig wachtwoord",
"LabelMaxParentalRating": "Leeftijdsgrens",
"MaxParentalRatingHelp": "Media met een hogere classificatie wordt niet weergegeven",
"LibraryAccessHelp": "Selecteer de media mappen om met deze gebruiker te delen. Beheerders kunnen alle mappen bewerken via de metagegevens manager.",
"LibraryAccessHelp": "Selecteer de mediamappen om met deze gebruiker te delen. Beheerders kunnen alle mappen bewerken via de metagegevens manager.",
"ButtonDeleteImage": "Verwijder afbeelding",
"ButtonUpload": "Uploaden",
"HeaderUploadNewImage": "Nieuwe afbeelding uploaden",
@ -191,10 +191,10 @@
"OptionMissingImdbId": "IMDb Id ontbreekt",
"OptionMissingTvdbId": "TheTVDB Id ontbreekt",
"OptionMissingOverview": "Overzicht ontbreekt",
"OptionFileMetadataYearMismatch": "Bestands\/metagegevens Jaren komen niet overeen",
"OptionFileMetadataYearMismatch": "Het jaartal in de Bestands\/metagegevens komt niet overeen",
"TabGeneral": "Algemeen",
"TitleSupport": "Ondersteuning",
"TabLog": "Log",
"TabLog": "Logboek",
"TabAbout": "Over",
"TabSupporterKey": "Supporter Sleutel",
"TabBecomeSupporter": "Word Supporter",
@ -236,7 +236,7 @@
"OptionDev": "Dev (Instabiel)",
"LabelAllowServerAutoRestart": "Sta de server toe automatisch te herstarten om updates toe te passen",
"LabelAllowServerAutoRestartHelp": "De server zal alleen opnieuw op tijdens inactieve perioden, wanneer er geen gebruikers actief zijn.",
"LabelEnableDebugLogging": "Foutopsporing log inschakelen",
"LabelEnableDebugLogging": "Foutopsporings loboekg inschakelen",
"LabelRunServerAtStartup": "Start server bij het aanmelden",
"LabelRunServerAtStartupHelp": "Dit zal de applicatie starten als pictogram in het systeemvak tijdens het aanmelden van Windows. Om de Windows-service te starten, schakelt u deze uit en start u de service via het Configuratiescherm van Windows. Houd er rekening mee dat u de service en de applicatie niet tegelijk kunt uitvoeren, u moet dus eerst de applicatie sluiten alvorens u de service start.",
"ButtonSelectDirectory": "Selecteer map",
@ -356,7 +356,7 @@
"TabGameSystems": "Game Systemen",
"TitleMediaLibrary": "Media Bibliotheek",
"TabFolders": "Mappen",
"TabPathSubstitution": "Pad Vervanging",
"TabPathSubstitution": "Pad Vervangen",
"LabelSeasonZeroDisplayName": "Seizoen 0 weergave naam:",
"LabelEnableRealtimeMonitor": "Real time monitoring inschakelen",
"LabelEnableRealtimeMonitorHelp": "Wijzigingen worden direct verwerkt, op ondersteunde bestandssystemen.",
@ -381,7 +381,7 @@
"ButtonPlayTrailer": "Trailer",
"LabelMissing": "Ontbreekt",
"LabelOffline": "Offline",
"PathSubstitutionHelp": "Pad vervangingen worden gebruikt voor het in kaart brengen van een pad op de server naar een pad dat de Cli\u00ebnts in staat stellen om toegang te krijgen. Doordat Cli\u00ebnts directe toegang tot de media op de server hebben zijn zij in staat om ze direct af te spelen via het netwerk en daardoor het gebruik van server resources om te streamen en te transcoderen te vermijden.",
"PathSubstitutionHelp": "Pad vervangen worden gebruikt voor het in kaart brengen van een pad op de server naar een pad dat de Cli\u00ebnt in staat stelt om toegang te krijgen. Doordat de Cli\u00ebnt directe toegang tot de media op de server heeft is deze in staat om ze direct af te spelen via het netwerk. Daardoor wordt het gebruik van server resources om te streamen en te transcoderen vermeden.",
"HeaderFrom": "Van",
"HeaderTo": "Naar",
"LabelFrom": "Van:",
@ -403,7 +403,7 @@
"OptionHighSpeedTranscoding": "Hogere snelheid",
"OptionHighQualityTranscoding": "Hogere kwaliteit",
"OptionMaxQualityTranscoding": "Max kwaliteit",
"OptionEnableDebugTranscodingLogging": "Transcodeer Foutopsporings log inschakelen",
"OptionEnableDebugTranscodingLogging": "Transcodeer Foutopsporings logboek inschakelen",
"OptionEnableDebugTranscodingLoggingHelp": "Dit zal zeer grote logbestanden maken en wordt alleen aanbevolen wanneer het nodig is voor het oplossen van problemen.",
"OptionUpscaling": "Cli\u00ebnts kunnen opgeschaalde video aanvragen",
"OptionUpscalingHelp": "In sommige gevallen zal dit resulteren in een betere videokwaliteit, maar zal het CPU-gebruik verhogen.",
@ -451,7 +451,7 @@
"LabelMaxResumePercentageHelp": "Titels worden ingesteld als volledig afgespeeld als gestopt na deze tijd",
"LabelMinResumeDurationHelp": "Titels korter deze tijd dit zullen niet hervatbaar zijn",
"TitleAutoOrganize": "Automatisch Organiseren",
"TabActivityLog": "Activiteiten Log",
"TabActivityLog": "Activiteiten Logboek",
"HeaderName": "Naam",
"HeaderDate": "Datum",
"HeaderSource": "Bron",
@ -545,21 +545,34 @@
"HeaderServerSettings": "Server Instellingen",
"LabelWeatherDisplayLocation": "Weersbericht locatie:",
"LabelWeatherDisplayLocationHelp": "US postcode \/ plaats, staat, land \/ Stad, Land \/ Weer ID",
"LabelWeatherDisplayUnit": "Temeratuur eenheid:",
"LabelWeatherDisplayUnit": "Temperatuurs eenheid:",
"OptionCelsius": "Celsius",
"OptionFahrenheit": "Fahrenheit",
"HeaderRequireManualLogin": "Vereist handmatig aanmelden met gebruikersnaam voor:",
"HeaderRequireManualLoginHelp": "Indien uitgeschaleld dan tonen de cli\u00ebnts een visueel login-scherm met een selectie van gebruikers.",
"HeaderRequireManualLoginHelp": "Indien uitgeschaleld dan toont de cli\u00ebnt een login-scherm met een visuele selectie van gebruikers.",
"OptionOtherApps": "Overige apps",
"OptionMobileApps": "Mobiele apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Klik op een melding om de opties voor het versturen ervan te configureren .",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "Meldingen worden geplaatst in de dashboard inbox. Blader door de Plug-ins catalogus om aanvullende opties voor meldingen te installeren.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Deze melding inschakelen",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -55,7 +55,7 @@
"TabProfile": "Perfil",
"TabMetadata": "Metadados",
"TabImages": "Imagens",
"TabNotifications": "Notifications",
"TabNotifications": "Notifica\u00e7\u00f5es",
"TabCollectionTitles": "T\u00edtulos",
"LabelDisplayMissingEpisodesWithinSeasons": "Exibir epis\u00f3dios ausentes dentro das temporadas",
"LabelUnairedMissingEpisodesWithinSeasons": "Exibir epis\u00f3dios por estrear dentro das temporadas",
@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "Quando desativados, os clientes podem mostrar a tela de login com uma sele\u00e7\u00e3o visual de usu\u00e1rios.",
"OptionOtherApps": "Outras apps",
"OptionMobileApps": "Apps m\u00f3veis",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "Notifica\u00e7\u00f5es s\u00e3o entregues \u00e0 caixa de entrada do painel. Navegue pelo cat\u00e1logo de plugins para instalar op\u00e7\u00f5es adicionais de notifica\u00e7\u00f5es.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -60,7 +60,7 @@
"LabelDisplayMissingEpisodesWithinSeasons": "Mostrar epis\u00f3dios em falta dentro das temporadas",
"LabelUnairedMissingEpisodesWithinSeasons": "Mostrar epis\u00f3dios por estrear dentro das temporadas",
"HeaderVideoPlaybackSettings": "Configura\u00e7\u00f5es de Reprodu\u00e7\u00e3o de V\u00eddeo",
"HeaderPlaybackSettings": "Playback Settings",
"HeaderPlaybackSettings": "Op\u00e7\u00f5es de Reprodu\u00e7\u00e3o",
"LabelAudioLanguagePreference": "Prefer\u00eancias de Idioma de Audio:",
"LabelSubtitleLanguagePreference": "Prefer\u00eancia de Idioma de Legenda:",
"LabelDisplayForcedSubtitlesOnly": "Mostrar apenas legendas for\u00e7adas",
@ -534,7 +534,7 @@
"HeaderDisplaySettings": "Apresentar Configura\u00e7\u00f5es",
"TabPlayTo": "Play To",
"LabelEnableDlnaServer": "Ativar servidor DLNA",
"LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play Media Browser content.",
"LabelEnableDlnaServerHelp": "Permite aos dispositivos UPnP da sua rede, navegar e reproduzir conte\u00fado do Media Browser.",
"LabelEnableBlastAliveMessages": "Blast alive messages",
"LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.",
"LabelBlastMessageInterval": "Alive message interval (seconds)",
@ -542,24 +542,37 @@
"LabelDefaultUser": "Utilizador padr\u00e3o:",
"LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden for each device using profiles.",
"TitleDlna": "DLNA",
"HeaderServerSettings": "Server Settings",
"HeaderServerSettings": "Op\u00e7\u00f5es do Servidor",
"LabelWeatherDisplayLocation": "Weather display location:",
"LabelWeatherDisplayLocationHelp": "US zip code \/ City, State, Country \/ City, Country",
"LabelWeatherDisplayLocationHelp": "C\u00f3digo postal dos EUA \/ Cidade, Estado, Pa\u00eds \/ Cidade, Pa\u00eds",
"LabelWeatherDisplayUnit": "Weather display unit:",
"OptionCelsius": "Celsius",
"OptionFahrenheit": "Fahrenheit",
"HeaderRequireManualLogin": "Require manual username entry for:",
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"HeaderRequireManualLogin": "Necessita a inser\u00e7\u00e3o manual de um nome de utilizador para:",
"HeaderRequireManualLoginHelp": "Quando desativados, os clientes podem mostrar a tela de login com uma sele\u00e7\u00e3o visual de utilizadores.",
"OptionOtherApps": "Outras apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Quando est\u00e3o dispon\u00edveis atualiza\u00e7\u00f5es",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"OptionMobileApps": "Apps m\u00f3veis",
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -103,7 +103,7 @@
"OptionWriters": "\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0441\u0442\u044b",
"OptionProducers": "\u041f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u044b",
"HeaderResume": "\u0412\u043e\u0437\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435",
"HeaderNextUp": "\u0427\u0442\u043e \u043f\u043e\u0442\u043e\u043c",
"HeaderNextUp": "\u041d\u0430 \u043f\u043e\u0434\u0445\u043e\u0434\u0435",
"NoNextUpItemsMessage": "\u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e. \u0421\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0441\u0432\u043e\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u044b \u0441 \u043d\u0430\u0447\u0430\u043b\u0430!",
"HeaderLatestEpisodes": "\u0421\u0432\u0435\u0436\u0438\u0435 \u044d\u043f\u0438\u0437\u043e\u0434\u044b",
"HeaderPersonTypes": "\u0422\u0438\u043f\u044b \u043b\u044e\u0434\u0435\u0439:",
@ -291,8 +291,8 @@
"OptionPrePaddingRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u0431\u0438\u0432\u043a\u0430 \u0434\u043e \u043d\u0430\u0447\u0430\u043b\u0430 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043b\u044f \u0435\u0451 \u0446\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u0438.",
"LabelPostPaddingMinutes": "\u041e\u0442\u0431\u0438\u0432\u043a\u0430 \u043f\u043e\u0441\u043b\u0435, \u043c\u0438\u043d:",
"OptionPostPaddingRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u0431\u0438\u0432\u043a\u0430 \u043f\u043e\u0441\u043b\u0435 \u043a\u043e\u043d\u0446\u0430 \u0437\u0430\u043f\u0438\u0441\u0438 \u0434\u043b\u044f \u0435\u0451 \u0446\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u0438.",
"HeaderWhatsOnTV": "\u0427\u0442\u043e \u0438\u0434\u0451\u0442",
"HeaderUpcomingTV": "\u0421\u043a\u043e\u0440\u043e \u0432 \u044d\u0444\u0438\u0440\u0435",
"HeaderWhatsOnTV": "\u0412 \u044d\u0444\u0438\u0440\u0435",
"HeaderUpcomingTV": "\u0421\u043a\u043e\u0440\u043e \u043d\u0430 \u0422\u0412",
"TabStatus": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435:",
"TabSettings": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b",
"ButtonRefreshGuideData": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0442\u0435\u043b\u0435\u0433\u0438\u0434",
@ -396,7 +396,7 @@
"OptionSeriesSortName": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0440\u0438\u0430\u043b\u0430",
"OptionTvdbRating": "\u041e\u0446\u0435\u043d\u043a\u0430 TVDb",
"HeaderTranscodingQualityPreference": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u0440\u0430\u043d\u0441\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f:",
"OptionAutomaticTranscodingHelp": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0438 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c",
"OptionAutomaticTranscodingHelp": "\u041a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0438 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0431\u0443\u0434\u0443\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c\u0441\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c",
"OptionHighSpeedTranscodingHelp": "\u0411\u043e\u043b\u0435\u0435 \u043d\u0438\u0437\u043a\u043e\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e, \u043d\u043e \u043f\u0440\u0438 \u0431\u043e\u043b\u0435\u0435 \u0431\u044b\u0441\u0442\u0440\u043e\u043c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438",
"OptionHighQualityTranscodingHelp": "\u0411\u043e\u043b\u0435\u0435 \u0432\u044b\u0441\u043e\u043a\u043e\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e, \u043d\u043e \u043f\u0440\u0438 \u0431\u043e\u043b\u0435\u0435 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438",
"OptionMaxQualityTranscodingHelp": "\u041d\u0430\u0438\u043b\u0443\u0447\u0448\u0435\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e, \u043f\u0440\u0438 \u0431\u043e\u043b\u0435\u0435 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u043c \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0438 \u043f\u0440\u0438 \u0432\u044b\u0441\u043e\u043a\u043e\u0439 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u043d\u0430 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440",
@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "\u041f\u0440\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438, \u043a\u043b\u0438\u0435\u043d\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u0432\u0445\u043e\u0434\u0430 \u0441 \u0432\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u044b\u043c \u0432\u044b\u0431\u043e\u0440\u043e\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439.",
"OptionOtherApps": "\u0414\u0440\u0443\u0433\u0438\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f",
"OptionMobileApps": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f",
"HeaderEnableNotificationForEvents": "\u041f\u043e\u0441\u044b\u043b\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445:",
"OptionNotifyOnUpdates": "\u041a\u043e\u0433\u0434\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "\u041a\u043e\u0433\u0434\u0430 \u0441\u0431\u043e\u0439 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0445 \u0437\u0430\u0434\u0430\u043d\u0438\u0439",
"OptionNotifyOnNewLibraryContent": "\u041a\u043e\u0433\u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0432 \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0443",
"SendNotificationHelp": "\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u044f\u0449\u0438\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043f\u0430\u043d\u0435\u043b\u0438 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "\u0429\u0451\u043b\u043a\u043d\u0438\u0442\u0435 \u043f\u043e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044e, \u0447\u0442\u043e\u0431\u044b \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043e\u0442\u0441\u044b\u043b\u043a\u0438.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0432\u0438\u0434\u0435\u043e",
"NotificationOptionAudioPlayback": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0430\u0443\u0434\u0438\u043e",
"NotificationOptionGamePlayback": "\u0417\u0430\u043f\u0443\u0441\u043a \u0438\u0433\u0440\u044b",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f",
"SendNotificationHelp": "\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0432 \u044f\u0449\u0438\u043a \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u043f\u0430\u043d\u0435\u043b\u0438 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u0432 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f.",
"NotificationOptionServerRestartRequired": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
"LabelNotificationEnabled": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435",
"LabelMonitorUsers": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043e\u0442:",
"LabelSendNotificationToUsers": "\u041f\u043e\u0441\u044b\u043b\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043d\u0430:",
"UsersNotNotifiedAboutSelfActivity": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0442\u044c\u0441\u044f \u043e \u0441\u0432\u043e\u0438\u0445 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u044f\u0445.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -556,14 +556,28 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:",
"AdditionalNotificationServices": "Browse the plugin catalog to install additional notification services."
}

@ -55,12 +55,12 @@
"TabProfile": "Profil",
"TabMetadata": "Metadata",
"TabImages": "Bilder",
"TabNotifications": "Notifications",
"TabNotifications": "Meddelanden",
"TabCollectionTitles": "Titlar",
"LabelDisplayMissingEpisodesWithinSeasons": "Visa saknade avsnitt i s\u00e4songer",
"LabelUnairedMissingEpisodesWithinSeasons": "Visa \u00e4nnu ej s\u00e4nda avsnitt i s\u00e4songer",
"HeaderVideoPlaybackSettings": "Inst\u00e4llningar f\u00f6r videouppspelning",
"HeaderPlaybackSettings": "Playback Settings",
"HeaderPlaybackSettings": "Uppspelningsinst\u00e4llningar",
"LabelAudioLanguagePreference": "\u00d6nskat spr\u00e5k f\u00f6r ljudsp\u00e5r",
"LabelSubtitleLanguagePreference": "\u00d6nskat spr\u00e5k f\u00f6r undertexter",
"LabelDisplayForcedSubtitlesOnly": "Visa endast tvingande undertexter",
@ -196,8 +196,8 @@
"TitleSupport": "Support",
"TabLog": "H\u00e4ndelselogg",
"TabAbout": "Om",
"TabSupporterKey": "Sponsorkod",
"TabBecomeSupporter": "Bli sponsor",
"TabSupporterKey": "Donationskod",
"TabBecomeSupporter": "Bidra med en donation",
"MediaBrowserHasCommunity": "Media Browser har en livaktig grupp av anv\u00e4ndare och medverkande.",
"CheckoutKnowledgeBase": "Ta en titt i v\u00e5r kunskapsdatabas s\u00e5 att du f\u00e5r ut mesta m\u00f6jliga av Media Browser.",
"SearchKnowledgeBase": "S\u00f6k i kunskapsdatabasen",
@ -470,8 +470,8 @@
"HeaderSupportTheTeam": "St\u00f6d Media Browser",
"LabelSupportAmount": "M\u00e4ngd (USD)",
"HeaderSupportTheTeamHelp": "Bidra till fortsatt utveckling av projektet genom att donera. En del av alla donationer kommer att ges till andra kostnadsfria verktyg som vi \u00e4r beroende av.",
"ButtonEnterSupporterKey": "Skriv in donations kod",
"DonationNextStep": "N\u00e4r du \u00e4r klar, g\u00e5 tillbaka och ange din donations kod, som du kommer att f\u00e5 via e-post.",
"ButtonEnterSupporterKey": "Ange din donationskod",
"DonationNextStep": "N\u00e4r du \u00e4r klar, g\u00e5 tillbaka och ange din donationskod, som du kommer att f\u00e5 via e-post.",
"AutoOrganizeHelp": "Auto-Organisera \u00f6vervakar din nedladdnings mapp efter nya filer och kommer att flytta dessa till dina medie mappar.",
"AutoOrganizeTvHelp": "TV-fil organisering kommer bara l\u00e4gga episoder till befintliga serier. Den kommer inte att skapa nya serie mappar.",
"OptionEnableEpisodeOrganization": "Aktivera ny avsnitts organisering",
@ -517,18 +517,18 @@
"LabelDownMixAudioScale": "Skala vid volymh\u00f6jning av nedmixat ljud",
"LabelDownMixAudioScaleHelp": "H\u00f6j volymen vid nedmixning. S\u00e4tt v\u00e4rdet till 1 f\u00f6r att beh\u00e5lla den ursprungliga niv\u00e5n.",
"ButtonLinkKeys": "Koppla koder",
"LabelOldSupporterKey": "Gammal supporterkod",
"LabelNewSupporterKey": "Ny supporterkod",
"LabelOldSupporterKey": "Tidigare donationskod",
"LabelNewSupporterKey": "Ny donationskod",
"HeaderMultipleKeyLinking": "Koppla ihop flera koder",
"MultipleKeyLinkingHelp": "Om du har flera supporterkoder, anv\u00e4nd det h\u00e4r formul\u00e4ret f\u00f6r att koppla deras registreringar till den nya koden.",
"MultipleKeyLinkingHelp": "Om du har flera donationskoder, anv\u00e4nd det h\u00e4r formul\u00e4ret f\u00f6r att koppla deras registreringar till den nya koden.",
"LabelCurrentEmailAddress": "Nuvarande e-postadress",
"LabelCurrentEmailAddressHelp": "Den e-postadress den nya koden skickades till.",
"HeaderForgotKey": "Gl\u00f6mt koden",
"LabelEmailAddress": "E-postadress",
"LabelSupporterEmailAddress": "Den e-postadress du angav vid k\u00f6pet av den nya koden.",
"ButtonRetrieveKey": "H\u00e4mta supporterkod",
"LabelSupporterKey": "Supporterkod (klistra in fr\u00e5n e-postmeddelandet)",
"LabelSupporterKeyHelp": "Ange din supporterkod s\u00e5 att du kan b\u00f6rja anv\u00e4nda de extrafunktioner som har utvecklats inom Media Browsers anv\u00e4ndargrupper.",
"ButtonRetrieveKey": "H\u00e4mta donationskod",
"LabelSupporterKey": "Donationskod (klistra in fr\u00e5n e-postmeddelandet)",
"LabelSupporterKeyHelp": "Ange din donationskod s\u00e5 att du kan b\u00f6rja anv\u00e4nda de extrafunktioner som har utvecklats inom Media Browsers anv\u00e4ndargrupper.",
"MessageInvalidKey": "MB3-kod ogiltig eller saknas",
"ErrorMessageInvalidKey": "F\u00f6r att kunna registrera premiumfunktioner m\u00e5ste du \u00e4ven vara MB3-supporter. V\u00e4nligen g\u00f6r en donation s\u00e5 att du st\u00f6ttar den fortsatta utvecklingen av huvudprodukten. Tack!",
"HeaderDisplaySettings": "Bildsk\u00e4rmsinst\u00e4llningar",
@ -542,24 +542,37 @@
"LabelDefaultUser": "F\u00f6rvald anv\u00e4ndare:",
"LabelDefaultUserHelp": "Anger vilket anv\u00e4ndarbibliotek som skall visas p\u00e5 anslutna enheter. Denna inst\u00e4llning kan \u00e4ndras med hj\u00e4lp av en enhetsprofil.",
"TitleDlna": "DLNA",
"HeaderServerSettings": "Server Settings",
"LabelWeatherDisplayLocation": "Weather display location:",
"LabelWeatherDisplayLocationHelp": "US zip code \/ City, State, Country \/ City, Country",
"LabelWeatherDisplayUnit": "Weather display unit:",
"HeaderServerSettings": "Serverinst\u00e4llningar",
"LabelWeatherDisplayLocation": "Geografisk plats f\u00f6r v\u00e4derdata:",
"LabelWeatherDisplayLocationHelp": "U.S. zip code \/ Stad, stat, land \/ Stad, land",
"LabelWeatherDisplayUnit": "Enhet f\u00f6r v\u00e4derdata:",
"OptionCelsius": "Celsius",
"OptionFahrenheit": "Fahrenheit",
"HeaderRequireManualLogin": "Require manual username entry for:",
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderRequireManualLogin": "Kr\u00e4v att anv\u00e4ndarnamn anges manuellt f\u00f6r:",
"HeaderRequireManualLoginHelp": "Om avaktiverat kan klienterna visa en inloggningsbild f\u00f6r visuellt val av anv\u00e4ndare.",
"OptionOtherApps": "Andra appar",
"OptionMobileApps": "Mobilappar",
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "Meddelanden visas i kontrollpanelens inkorg. S\u00f6k efter fler meddelandetill\u00e4gg i pluginkatalogen.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -552,14 +552,27 @@
"HeaderRequireManualLoginHelp": "When disabled clients may present a login screen with a visual selection of users.",
"OptionOtherApps": "Other apps",
"OptionMobileApps": "Mobile apps",
"HeaderEnableNotificationForEvents": "Nofity administrative users when:",
"OptionNotifyOnUpdates": "Updates are available",
"OptionNotifyOnVideoPlayback": "Video",
"OptionNotifyOnAudioPlayback": "Audio",
"OptionNotifyOnGamePlayback": "Games",
"OptionNotifyOnFailedTasks": "Scheduled tasks fail",
"OptionNotifyOnNewLibraryContent": "New library content is added",
"SendNotificationHelp": "Notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"HeaderEnableNotificationForPlayback": "Notify when users play:",
"OptionNotifyOnServerRestartRequired": "The server needs to be restarted"
"HeaderNotificationList": "Click on a notification to configure it's sending options.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
"NotificationOptionPluginInstalled": "Plugin installed",
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
"NotificationOptionVideoPlayback": "Video playback",
"NotificationOptionAudioPlayback": "Audio playback",
"NotificationOptionGamePlayback": "Game playback",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
"LabelMonitorUsers": "Monitor activity from:",
"LabelSendNotificationToUsers": "Send the notification to:",
"UsersNotNotifiedAboutSelfActivity": "Users will not be notified about their own activities.",
"LabelUseNotificationServices": "Use the following services:",
"CategoryUser": "User",
"CategorySystem": "System",
"LabelMessageTitle": "Message title:",
"LabelAvailableTokens": "Available tokens:"
}

@ -185,6 +185,7 @@
<Compile Include="MediaEncoder\EncodingManager.cs" />
<Compile Include="News\NewsEntryPoint.cs" />
<Compile Include="News\NewsService.cs" />
<Compile Include="Notifications\CoreNotificationTypes.cs" />
<Compile Include="Notifications\InternalNotificationService.cs" />
<Compile Include="Notifications\NotificationManager.cs" />
<Compile Include="Persistence\SqliteChapterRepository.cs" />

@ -2,9 +2,12 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.News;
using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
@ -25,15 +28,20 @@ namespace MediaBrowser.Server.Implementations.News
private readonly ILogger _logger;
private readonly IJsonSerializer _json;
private readonly INotificationManager _notifications;
private readonly IUserManager _userManager;
private readonly TimeSpan _frequency = TimeSpan.FromHours(24);
public NewsEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IJsonSerializer json)
public NewsEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IJsonSerializer json, INotificationManager notifications, IUserManager userManager)
{
_httpClient = httpClient;
_appPaths = appPaths;
_fileSystem = fileSystem;
_logger = logger;
_json = json;
_notifications = notifications;
_userManager = userManager;
}
public void Run()
@ -61,6 +69,13 @@ namespace MediaBrowser.Server.Implementations.News
private async Task DownloadNews(string path)
{
DateTime? lastUpdate = null;
if (File.Exists(path))
{
lastUpdate = _fileSystem.GetLastWriteTimeUtc(path);
}
var requestOptions = new HttpRequestOptions
{
Url = "http://mediabrowser3.com/community/index.php?/blog/rss/1-media-browser-developers-blog",
@ -75,9 +90,31 @@ namespace MediaBrowser.Server.Implementations.News
var news = ParseRssItems(doc).ToList();
_json.SerializeToFile(news, path);
await CreateNotifications(news, lastUpdate, CancellationToken.None).ConfigureAwait(false);
}
}
private Task CreateNotifications(List<NewsItem> items, DateTime? lastUpdate, CancellationToken cancellationToken)
{
if (lastUpdate.HasValue)
{
items = items.Where(i => i.Date.ToUniversalTime() > lastUpdate.Value)
.ToList();
}
var tasks = items.Select(i => _notifications.SendNotification(new NotificationRequest
{
Date = i.Date,
Name = i.Title,
Description = i.Link,
UserIds = _userManager.Users.Select(u => u.Id.ToString("N")).ToList()
}, cancellationToken));
return Task.WhenAll(tasks);
}
private IEnumerable<NewsItem> ParseRssItems(XmlDocument xmlDoc)
{
var nodes = xmlDoc.SelectNodes("rss/channel/item");

@ -2,10 +2,10 @@
using MediaBrowser.Controller.News;
using MediaBrowser.Model.News;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Serialization;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.News
{

@ -0,0 +1,131 @@
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Notifications;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Server.Implementations.Notifications
{
public class CoreNotificationTypes : INotificationTypeFactory
{
private readonly ILocalizationManager _localization;
public CoreNotificationTypes(ILocalizationManager localization)
{
_localization = localization;
}
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
{
var knownTypes = new List<NotificationTypeInfo>
{
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateAvailable.ToString(),
DefaultTitle = "A new version of Media Browser Server is available for download."
},
new NotificationTypeInfo
{
Type = NotificationType.ApplicationUpdateInstalled.ToString(),
DefaultTitle = "A new version of Media Browser Server has been installed.",
Variables = new List<string>{"Version"}
},
new NotificationTypeInfo
{
Type = NotificationType.InstallationFailed.ToString(),
DefaultTitle = "{Name} installation failed.",
Variables = new List<string>{"Name", "Version"}
},
new NotificationTypeInfo
{
Type = NotificationType.PluginInstalled.ToString(),
DefaultTitle = "{Name} was installed.",
Variables = new List<string>{"Name", "Version"}
},
new NotificationTypeInfo
{
Type = NotificationType.PluginUninstalled.ToString(),
DefaultTitle = "{Name} was uninstalled.",
Variables = new List<string>{"Name", "Version"}
},
new NotificationTypeInfo
{
Type = NotificationType.PluginUpdateInstalled.ToString(),
DefaultTitle = "{Name} was updated.",
Variables = new List<string>{"Name", "Version"}
},
new NotificationTypeInfo
{
Type = NotificationType.ServerRestartRequired.ToString(),
DefaultTitle = "Please restart Media Browser Server to finish updating."
},
new NotificationTypeInfo
{
Type = NotificationType.TaskFailed.ToString(),
DefaultTitle = "{Name} failed.",
Variables = new List<string>{"Name"}
},
new NotificationTypeInfo
{
Type = NotificationType.NewLibraryContent.ToString(),
DefaultTitle = "{Name} has been added to your media library.",
Variables = new List<string>{"Name"}
},
new NotificationTypeInfo
{
Type = NotificationType.AudioPlayback.ToString(),
DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
},
new NotificationTypeInfo
{
Type = NotificationType.GamePlayback.ToString(),
DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
},
new NotificationTypeInfo
{
Type = NotificationType.VideoPlayback.ToString(),
DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.",
Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
}
};
foreach (var type in knownTypes)
{
Update(type);
}
return knownTypes.OrderBy(i => i.Category).ThenBy(i => i.Name);
}
private void Update(NotificationTypeInfo note)
{
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type;
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
if (note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1)
{
note.Category = _localization.GetLocalizedString("CategoryUser");
}
else
{
note.Category = _localization.GetLocalizedString("CategorySystem");
}
}
}
}

@ -1,4 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Logging;
@ -15,11 +17,15 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
private readonly ILogger _logger;
private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config;
private INotificationService[] _services;
private INotificationTypeFactory[] _typeFactories;
public NotificationManager(ILogManager logManager, IUserManager userManager)
public NotificationManager(ILogManager logManager, IUserManager userManager, IServerConfigurationManager config)
{
_userManager = userManager;
_config = config;
_logger = logManager.GetLogger(GetType().Name);
}
@ -27,27 +33,34 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
var users = request.UserIds.Select(i => _userManager.GetUserById(new Guid(i)));
var tasks = _services.Select(i => SendNotification(request, i, users, cancellationToken));
var notificationType = request.NotificationType;
var title = GetTitle(request);
var tasks = _services.Where(i => IsEnabled(i, notificationType))
.Select(i => SendNotification(request, i, users, title, cancellationToken));
return Task.WhenAll(tasks);
}
public Task SendNotification(NotificationRequest request,
private Task SendNotification(NotificationRequest request,
INotificationService service,
IEnumerable<User> users,
string title,
CancellationToken cancellationToken)
{
users = users.Where(i => IsEnabledForUser(service, i))
.ToList();
var tasks = users.Select(i => SendNotification(request, service, i, cancellationToken));
var tasks = users.Select(i => SendNotification(request, service, title, i, cancellationToken));
return Task.WhenAll(tasks);
}
public async Task SendNotification(NotificationRequest request,
private async Task SendNotification(NotificationRequest request,
INotificationService service,
string title,
User user,
CancellationToken cancellationToken)
{
@ -56,13 +69,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
Date = request.Date,
Description = request.Description,
Level = request.Level,
Name = request.Name,
Name = title,
Url = request.Url,
User = user
};
_logger.Debug("Sending notification via {0} to user {1}", service.Name, user.Name);
try
{
await service.SendNotification(notification, cancellationToken).ConfigureAwait(false);
@ -73,6 +86,50 @@ namespace MediaBrowser.Server.Implementations.Notifications
}
}
private string GetTitle(NotificationRequest request)
{
var title = request.Name;
// If empty, grab from options
if (string.IsNullOrEmpty(title))
{
if (!string.IsNullOrEmpty(request.NotificationType))
{
var options = _config.Configuration.NotificationOptions.GetOptions(request.NotificationType);
if (options != null)
{
title = options.Title;
}
}
}
// If still empty, grab default
if (string.IsNullOrEmpty(title))
{
if (!string.IsNullOrEmpty(request.NotificationType))
{
var info = GetNotificationTypes().FirstOrDefault(i => string.Equals(i.Type, request.NotificationType, StringComparison.OrdinalIgnoreCase));
if (info != null)
{
title = info.DefaultTitle;
}
}
}
title = title ?? string.Empty;
foreach (var pair in request.Variables)
{
var token = "{" + pair.Key + "}";
title = title.Replace(token, pair.Value, StringComparison.OrdinalIgnoreCase);
}
return title;
}
private bool IsEnabledForUser(INotificationService service, User user)
{
try
@ -86,9 +143,50 @@ namespace MediaBrowser.Server.Implementations.Notifications
}
}
public void AddParts(IEnumerable<INotificationService> services)
private bool IsEnabled(INotificationService service, string notificationType)
{
return string.IsNullOrEmpty(notificationType) ||
_config.Configuration.NotificationOptions.IsServiceEnabled(service.Name, notificationType);
}
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
{
_services = services.ToArray();
_typeFactories = notificationTypeFactories.ToArray();
}
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
{
var list = _typeFactories.Select(i =>
{
try
{
return i.GetNotificationTypes().ToList();
}
catch (Exception ex)
{
_logger.ErrorException("Error in GetNotificationTypes", ex);
return new List<NotificationTypeInfo>();
}
}).SelectMany(i => i).ToList();
foreach (var i in list)
{
i.Enabled = _config.Configuration.NotificationOptions.IsEnabled(i.Type);
}
return list;
}
public IEnumerable<NotificationServiceInfo> GetNotificationServices()
{
return _services.Select(i => new NotificationServiceInfo
{
Name = i.Name,
Id = i.Name.GetMD5().ToString("N")
}).OrderBy(i => i.Name);
}
}
}

@ -438,7 +438,9 @@ namespace MediaBrowser.Server.Implementations.Session
Item = libraryItem,
Users = users,
MediaSourceId = info.MediaSourceId,
MediaInfo = info.Item
MediaInfo = info.Item,
DeviceName = session.DeviceName,
ClientName = session.Client
}, _logger);
@ -507,7 +509,9 @@ namespace MediaBrowser.Server.Implementations.Session
Users = users,
PlaybackPositionTicks = session.PlayState.PositionTicks,
MediaSourceId = session.PlayState.MediaSourceId,
MediaInfo = info.Item
MediaInfo = info.Item,
DeviceName = session.DeviceName,
ClientName = session.Client
}, _logger);
}
@ -577,7 +581,9 @@ namespace MediaBrowser.Server.Implementations.Session
PlaybackPositionTicks = info.PositionTicks,
PlayedToCompletion = playedToCompletion,
MediaSourceId = info.MediaSourceId,
MediaInfo = info.Item
MediaInfo = info.Item,
DeviceName = session.DeviceName,
ClientName = session.Client
}, _logger);

@ -34,6 +34,7 @@ using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Themes;
using MediaBrowser.Dlna;
using MediaBrowser.Dlna.Eventing;
using MediaBrowser.Dlna.Main;
using MediaBrowser.Dlna.PlayTo;
using MediaBrowser.Dlna.Server;
using MediaBrowser.MediaEncoding.BdInfo;
@ -527,7 +528,7 @@ namespace MediaBrowser.ServerApplication
LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager);
RegisterSingleInstance(LiveTvManager);
NotificationManager = new NotificationManager(LogManager, UserManager);
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
RegisterSingleInstance(NotificationManager);
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
@ -713,7 +714,7 @@ namespace MediaBrowser.ServerApplication
ChannelManager.AddParts(GetExports<IChannel>());
NotificationManager.AddParts(GetExports<INotificationService>());
NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
}
/// <summary>
@ -846,7 +847,7 @@ namespace MediaBrowser.ServerApplication
list.Add(typeof(MediaEncoder).Assembly);
// Dlna
list.Add(typeof(PlayToServerEntryPoint).Assembly);
list.Add(typeof(DlnaEntryPoint).Assembly);
list.AddRange(Assemblies.GetAssembliesWithParts());

@ -597,6 +597,7 @@ namespace MediaBrowser.WebDashboard.Api
"musicrecommended.js",
"musicvideos.js",
"notifications.js",
"notificationsetting.js",
"notificationsettings.js",
"playlist.js",
"plugincatalogpage.js",

@ -508,6 +508,9 @@
<Content Include="dashboard-ui\livetvrecordings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\notificationsetting.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\notificationsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -634,6 +637,9 @@
<Content Include="dashboard-ui\scripts\livetvrecordings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\notificationsetting.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\notificationsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

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

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

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

Loading…
Cancel
Save