199 lines
6.2 KiB
C#
199 lines
6.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using StreamPlayer.Desktop.Models;
|
|
|
|
namespace StreamPlayer.Desktop.Services;
|
|
|
|
public sealed class EventService
|
|
{
|
|
private static readonly Uri EventsUri = new("https://streamtpmedia.com/eventos.json");
|
|
private static readonly TimeSpan CacheDuration = TimeSpan.FromHours(24);
|
|
private static readonly string CachePath = Path.Combine(GetDataDirectory(), "events-cache.json");
|
|
private static readonly TimeZoneInfo EventZone = ResolveEventZone();
|
|
private static readonly HttpClient HttpClient = CreateHttpClient();
|
|
|
|
public async Task<IReadOnlyList<LiveEvent>> GetEventsAsync(bool forceRefresh, CancellationToken cancellationToken)
|
|
{
|
|
if (!forceRefresh && TryLoadFromCache(out var cached))
|
|
{
|
|
return cached;
|
|
}
|
|
|
|
try
|
|
{
|
|
string json = await DownloadJsonAsync(cancellationToken).ConfigureAwait(false);
|
|
var events = ParseEvents(json);
|
|
SaveCache(json);
|
|
return events;
|
|
}
|
|
catch
|
|
{
|
|
if (TryLoadFromCache(out var cachedEvents))
|
|
{
|
|
return cachedEvents;
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static bool TryLoadFromCache(out IReadOnlyList<LiveEvent> events)
|
|
{
|
|
events = Array.Empty<LiveEvent>();
|
|
if (!File.Exists(CachePath))
|
|
{
|
|
return false;
|
|
}
|
|
var age = DateTimeOffset.UtcNow - File.GetLastWriteTimeUtc(CachePath);
|
|
if (age > CacheDuration)
|
|
{
|
|
return false;
|
|
}
|
|
try
|
|
{
|
|
string json = File.ReadAllText(CachePath, Encoding.UTF8);
|
|
events = ParseEvents(json);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void SaveCache(string json)
|
|
{
|
|
try
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(CachePath)!);
|
|
File.WriteAllText(CachePath, json, Encoding.UTF8);
|
|
}
|
|
catch
|
|
{
|
|
// Not critical if the cache cannot be persisted.
|
|
}
|
|
}
|
|
|
|
private static async Task<string> DownloadJsonAsync(CancellationToken cancellationToken)
|
|
{
|
|
using var response = await HttpClient.GetAsync(EventsUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
private static IReadOnlyList<LiveEvent> ParseEvents(string json)
|
|
{
|
|
using var document = JsonDocument.Parse(json);
|
|
var results = new List<LiveEvent>();
|
|
foreach (var item in document.RootElement.EnumerateArray())
|
|
{
|
|
string title = item.GetPropertyOrDefault("title");
|
|
string time = item.GetPropertyOrDefault("time");
|
|
string category = item.GetPropertyOrDefault("category");
|
|
string status = item.GetPropertyOrDefault("status");
|
|
string link = NormalizeLink(item.GetPropertyOrDefault("link"));
|
|
string channelName = ExtractChannelName(link);
|
|
long startMillis = ParseEventTime(time);
|
|
|
|
results.Add(new LiveEvent(title, time, category, status, link, channelName, startMillis));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static string NormalizeLink(string link)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(link))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
return link.Replace("global1.php", "global2.php", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static string ExtractChannelName(string link)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(link))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
int index = link.IndexOf("stream=", StringComparison.OrdinalIgnoreCase);
|
|
if (index < 0 || index + 7 >= link.Length)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
return link[(index + 7)..].Replace("_", " ").ToUpperInvariant();
|
|
}
|
|
|
|
private static long ParseEventTime(string time)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(time))
|
|
{
|
|
return -1;
|
|
}
|
|
try
|
|
{
|
|
var parsed = DateTime.ParseExact(time.Trim(), "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None);
|
|
var today = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, EventZone).Date;
|
|
var localCandidate = new DateTime(today.Year, today.Month, today.Day, parsed.Hour, parsed.Minute, 0, DateTimeKind.Unspecified);
|
|
var utcDateTime = TimeZoneInfo.ConvertTimeToUtc(localCandidate, EventZone);
|
|
var start = new DateTimeOffset(utcDateTime, TimeSpan.Zero);
|
|
if (start < DateTimeOffset.UtcNow.AddHours(-12))
|
|
{
|
|
start = start.AddDays(1);
|
|
}
|
|
return start.ToUnixTimeMilliseconds();
|
|
}
|
|
catch
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
private static string GetDataDirectory()
|
|
{
|
|
var folder = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"StreamPlayerDesktop");
|
|
Directory.CreateDirectory(folder);
|
|
return folder;
|
|
}
|
|
|
|
private static HttpClient CreateHttpClient()
|
|
{
|
|
var handler = new HttpClientHandler
|
|
{
|
|
AutomaticDecompression = System.Net.DecompressionMethods.All
|
|
};
|
|
var client = new HttpClient(handler)
|
|
{
|
|
Timeout = TimeSpan.FromSeconds(15)
|
|
};
|
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("StreamPlayerDesktop/1.0");
|
|
client.DefaultRequestHeaders.ConnectionClose = false;
|
|
return client;
|
|
}
|
|
|
|
private static TimeZoneInfo ResolveEventZone()
|
|
{
|
|
string[] candidates = { "America/Argentina/Buenos_Aires", "Argentina Standard Time" };
|
|
foreach (var id in candidates)
|
|
{
|
|
try
|
|
{
|
|
return TimeZoneInfo.FindSystemTimeZoneById(id);
|
|
}
|
|
catch
|
|
{
|
|
// try next
|
|
}
|
|
}
|
|
return TimeZoneInfo.Local;
|
|
}
|
|
}
|