commit
3a91c37283
@ -1,3 +0,0 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
|
||||
always-auth=true
|
@ -0,0 +1,90 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||
|
||||
<ItemGroup Label="Package Dependencies">
|
||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
||||
<PackageVersion Include="BDInfo" Version="0.7.6.2" />
|
||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
||||
<PackageVersion Include="BlurHashSharp" Version="1.2.0" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="coverlet.collector" Version="3.2.0" />
|
||||
<PackageVersion Include="Diacritics" Version="3.3.14" />
|
||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.3" />
|
||||
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
|
||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageVersion Include="libse" Version="3.6.10" />
|
||||
<PackageVersion Include="LrcParser" Version="2022.529.1" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
||||
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="NEbml" Version="0.11.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageVersion Include="PlaylistsNET" Version="1.3.1" />
|
||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="7.0.0" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="prometheus-net" Version="7.0.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="6.1.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Graylog" Version="2.3.0" />
|
||||
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageVersion Include="SharpFuzz" Version="2.0.1" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
|
||||
<PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
<PackageVersion Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="7.0.2" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="2.0.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
|
||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
<PackageVersion Include="xunit" Version="2.4.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,123 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
public class CoreNotificationTypes : INotificationTypeFactory
|
||||
{
|
||||
private readonly ILocalizationManager _localization;
|
||||
|
||||
public CoreNotificationTypes(ILocalizationManager localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var knownTypes = new NotificationTypeInfo[]
|
||||
{
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.ApplicationUpdateInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.InstallationFailed)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginError)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginUninstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.PluginUpdateInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.ServerRestartRequired)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.TaskFailed)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.NewLibraryContent)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.AudioPlayback)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.VideoPlayback)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.AudioPlaybackStopped)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.VideoPlaybackStopped)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.UserLockedOut)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = nameof(NotificationType.ApplicationUpdateAvailable)
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var type in knownTypes)
|
||||
{
|
||||
Update(type);
|
||||
}
|
||||
|
||||
var systemName = _localization.GetLocalizedString("System");
|
||||
|
||||
return knownTypes.OrderByDescending(i => string.Equals(i.Category, systemName, StringComparison.OrdinalIgnoreCase))
|
||||
.ThenBy(i => i.Category)
|
||||
.ThenBy(i => i.Name);
|
||||
}
|
||||
|
||||
private void Update(NotificationTypeInfo note)
|
||||
{
|
||||
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
|
||||
|
||||
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
if (note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("User");
|
||||
}
|
||||
else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("Plugin");
|
||||
}
|
||||
else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("User");
|
||||
}
|
||||
else
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("System");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,23 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
public class NotificationConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new ConfigurationStore[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
Key = "notifications",
|
||||
ConfigurationType = typeof(NotificationOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates notifications for various system events.
|
||||
/// </summary>
|
||||
public class NotificationEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ILogger<NotificationEntryPoint> _logger;
|
||||
private readonly IActivityManager _activityManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly INotificationManager _notificationManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfigurationManager _config;
|
||||
|
||||
private readonly object _libraryChangedSyncLock = new object();
|
||||
private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
|
||||
|
||||
private Timer? _libraryUpdateTimer;
|
||||
|
||||
private string[] _coreNotificationTypes;
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="activityManager">The activity manager.</param>
|
||||
/// <param name="localization">The localization manager.</param>
|
||||
/// <param name="notificationManager">The notification manager.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="config">The configuration manager.</param>
|
||||
public NotificationEntryPoint(
|
||||
ILogger<NotificationEntryPoint> logger,
|
||||
IActivityManager activityManager,
|
||||
ILocalizationManager localization,
|
||||
INotificationManager notificationManager,
|
||||
ILibraryManager libraryManager,
|
||||
IServerApplicationHost appHost,
|
||||
IConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_activityManager = activityManager;
|
||||
_localization = localization;
|
||||
_notificationManager = notificationManager;
|
||||
_libraryManager = libraryManager;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
|
||||
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync()
|
||||
{
|
||||
_libraryManager.ItemAdded += OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
|
||||
_activityManager.EntryCreated += OnActivityManagerEntryCreated;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async void OnAppHostHasPendingRestartChanged(object? sender, EventArgs e)
|
||||
{
|
||||
var type = NotificationType.ServerRestartRequired.ToString();
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type,
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"),
|
||||
_appHost.Name)
|
||||
};
|
||||
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async void OnActivityManagerEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
|
||||
{
|
||||
var entry = e.Argument;
|
||||
|
||||
var type = entry.Type;
|
||||
|
||||
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = e.Argument.UserId;
|
||||
|
||||
if (!userId.Equals(default) && !GetOptions().IsEnabledToMonitorUser(type, userId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type,
|
||||
Name = entry.Name,
|
||||
Description = entry.Overview
|
||||
};
|
||||
|
||||
await SendNotification(notification, null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private NotificationOptions GetOptions()
|
||||
{
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e)
|
||||
{
|
||||
if (!FilterItem(e.Item))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
if (_libraryUpdateTimer is null)
|
||||
{
|
||||
_libraryUpdateTimer = new Timer(
|
||||
LibraryUpdateTimerCallback,
|
||||
null,
|
||||
5000,
|
||||
Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
_libraryUpdateTimer.Change(5000, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_itemsAdded.Add(e.Item);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterItem(BaseItem item)
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.HasPathProtocol)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item is IItemByName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return item.SourceType == SourceType.Library;
|
||||
}
|
||||
|
||||
private async void LibraryUpdateTimerCallback(object? state)
|
||||
{
|
||||
List<BaseItem> items;
|
||||
|
||||
lock (_libraryChangedSyncLock)
|
||||
{
|
||||
items = _itemsAdded.ToList();
|
||||
_itemsAdded.Clear();
|
||||
_libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback
|
||||
_libraryUpdateTimer = null;
|
||||
}
|
||||
|
||||
if (items.Count > 10)
|
||||
{
|
||||
items = items.GetRange(0, 10);
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = NotificationType.NewLibraryContent.ToString(),
|
||||
Name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"),
|
||||
GetItemName(item)),
|
||||
Description = item.Overview
|
||||
};
|
||||
|
||||
await SendNotification(notification, item).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a human readable name for the item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>A human readable name for the item.</returns>
|
||||
public static string GetItemName(BaseItem item)
|
||||
{
|
||||
var name = item.Name;
|
||||
if (item is Episode episode)
|
||||
{
|
||||
if (episode.IndexNumber.HasValue)
|
||||
{
|
||||
name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Ep{0} - {1}",
|
||||
episode.IndexNumber.Value,
|
||||
name);
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
name = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"S{0}, {1}",
|
||||
episode.ParentIndexNumber.Value,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
if (item is IHasSeries hasSeries)
|
||||
{
|
||||
name = hasSeries.SeriesName + " - " + name;
|
||||
}
|
||||
|
||||
if (item is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
var artists = hasAlbumArtist.AlbumArtists;
|
||||
|
||||
if (artists.Count > 0)
|
||||
{
|
||||
name = artists[0] + " - " + name;
|
||||
}
|
||||
}
|
||||
else if (item is IHasArtist hasArtist)
|
||||
{
|
||||
var artists = hasArtist.Artists;
|
||||
|
||||
if (artists.Count > 0)
|
||||
{
|
||||
name = artists[0] + " - " + name;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _notificationManager.SendNotification(notification, relatedItem, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending notification");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_libraryUpdateTimer?.Dispose();
|
||||
}
|
||||
|
||||
_libraryUpdateTimer = null;
|
||||
|
||||
_libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
|
||||
_appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
|
||||
_activityManager.EntryCreated -= OnActivityManagerEntryCreated;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,224 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Notifications;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Notifications
|
||||
{
|
||||
/// <summary>
|
||||
/// NotificationManager class.
|
||||
/// </summary>
|
||||
public class NotificationManager : INotificationManager
|
||||
{
|
||||
private readonly ILogger<NotificationManager> _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
private INotificationService[] _services = Array.Empty<INotificationService>();
|
||||
private INotificationTypeFactory[] _typeFactories = Array.Empty<INotificationTypeFactory>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NotificationManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
public NotificationManager(
|
||||
ILogger<NotificationManager> logger,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
private NotificationOptions GetConfiguration()
|
||||
{
|
||||
return _config.GetConfiguration<NotificationOptions>("notifications");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendNotification(request, null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken)
|
||||
{
|
||||
var notificationType = request.NotificationType;
|
||||
|
||||
var options = string.IsNullOrEmpty(notificationType) ?
|
||||
null :
|
||||
GetConfiguration().GetOptions(notificationType);
|
||||
|
||||
var users = GetUserIds(request, options)
|
||||
.Select(i => _userManager.GetUserById(i))
|
||||
.Where(i => relatedItem is null || relatedItem.IsVisibleStandalone(i))
|
||||
.ToArray();
|
||||
|
||||
var title = request.Name;
|
||||
var description = request.Description;
|
||||
|
||||
var tasks = _services.Where(i => IsEnabled(i, notificationType))
|
||||
.Select(i => SendNotification(request, i, users, title, description, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
IEnumerable<User> users,
|
||||
string title,
|
||||
string description,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
users = users.Where(i => IsEnabledForUser(service, i));
|
||||
|
||||
var tasks = users.Select(i => SendNotification(request, service, title, description, i, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption? options)
|
||||
{
|
||||
if (request.SendToUserMode.HasValue)
|
||||
{
|
||||
switch (request.SendToUserMode.Value)
|
||||
{
|
||||
case SendToUserType.Admins:
|
||||
return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
|
||||
.Select(i => i.Id);
|
||||
case SendToUserType.All:
|
||||
return _userManager.UsersIds;
|
||||
case SendToUserType.Custom:
|
||||
return request.UserIds;
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized SendToUserMode: " + request.SendToUserMode.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (options is not null && !string.IsNullOrEmpty(request.NotificationType))
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
|
||||
return _userManager.Users
|
||||
.Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i))
|
||||
.Select(i => i.Id);
|
||||
}
|
||||
|
||||
return request.UserIds;
|
||||
}
|
||||
|
||||
private async Task SendNotification(
|
||||
NotificationRequest request,
|
||||
INotificationService service,
|
||||
string title,
|
||||
string description,
|
||||
User user,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var notification = new UserNotification
|
||||
{
|
||||
Date = request.Date,
|
||||
Description = description,
|
||||
Level = request.Level,
|
||||
Name = title,
|
||||
Url = request.Url,
|
||||
User = user
|
||||
};
|
||||
|
||||
_logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username);
|
||||
|
||||
try
|
||||
{
|
||||
await service.SendNotification(notification, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending notification to {0}", service.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEnabledForUser(INotificationService service, User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return service.IsEnabledForUser(user);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in IsEnabledForUser");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEnabled(INotificationService service, string notificationType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(notificationType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
|
||||
{
|
||||
_services = services.ToArray();
|
||||
_typeFactories = notificationTypeFactories.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<NotificationTypeInfo> GetNotificationTypes()
|
||||
{
|
||||
var list = _typeFactories.Select(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return i.GetNotificationTypes().ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in GetNotificationTypes");
|
||||
return new List<NotificationTypeInfo>();
|
||||
}
|
||||
}).SelectMany(i => i).ToList();
|
||||
|
||||
var config = GetConfiguration();
|
||||
|
||||
foreach (var i in list)
|
||||
{
|
||||
i.Enabled = config.IsEnabled(i.Type);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<NameIdPair> GetNotificationServices()
|
||||
{
|
||||
return _services.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
}).OrderBy(i => i.Name);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Emby.Notifications")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
@ -1,113 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth
|
||||
{
|
||||
/// <summary>
|
||||
/// Base authorization handler.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of Authorization Requirement.</typeparam>
|
||||
public abstract class BaseAuthorizationHandler<T> : AuthorizationHandler<T>
|
||||
where T : IAuthorizationRequirement
|
||||
{
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseAuthorizationHandler{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
protected BaseAuthorizationHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_networkManager = networkManager;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate authenticated claims.
|
||||
/// </summary>
|
||||
/// <param name="claimsPrincipal">Request claims.</param>
|
||||
/// <param name="ignoreSchedule">Whether to ignore parental control.</param>
|
||||
/// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
|
||||
/// <param name="requiredDownloadPermission">Whether validation requires download permission.</param>
|
||||
/// <returns>Validated claim status.</returns>
|
||||
protected bool ValidateClaims(
|
||||
ClaimsPrincipal claimsPrincipal,
|
||||
bool ignoreSchedule = false,
|
||||
bool localAccessOnly = false,
|
||||
bool requiredDownloadPermission = false)
|
||||
{
|
||||
// ApiKey is currently global admin, always allow.
|
||||
var isApiKey = claimsPrincipal.GetIsApiKey();
|
||||
if (isApiKey)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure claim has userId.
|
||||
var userId = claimsPrincipal.GetUserId();
|
||||
if (userId.Equals(default))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure userId links to a valid user.
|
||||
var user = _userManager.GetUserById(userId);
|
||||
if (user is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure user is not disabled.
|
||||
if (user.HasPermission(PermissionKind.IsDisabled))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
|
||||
&& _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp());
|
||||
|
||||
// User cannot access remotely and user is remote
|
||||
if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !isInLocalNetwork)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (localAccessOnly && !isInLocalNetwork)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// User attempting to access out of parental control hours.
|
||||
if (!ignoreSchedule
|
||||
&& !user.HasPermission(PermissionKind.IsAdministrator)
|
||||
&& !user.IsParentalScheduleAllowed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// User attempting to download without permission.
|
||||
if (requiredDownloadPermission
|
||||
&& !user.HasPermission(PermissionKind.EnableContentDownloading))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization handler for requiring first time setup or default privileges.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrDefaultHandler : BaseAuthorizationHandler<FirstTimeSetupOrDefaultRequirement>
|
||||
{
|
||||
private readonly IConfigurationManager _configurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FirstTimeSetupOrDefaultHandler" /> class.
|
||||
/// </summary>
|
||||
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public FirstTimeSetupOrDefaultHandler(
|
||||
IConfigurationManager configurationManager,
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
_configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrDefaultRequirement requirement)
|
||||
{
|
||||
if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrDefaultRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement, requiring incomplete first time setup or elevated privileges, for the authorization handler.
|
||||
/// </summary>
|
||||
public class FirstTimeSetupOrElevatedRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Jellyfin.Api.Auth.RequiresElevationPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Authorization handler for requiring elevated privileges.
|
||||
/// </summary>
|
||||
public class RequiresElevationHandler : BaseAuthorizationHandler<RequiresElevationRequirement>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequiresElevationHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
|
||||
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
|
||||
public RequiresElevationHandler(
|
||||
IUserManager userManager,
|
||||
INetworkManager networkManager,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
: base(userManager, networkManager, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement)
|
||||
{
|
||||
var validated = ValidateClaims(context.User);
|
||||
if (validated && context.User.IsInRole(UserRoles.Administrator))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Fail();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Jellyfin.Api.Auth.RequiresElevationPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization requirement for requiring elevated privileges in the authorization handler.
|
||||
/// </summary>
|
||||
public class RequiresElevationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
namespace Jellyfin.Api.Constants
|
||||
namespace Jellyfin.Api.Constants;
|
||||
|
||||
/// <summary>
|
||||
/// Authentication schemes for user authentication in the API.
|
||||
/// </summary>
|
||||
public static class AuthenticationSchemes
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication schemes for user authentication in the API.
|
||||
/// </summary>
|
||||
public static class AuthenticationSchemes
|
||||
{
|
||||
/// <summary>
|
||||
/// Scheme name for the custom legacy authentication.
|
||||
/// </summary>
|
||||
public const string CustomAuthentication = "CustomAuthentication";
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue