Files
jellyfin-plugin-enigma2/Jellyfin.Plugin.Enigma2/PreferencesController.cs
T

71 lines
3.6 KiB
C#

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Plugin.Enigma2;
public sealed class ChannelPreferences
{
public string[] FavoriteIds { get; set; } = Array.Empty<string>();
public string[] HiddenIds { get; set; } = Array.Empty<string>();
public string[] OrderedIds { get; set; } = Array.Empty<string>();
public string DefaultView { get; set; } = "all";
public void Validate()
{
string[] Clean(string[] values) => (values ?? Array.Empty<string>()).Take(2000).Where(v=>Guid.TryParse(v,out _)).Select(v=>Guid.Parse(v).ToString("N")).Distinct().ToArray();
FavoriteIds=Clean(FavoriteIds); HiddenIds=Clean(HiddenIds); OrderedIds=Clean(OrderedIds);
if (DefaultView != "all" && DefaultView != "favorites" && DefaultView != "guide") DefaultView="all";
}
}
public sealed class PreferencesStore
{
private readonly string folder;
private readonly ConcurrentDictionary<Guid,SemaphoreSlim> locks = new();
public PreferencesStore(IApplicationPaths paths) { folder=Path.Combine(paths.DataPath,"decoder-integration","users"); }
public async Task<ChannelPreferences> Load(Guid user, CancellationToken token)
{
var gate=locks.GetOrAdd(user,_=>new(1)); await gate.WaitAsync(token);
try { var file=Path.Combine(folder,user.ToString("N")+".json"); if (!File.Exists(file)) return new();
var result=JsonSerializer.Deserialize<ChannelPreferences>(await File.ReadAllTextAsync(file,token)) ?? new(); result.Validate(); return result; }
finally { gate.Release(); }
}
public async Task Save(Guid user, ChannelPreferences value, CancellationToken token)
{
value.Validate(); var gate=locks.GetOrAdd(user,_=>new(1)); await gate.WaitAsync(token);
try { Directory.CreateDirectory(folder); var file=Path.Combine(folder,user.ToString("N")+".json"); var temp=file+".tmp";
await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(value),token); File.Move(temp,file,true); }
finally { gate.Release(); }
}
}
[ApiController]
[Route("DecoderIntegration")]
[Authorize(Policy="LiveTvAccess")]
public sealed class PreferencesController : ControllerBase
{
private readonly PreferencesStore store;
private readonly InternetEpg epg;
public PreferencesController(PreferencesStore store,InternetEpg epg) { this.store=store; this.epg=epg; }
private Guid UserId => Guid.TryParse(User.FindFirst("Jellyfin-UserId")?.Value,out var id) ? id : Guid.Empty;
[HttpGet("Channels")]
[AllowAnonymous]
public IActionResult Page()
{
var stream=typeof(Plugin).Assembly.GetManifestResourceStream("Jellyfin.Plugin.Enigma2.Configuration.channels.html");
return File(stream,"text/html; charset=utf-8");
}
[HttpGet("Preferences")]
public async Task<ActionResult<ChannelPreferences>> Get(CancellationToken token) => UserId == Guid.Empty ? Unauthorized() : Ok(await store.Load(UserId,token));
[HttpPut("Preferences")]
public async Task<IActionResult> Put(ChannelPreferences value,CancellationToken token)
{ if (UserId == Guid.Empty) return Unauthorized(); await store.Save(UserId,value,token); return NoContent(); }
[HttpGet("Status")]
[Authorize(Policy="RequiresElevation")]
public IActionResult Status() => Ok(new { epg.LastSuccess,epg.MatchedImages,epg.LastError, Source="https://epg.polishteam.one/guide.xml" });
}