This commit is contained in:
Cody Robibero 2022-01-24 18:43:54 -07:00
parent c5fa9c03af
commit 1e9387408d
6 changed files with 200 additions and 124 deletions

View File

@ -1,15 +1,14 @@
using MediaBrowser.Model.Plugins;
namespace Jellyfin.Plugin.SessionCleaner.Configuration
namespace Jellyfin.Plugin.SessionCleaner.Configuration;
/// <summary>
/// Plugin configuration.
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
/// <summary>
/// Plugin configuration.
/// Gets or sets the amount of days a device should be kept.
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
/// <summary>
/// Gets or sets the amount of days a device should be kept.
/// </summary>
public int Days { get; set; } = 30;
}
}
public int Days { get; set; } = 30;
}

View File

@ -15,7 +15,7 @@
<ItemGroup>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>

View File

@ -6,48 +6,47 @@ using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Plugin.SessionCleaner
namespace Jellyfin.Plugin.SessionCleaner;
/// <summary>
/// Plugin entrypoint.
/// </summary>
public class SessionCleanerPlugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
private readonly Guid _id = new("EC9E2A74-1311-4A14-B302-158E3D95FD1D");
/// <summary>
/// Plugin entrypoint.
/// Initializes a new instance of the <see cref="SessionCleanerPlugin"/> class.
/// </summary>
public class SessionCleanerPlugin : BasePlugin<PluginConfiguration>, IHasWebPages
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
public SessionCleanerPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
private readonly Guid _id = new ("EC9E2A74-1311-4A14-B302-158E3D95FD1D");
Instance = this;
}
/// <summary>
/// Initializes a new instance of the <see cref="SessionCleanerPlugin"/> class.
/// </summary>
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
public SessionCleanerPlugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
/// <summary>
/// Gets the current plugin instance.
/// </summary>
public static SessionCleanerPlugin? Instance { get; private set; }
/// <inheritdoc />
public override Guid Id => _id;
/// <inheritdoc />
public override string Name => "Session Cleaner";
/// <inheritdoc />
public override string Description => "Cleans old sessions.";
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
yield return new PluginPageInfo
{
Instance = this;
}
/// <summary>
/// Gets the current plugin instance.
/// </summary>
public static SessionCleanerPlugin? Instance { get; private set; }
/// <inheritdoc />
public override Guid Id => _id;
/// <inheritdoc />
public override string Name => "Session Cleaner";
/// <inheritdoc />
public override string Description => "Cleans old sessions.";
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
yield return new PluginPageInfo
{
Name = Name,
EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
};
}
Name = Name,
EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
};
}
}

View File

@ -9,82 +9,81 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
namespace Jellyfin.Plugin.SessionCleaner
namespace Jellyfin.Plugin.SessionCleaner;
/// <summary>
/// Device cleaner task.
/// </summary>
public class SessionCleanerTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IDeviceManager _deviceManager;
private readonly ISessionManager _sessionManager;
private readonly ILocalizationManager _localizationManager;
/// <summary>
/// Device cleaner task.
/// Initializes a new instance of the <see cref="SessionCleanerTask"/> class.
/// </summary>
public class SessionCleanerTask : IScheduledTask, IConfigurableScheduledTask
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
public SessionCleanerTask(
ISessionManager sessionManager,
ILocalizationManager localizationManager,
IDeviceManager deviceManager)
{
private readonly IDeviceManager _deviceManager;
private readonly ISessionManager _sessionManager;
private readonly ILocalizationManager _localizationManager;
_sessionManager = sessionManager;
_localizationManager = localizationManager;
_deviceManager = deviceManager;
}
/// <summary>
/// Initializes a new instance of the <see cref="SessionCleanerTask"/> class.
/// </summary>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
public SessionCleanerTask(
ISessionManager sessionManager,
ILocalizationManager localizationManager,
IDeviceManager deviceManager)
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public string Name => "Clean Old Sessions";
/// <inheritdoc />
public string Key => "CleanOldSessions";
/// <inheritdoc />
public string Description => "Removes sessions older then the configured age.";
/// <inheritdoc />
public string Category => _localizationManager.GetLocalizedString("TasksMaintenanceCategory");
/// <inheritdoc />
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
ArgumentNullException.ThrowIfNull(SessionCleanerPlugin.Instance?.Configuration);
var expireDays = SessionCleanerPlugin.Instance.Configuration.Days;
var expireDate = DateTime.UtcNow.AddDays(expireDays * -1);
var deviceResult = await _deviceManager.GetDevices(new DeviceQuery())
.ConfigureAwait(false);
var devices = deviceResult?.Items;
if (devices is null)
{
_sessionManager = sessionManager;
_localizationManager = localizationManager;
_deviceManager = deviceManager;
return;
}
/// <inheritdoc />
public bool IsHidden => false;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public string Name => "Clean Old Sessions";
/// <inheritdoc />
public string Key => "CleanOldSessions";
/// <inheritdoc />
public string Description => "Removes sessions older then the configured age.";
/// <inheritdoc />
public string Category => _localizationManager.GetLocalizedString("TasksMaintenanceCategory");
/// <inheritdoc />
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
foreach (var device in devices)
{
var expireDays = SessionCleanerPlugin.Instance?.Configuration.Days
?? throw new Exception("Plugin instance is null");
var expireDate = DateTime.UtcNow.AddDays(expireDays * -1);
var deviceResult = await _deviceManager.GetDevices(new DeviceQuery())
.ConfigureAwait(false);
var devices = deviceResult?.Items;
if (devices is null)
if (device.DateLastActivity < expireDate)
{
return;
await _sessionManager.Logout(device).ConfigureAwait(false);
}
foreach (var device in devices)
{
if (device.DateLastActivity < expireDate)
{
await _sessionManager.Logout(device).ConfigureAwait(false);
}
}
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return Enumerable.Empty<TaskTriggerInfo>();
}
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return Enumerable.Empty<TaskTriggerInfo>();
}
}

View File

@ -1,5 +1,34 @@
# Jellyfin Session Cleaner Plugin
<h1 align="center">Jellyfin Session Cleaner Plugin</h1>
<h3 align="center">Part of the <a href="https://jellyfin.media">Jellyfin Project</a></h3>
## Part of the [Jellyfin Project](https://jellyfin.org)
<p align="center">
<img alt="Plugin Banner" src="https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/plugins/SVG/jellyfin-plugin-sessioncleaner.svg?sanitize=true"/>
<br/>
<br/>
<a href="https://github.com/jellyfin/jellyfin-plugin-sessioncleaner/actions?query=workflow%3A%22Test+Build+Plugin%22">
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/jellyfin/jellyfin-plugin-sessioncleaner/Test%20Build%20Plugin.svg">
</a>
<a href="https://github.com/jellyfin/jellyfin-plugin-sessioncleaner">
<img alt="GPLv3 License" src="https://img.shields.io/github/license/jellyfin/jellyfin-plugin-sessioncleaner.svg"/>
</a>
<a href="https://github.com/jellyfin/jellyfin-plugin-sessioncleaner/releases">
<img alt="Current Release" src="https://img.shields.io/github/release/jellyfin/jellyfin-plugin-sessioncleaner.svg"/>
</a>
</p>
Cleans sessions (devices) older then the configured age.
## About
This plugin allows automatic cleaning of leftover sessions.
## Installation
[See the official documentation for install instructions](https://jellyfin.org/docs/general/server/plugins/index.html#installing).
## Contributing
We welcome all contributions and pull requests! If you have a larger feature in mind please open an issue so we can discuss the implementation before you start.
In general refer to our [contributing guidelines](https://github.com/jellyfin/.github/blob/master/CONTRIBUTING.md) for further information.
## Licence
This plugins code and packages are distributed under the GPLv3 License. See [LICENSE](./LICENSE) for more information.

View File

@ -1,25 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for Jellyfin.Server" Description="Code analysis rules for Jellyfin.Server.csproj" ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
<!-- disable warning SA1202: 'public' members must come before 'private' members -->
<Rule Id="SA1202" Action="Info" />
<!-- disable warning SA1204: Static members must appear before non-static members -->
<Rule Id="SA1204" Action="Info" />
<!-- disable warning SA1404: Code analysis suppression should have justification -->
<Rule Id="SA1404" Action="Info" />
<!-- disable warning SA1009: Closing parenthesis should be followed by a space. -->
<Rule Id="SA1009" Action="None" />
<!-- disable warning SA1011: Closing square bracket should be followed by a space. -->
<Rule Id="SA1011" Action="None" />
<!-- disable warning SA1101: Prefix local calls with 'this.' -->
<Rule Id="SA1101" Action="None" />
<!-- disable warning SA1108: Block statements should not contain embedded comments -->
<Rule Id="SA1108" Action="None" />
<!-- disable warning SA1118: Parameter must not span multiple lines. -->
<Rule Id="SA1118" Action="None" />
<!-- disable warning SA1128:: Put constructor initializers on their own line -->
<Rule Id="SA1128" Action="None" />
<!-- disable warning SA1130: Use lambda syntax -->
<Rule Id="SA1130" Action="None" />
<!-- disable warning SA1200: 'using' directive must appear within a namespace declaration -->
<Rule Id="SA1200" Action="None" />
<!-- disable warning SA1202: 'public' members must come before 'private' members -->
<Rule Id="SA1202" Action="None" />
<!-- disable warning SA1204: Static members must appear before non-static members -->
<Rule Id="SA1204" Action="None" />
<!-- disable warning SA1309: Fields must not begin with an underscore -->
<Rule Id="SA1309" Action="None" />
<!-- disable warning SA1413: Use trailing comma in multi-line initializers -->
@ -30,21 +31,60 @@
<Rule Id="SA1515" Action="None" />
<!-- disable warning SA1600: Elements should be documented -->
<Rule Id="SA1600" Action="None" />
<!-- disable warning SA1602: Enumeration items should be documented -->
<Rule Id="SA1602" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
<Rules AnalyzerId="Microsoft.CodeAnalysis.NetAnalyzers" RuleNamespace="Microsoft.Design">
<!-- error on CA1063: Implement IDisposable correctly -->
<Rule Id="CA1063" Action="Error" />
<!-- error on CA1305: Specify IFormatProvider -->
<Rule Id="CA1305" Action="Error" />
<!-- error on CA1307: Specify StringComparison for clarity -->
<Rule Id="CA1307" Action="Error" />
<!-- error on CA1309: Use ordinal StringComparison -->
<Rule Id="CA1309" Action="Error" />
<!-- error on CA1725: Parameter names should match base declaration -->
<Rule Id="CA1725" Action="Error" />
<!-- error on CA1725: Call async methods when in an async method -->
<Rule Id="CA1727" Action="Error" />
<!-- error on CA1813: Avoid unsealed attributes -->
<Rule Id="CA1813" Action="Error" />
<!-- error on CA1843: Do not use 'WaitAll' with a single task -->
<Rule Id="CA1843" Action="Error" />
<!-- error on CA1845: Use span-based 'string.Concat' -->
<Rule Id="CA1845" Action="Error" />
<!-- error on CA2016: Forward the CancellationToken parameter to methods that take one
or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token -->
<Rule Id="CA2016" Action="Error" />
<!-- error on CA2254: Template should be a static expression -->
<Rule Id="CA2254" Action="Error" />
<!-- disable warning CA1014: Mark assemblies with CLSCompliantAttribute -->
<Rule Id="CA1014" Action="Info" />
<!-- disable warning CA1024: Use properties where appropriate -->
<Rule Id="CA1024" Action="Info" />
<!-- disable warning CA1031: Do not catch general exception types -->
<Rule Id="CA1031" Action="Info" />
<!-- disable warning CA1032: Implement standard exception constructors -->
<Rule Id="CA1032" Action="Info" />
<!-- disable warning CA1040: Avoid empty interfaces -->
<Rule Id="CA1040" Action="Info" />
<!-- disable warning CA1062: Validate arguments of public methods -->
<Rule Id="CA1062" Action="Info" />
<!-- TODO: enable when false positives are fixed -->
<!-- disable warning CA1508: Avoid dead conditional code -->
<Rule Id="CA1508" Action="Info" />
<!-- disable warning CA1716: Identifiers should not match keywords -->
<Rule Id="CA1716" Action="Info" />
<!-- disable warning CA1720: Identifiers should not contain type names -->
<Rule Id="CA1720" Action="Info" />
<!-- disable warning CA1724: Type names should not match namespaces -->
<Rule Id="CA1724" Action="Info" />
<!-- disable warning CA1805: Do not initialize unnecessarily -->
<Rule Id="CA1805" Action="Info" />
<!-- disable warning CA1812: internal class that is apparently never instantiated.
If so, remove the code from the assembly.
If this class is intended to contain only static members, make it static -->
@ -53,6 +93,10 @@
<Rule Id="CA1822" Action="Info" />
<!-- disable warning CA2000: Dispose objects before losing scope -->
<Rule Id="CA2000" Action="Info" />
<!-- disable warning CA2253: Named placeholders should not be numeric values -->
<Rule Id="CA2253" Action="Info" />
<!-- disable warning CA5394: Do not use insecure randomness -->
<Rule Id="CA5394" Action="Info" />
<!-- disable warning CA1054: Change the type of parameter url from string to System.Uri -->
<Rule Id="CA1054" Action="None" />
@ -64,5 +108,11 @@
<Rule Id="CA1303" Action="None" />
<!-- disable warning CA1308: Normalize strings to uppercase -->
<Rule Id="CA1308" Action="None" />
<!-- disable warning CA1848: Use the LoggerMessage delegates -->
<Rule Id="CA1848" Action="None" />
<!-- disable warning CA2101: Specify marshaling for P/Invoke string arguments -->
<Rule Id="CA2101" Action="None" />
<!-- disable warning CA2234: Pass System.Uri objects instead of strings -->
<Rule Id="CA2234" Action="None" />
</Rules>
</RuleSet>