diff --git a/.gitignore b/.gitignore index a9d1153..d195c27 100644 --- a/.gitignore +++ b/.gitignore @@ -540,6 +540,7 @@ FodyWeavers.xsd # Data files .DataFiles/ +_DataFiles/ # Environment settings files appsettings.*.json diff --git a/PodcastRewind/Models/FeedRewindData.cs b/PodcastRewind/Models/FeedRewindData.cs index 00bb2e9..4576887 100644 --- a/PodcastRewind/Models/FeedRewindData.cs +++ b/PodcastRewind/Models/FeedRewindData.cs @@ -51,7 +51,9 @@ private void LoadRewoundFeed() RewoundEntries = originalFeed.Items.OrderBy(item => item.PublishDate).ToList(); var feedItemsCount = RewoundEntries.Count; var keyIndex = RewoundEntries.FindIndex(item => item.Id == feedRewindInfo.KeyEntryId); - var dateOfFirstEntry = feedRewindInfo.DateOfKeyEntry.AddDays(-feedRewindInfo.Interval * (keyIndex)); + var dateOfFirstEntry = DateTime.SpecifyKind( + feedRewindInfo.DateOfKeyEntry.AddDays(-feedRewindInfo.Interval * keyIndex), + DateTimeKind.Unspecified); for (var i = 0; i < feedItemsCount; i++) { @@ -88,16 +90,19 @@ private void LoadRewoundFeed() }; RewoundFeed.Description = new TextSyndicationContent(newDescription, descriptionType); - RewoundFeed.Items = RewoundEntries.Where(item => item.PublishDate <= DateTimeOffset.Now) - .OrderByDescending(item => item.PublishDate); - MostRecentRewoundFeedEntryDate = RewoundFeed.Items.First().PublishDate; + RewoundFeed.Items = RewoundEntries + .Where(item => item.PublishDate <= DateTimeOffset.Now) + .OrderByDescending(item => item.PublishDate).ToList(); + if (RewoundFeed.Items.Any()) + MostRecentRewoundFeedEntryDate = RewoundFeed.Items.First().PublishDate; } private void LoadScheduledFeed() { if (string.IsNullOrEmpty(feedRewindInfo.FeedUrl)) return; ScheduledFeed = originalFeed.Clone(true); - ScheduledFeed.Items = RewoundEntries.Where(item => item.PublishDate > DateTimeOffset.Now) + ScheduledFeed.Items = RewoundEntries + .Where(item => item.PublishDate > DateTimeOffset.Now) .OrderBy(item => item.PublishDate); } diff --git a/PodcastRewind/PodcastRewind.csproj b/PodcastRewind/PodcastRewind.csproj index 65b01e4..33dccc2 100644 --- a/PodcastRewind/PodcastRewind.csproj +++ b/PodcastRewind/PodcastRewind.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.5.0 + 0.5.1 diff --git a/PodcastRewind/Services/FeedRewindInfoRepository.cs b/PodcastRewind/Services/FeedRewindInfoRepository.cs index 1b9db81..ee2d603 100644 --- a/PodcastRewind/Services/FeedRewindInfoRepository.cs +++ b/PodcastRewind/Services/FeedRewindInfoRepository.cs @@ -34,19 +34,14 @@ public async Task SaveAsync(CreateFeedRewindInfoDto create) Interval = create.Interval, }; - var filePath = Path.Combine(_dataFilesDirectory, string.Concat(id.ToString(), ".json")); - Directory.CreateDirectory(_dataFilesDirectory); - await using var stream = File.Create(filePath); - await JsonSerializer.SerializeAsync(stream, feedRewind); - - cache.Set(id, feedRewind, CacheEntryOptions); + await SaveFeedRewindInfoToFileAsync(id, feedRewind); return id; } public async Task UpdateAsync(EditFeedRewindInfoDto edit) { - var original = await GetAsync(edit.Id); - if (original is null) throw new ArgumentException($"Item {edit.Id} does not exist.", nameof(edit)); + var original = await GetAsync(edit.Id) + ?? throw new ArgumentException($"Item {edit.Id} does not exist.", nameof(edit)); var feedRewind = new FeedRewindInfo { @@ -58,25 +53,37 @@ public async Task UpdateAsync(EditFeedRewindInfoDto edit) CreatedOn = original.CreatedOn, }; - var filePath = Path.Combine(_dataFilesDirectory, string.Concat(feedRewind.Id.ToString(), ".json")); + await SaveFeedRewindInfoToFileAsync(edit.Id, feedRewind); + } + + public async Task GetAsync(Guid id) + { + if (cache.TryGetValue(id, out FeedRewindInfo? info)) return info; + info = await LoadFeedRewindInfoFromFileAsync(id); + if (info != null) cache.Set(id, info, CacheEntryOptions); + return info; + } + + private async Task SaveFeedRewindInfoToFileAsync(Guid id, FeedRewindInfo feedRewind) + { + var filePath = Path.Combine(_dataFilesDirectory, string.Concat(id.ToString(), ".json")); + Directory.CreateDirectory(_dataFilesDirectory); await using var stream = File.Create(filePath); await JsonSerializer.SerializeAsync(stream, feedRewind); - cache.Set(edit.Id, feedRewind, CacheEntryOptions); + cache.Set(id, feedRewind, CacheEntryOptions); } - public Task GetAsync(Guid id) => - cache.GetOrCreateAsync(id, async entry => + private async Task LoadFeedRewindInfoFromFileAsync(Guid id) + { + var filePath = Path.Combine(_dataFilesDirectory, string.Concat(id.ToString(), ".json")); + try { - entry.SetOptions(CacheEntryOptions); - var filePath = Path.Combine(_dataFilesDirectory, string.Concat(id.ToString(), ".json")); - try - { - await using var stream = File.OpenRead(filePath); - return await JsonSerializer.DeserializeAsync(stream); - } - catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException) - { - return null; - } - }); + await using var stream = File.OpenRead(filePath); + return await JsonSerializer.DeserializeAsync(stream); + } + catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException) + { + return null; + } + } } diff --git a/PodcastRewind/Services/SyndicationFeedService.cs b/PodcastRewind/Services/SyndicationFeedService.cs index 189943d..ec9dbce 100644 --- a/PodcastRewind/Services/SyndicationFeedService.cs +++ b/PodcastRewind/Services/SyndicationFeedService.cs @@ -1,6 +1,7 @@ using System.ServiceModel.Syndication; using System.Xml; using Microsoft.Extensions.Caching.Memory; +using Sentry; namespace PodcastRewind.Services; @@ -12,21 +13,30 @@ public interface ISyndicationFeedService public class SyndicationFeedService(IHttpClientFactory httpClientFactory, IMemoryCache cache) : ISyndicationFeedService { - public Task GetSyndicationFeedAsync(string url) => - !Uri.IsWellFormedUriString(url, UriKind.Absolute) - ? Task.FromResult(null) - : cache.GetOrCreateAsync(url, entry => - { - entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(20); - return GetRemoteSyndicationFeedAsync(url); - }); + public async Task GetSyndicationFeedAsync(string url) + { + if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) return null; + if (cache.TryGetValue(url, out SyndicationFeed? feed)) return feed; + feed = await GetRemoteSyndicationFeedAsync(url); + if (feed != null) + cache.Set(url, feed, new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromHours(20))); + return feed; + } - private async Task GetRemoteSyndicationFeedAsync(string url) + private async Task GetRemoteSyndicationFeedAsync(string url) { var client = httpClientFactory.CreateClient("Polly"); client.DefaultRequestHeaders.Add("user-agent", "PodcastRewind/1.0"); - await using var stream = await client.GetStreamAsync(url); - using var xmlReader = XmlReader.Create(stream); - return SyndicationFeed.Load(xmlReader); + try + { + await using var stream = await client.GetStreamAsync(url); + using var xmlReader = XmlReader.Create(stream); + return SyndicationFeed.Load(xmlReader); + } + catch (HttpRequestException e) + { + SentrySdk.CaptureException(e); + return null; + } } } diff --git a/PodcastRewind/appsettings.json b/PodcastRewind/appsettings.json index 6055fd0..fc7aac9 100644 --- a/PodcastRewind/appsettings.json +++ b/PodcastRewind/appsettings.json @@ -1,5 +1,5 @@ { - "DataFilesDirectory": "../.DataFiles", + "DataFilesDirectory": "_DataFiles", "UseTestData": false, "Sentry": { "Environment": "production",