Skip to content

Commit

Permalink
Fix some DateTime and caching bugs (#70)
Browse files Browse the repository at this point in the history
* Fix two DateTime-related bugs

* Restore original data files directory (It's already in use in the deployed app.)

* Don't cache feed rewind info if it is null

* Consolidate file save methods

* Handle bad feed URLs better

* Bump version: 0.5.1
  • Loading branch information
dougwaldron authored Jan 1, 2024
1 parent 220cbed commit 1ab9e3b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ FodyWeavers.xsd

# Data files
.DataFiles/
_DataFiles/

# Environment settings files
appsettings.*.json
Expand Down
15 changes: 10 additions & 5 deletions PodcastRewind/Models/FeedRewindData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
{
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion PodcastRewind/PodcastRewind.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.5.0</Version>
<Version>0.5.1</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
55 changes: 31 additions & 24 deletions PodcastRewind/Services/FeedRewindInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,14 @@ public async Task<Guid> 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
{
Expand All @@ -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<FeedRewindInfo?> 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<FeedRewindInfo?> GetAsync(Guid id) =>
cache.GetOrCreateAsync(id, async entry =>
private async Task<FeedRewindInfo?> 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<FeedRewindInfo>(stream);
}
catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException)
{
return null;
}
});
await using var stream = File.OpenRead(filePath);
return await JsonSerializer.DeserializeAsync<FeedRewindInfo>(stream);
}
catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException)
{
return null;
}
}
}
34 changes: 22 additions & 12 deletions PodcastRewind/Services/SyndicationFeedService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.ServiceModel.Syndication;
using System.Xml;
using Microsoft.Extensions.Caching.Memory;
using Sentry;

namespace PodcastRewind.Services;

Expand All @@ -12,21 +13,30 @@ public interface ISyndicationFeedService
public class SyndicationFeedService(IHttpClientFactory httpClientFactory, IMemoryCache cache)
: ISyndicationFeedService
{
public Task<SyndicationFeed?> GetSyndicationFeedAsync(string url) =>
!Uri.IsWellFormedUriString(url, UriKind.Absolute)
? Task.FromResult<SyndicationFeed?>(null)
: cache.GetOrCreateAsync(url, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(20);
return GetRemoteSyndicationFeedAsync(url);
});
public async Task<SyndicationFeed?> 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<SyndicationFeed> GetRemoteSyndicationFeedAsync(string url)
private async Task<SyndicationFeed?> 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;
}
}
}
2 changes: 1 addition & 1 deletion PodcastRewind/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"DataFilesDirectory": "../.DataFiles",
"DataFilesDirectory": "_DataFiles",
"UseTestData": false,
"Sentry": {
"Environment": "production",
Expand Down

0 comments on commit 1ab9e3b

Please sign in to comment.