diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheService.cs b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheService.cs deleted file mode 100644 index 16ff656..0000000 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheService.cs +++ /dev/null @@ -1,93 +0,0 @@ -using AS1024.GeoFeed.Core.Interfaces; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; - -namespace AS1024.GeoFeed.Core.GeoFeedLocalCache -{ - public class GeoFeedCacheService : IHostedService - { - private readonly ILogger logger; - private readonly IGeoFeedProvider feedProvider; - private readonly IHost host; - - public GeoFeedCacheService(ILogger logger, - IGeoFeedProvider feedProvider, - IHost host) - { - this.logger = logger; - this.feedProvider = feedProvider; - this.host = host; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _ = StartPerioidicSync(cancellationToken); - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public async Task StartPerioidicSync(CancellationToken Token) - { - await DBContextMigrate(); - - List geoFeedCacheEntry = []; - while (!Token.IsCancellationRequested) - { - logger.LogInformation("Running on disk fallback cache process..."); - try - { - using var scope = host.Services.CreateScope(); - using var dbContext = scope.ServiceProvider.GetRequiredService(); - var results = await feedProvider.GetGeoFeedData(); - - results.ForEach(x => - { - geoFeedCacheEntry.Add(new() - { - Prefix = x.Prefix, - GeolocCity = x.GeolocCity, - GeolocCountry = x.GeolocCountry, - GeolocHasLocation = x.GeolocHasLocation, - GeolocPostalCode = x.GeolocPostalCode, - GeolocRegion = x.GeolocRegion - }); - }); - - if (dbContext.GeoFeedCacheEntries.Any()) - { - dbContext.GeoFeedCacheEntries.RemoveRange(dbContext.GeoFeedCacheEntries.ToArray()); - } - await dbContext.AddRangeAsync(geoFeedCacheEntry, Token); - await dbContext.SaveChangesAsync(Token); - } - catch (Exception ex) - { - logger.LogWarning("On disk cache failed to run. Waiting on 30 minutes before retry..."); - } - await Task.Delay(TimeSpan.FromMinutes(30)); - } - - return false; - } - - private async Task DBContextMigrate() - { - using IServiceScope scope = host.Services.CreateScope(); - using GeoFeedCacheDbContext? dbContext = - scope.ServiceProvider.GetService(); -#pragma warning disable CS8602 // Dereference of a possibly null reference. - if (dbContext.Database.GetPendingMigrations().Any()) { - await dbContext.Database.MigrateAsync(); - } -#pragma warning restore CS8602 // Dereference of a possibly null reference. - - } - } -} \ No newline at end of file diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheDbContext.cs b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheDbContext.cs similarity index 84% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheDbContext.cs rename to AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheDbContext.cs index 460f75e..ff17f44 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheDbContext.cs +++ b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheDbContext.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; -namespace AS1024.GeoFeed.Core.GeoFeedLocalCache +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache { public class GeoFeedCacheDbContext : DbContext { diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheEntry.cs b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheEntry.cs similarity index 77% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheEntry.cs rename to AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheEntry.cs index 3032f2f..142e6b0 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedCacheEntry.cs +++ b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheEntry.cs @@ -1,7 +1,7 @@ using AS1024.GeoFeed.Models; using System.ComponentModel.DataAnnotations; -namespace AS1024.GeoFeed.Core.GeoFeedLocalCache +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache { public class GeoFeedCacheEntry : IPGeoFeed { diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheService.cs b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheService.cs new file mode 100644 index 0000000..f516ea0 --- /dev/null +++ b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedCacheService.cs @@ -0,0 +1,58 @@ +using AS1024.GeoFeed.Core.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache +{ + public class GeoFeedCacheService : IHostedService + { + private readonly ILogger logger; + private readonly IGeoFeedProvider feedProvider; + private readonly IHost host; + private readonly IGeoFeedPersistentCacheProvider persistentCacheProvider; + + public GeoFeedCacheService(ILogger logger, + IGeoFeedProvider feedProvider, + IHost host, + IGeoFeedPersistentCacheProvider persistentCacheProvider) + { + this.logger = logger; + this.feedProvider = feedProvider; + this.host = host; + this.persistentCacheProvider = persistentCacheProvider; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _ = StartPerioidicSync(cancellationToken); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public async Task StartPerioidicSync(CancellationToken Token) + { + while (!Token.IsCancellationRequested) + { + try + { + var results = await feedProvider.GetGeoFeedData(); + await persistentCacheProvider.CacheGeoFeed(results); + } + catch (Exception) + { + logger.LogWarning("On disk cache failed to run. Waiting on 30 minutes before retry..."); + } + await Task.Delay(TimeSpan.FromMinutes(30)); + } + + return false; + } + } +} \ No newline at end of file diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedDesignTimeMigration.cs b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedDesignTimeMigration.cs similarity index 89% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedDesignTimeMigration.cs rename to AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedDesignTimeMigration.cs index 1207fc2..90d896e 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedLocalCache/GeoFeedDesignTimeMigration.cs +++ b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedDesignTimeMigration.cs @@ -1,7 +1,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -namespace AS1024.GeoFeed.Core.GeoFeedLocalCache +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache + { public class GeoFeedDesignTimeMigration : IDesignTimeDbContextFactory { diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedSqliteCache.cs b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedSqliteCache.cs new file mode 100644 index 0000000..c625536 --- /dev/null +++ b/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedSqliteLocalCache/GeoFeedSqliteCache.cs @@ -0,0 +1,72 @@ +using AS1024.GeoFeed.Core.Interfaces; +using AS1024.GeoFeed.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using AS1024.GeoFeed.Core.Tools; + +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache +{ + public class GeoFeedSqliteCache : IGeoFeedPersistentCacheProvider + { + protected readonly GeoFeedCacheDbContext dbContext; + private readonly IGeoFeedProvider feedProvider; + + public GeoFeedSqliteCache(GeoFeedCacheDbContext geoFeedCacheDb, + IHost host, + IGeoFeedProvider provider) + { + dbContext = geoFeedCacheDb; + feedProvider = provider; + } + + public string ProviderName => "sqlite"; + + public async Task CacheGeoFeed(IList pGeoFeeds) + { + await DBContextMigrate(); + List geoFeedCacheEntry = []; + + var results = pGeoFeeds.ToList(); + + results.ForEach(x => + { + geoFeedCacheEntry.Add(new() + { + Prefix = x.Prefix, + GeolocCity = x.GeolocCity, + GeolocCountry = x.GeolocCountry, + GeolocHasLocation = x.GeolocHasLocation, + GeolocPostalCode = x.GeolocPostalCode, + GeolocRegion = x.GeolocRegion + }); + }); + + if (dbContext.GeoFeedCacheEntries.Any()) + { + dbContext.GeoFeedCacheEntries.RemoveRange(dbContext.GeoFeedCacheEntries.ToArray()); + } + await dbContext.AddRangeAsync(geoFeedCacheEntry); + await dbContext.SaveChangesAsync(); + + return true; + } + + public string GetGeoFeed() + { + var results = + dbContext.GeoFeedCacheEntries.ToList(); + List cachedData = []; + results.ForEach(cachedData.Add); + + return cachedData.ToGeoFeedCsv(); + } + + private async Task DBContextMigrate() + { + if (dbContext.Database.GetPendingMigrations().Any()) + { + await dbContext.Database.MigrateAsync(); + } + } + } +} diff --git a/AS1024.GeoFeed.Core.SqliteGeoFeedCache/AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj new file mode 100644 index 0000000..cbc1240 --- /dev/null +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedCacheDbContext.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedCacheDbContext.cs new file mode 100644 index 0000000..ff17f44 --- /dev/null +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedCacheDbContext.cs @@ -0,0 +1,15 @@ +using Microsoft.EntityFrameworkCore; + +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache +{ + public class GeoFeedCacheDbContext : DbContext + { + public GeoFeedCacheDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet GeoFeedCacheEntries { get; set; } + } + +} \ No newline at end of file diff --git a/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedCacheEntry.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedCacheEntry.cs new file mode 100644 index 0000000..142e6b0 --- /dev/null +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedCacheEntry.cs @@ -0,0 +1,12 @@ +using AS1024.GeoFeed.Models; +using System.ComponentModel.DataAnnotations; + +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache +{ + public class GeoFeedCacheEntry : IPGeoFeed + { + [Key] + public int Id { get; set; } + } + +} \ No newline at end of file diff --git a/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedDesignTimeMigration.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedDesignTimeMigration.cs new file mode 100644 index 0000000..90d896e --- /dev/null +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedDesignTimeMigration.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache + +{ + public class GeoFeedDesignTimeMigration : IDesignTimeDbContextFactory + { + public GeoFeedCacheDbContext CreateDbContext(string[] args) + { + var builder = new DbContextOptionsBuilder(); + builder.UseSqlite("Data Source=migratedb.db"); + return new GeoFeedCacheDbContext(builder.Options); + } + } + +} \ No newline at end of file diff --git a/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedSqliteCache.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedSqliteCache.cs new file mode 100644 index 0000000..c625536 --- /dev/null +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/GeoFeedSqliteCache.cs @@ -0,0 +1,72 @@ +using AS1024.GeoFeed.Core.Interfaces; +using AS1024.GeoFeed.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Hosting; +using AS1024.GeoFeed.Core.Tools; + +namespace AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache +{ + public class GeoFeedSqliteCache : IGeoFeedPersistentCacheProvider + { + protected readonly GeoFeedCacheDbContext dbContext; + private readonly IGeoFeedProvider feedProvider; + + public GeoFeedSqliteCache(GeoFeedCacheDbContext geoFeedCacheDb, + IHost host, + IGeoFeedProvider provider) + { + dbContext = geoFeedCacheDb; + feedProvider = provider; + } + + public string ProviderName => "sqlite"; + + public async Task CacheGeoFeed(IList pGeoFeeds) + { + await DBContextMigrate(); + List geoFeedCacheEntry = []; + + var results = pGeoFeeds.ToList(); + + results.ForEach(x => + { + geoFeedCacheEntry.Add(new() + { + Prefix = x.Prefix, + GeolocCity = x.GeolocCity, + GeolocCountry = x.GeolocCountry, + GeolocHasLocation = x.GeolocHasLocation, + GeolocPostalCode = x.GeolocPostalCode, + GeolocRegion = x.GeolocRegion + }); + }); + + if (dbContext.GeoFeedCacheEntries.Any()) + { + dbContext.GeoFeedCacheEntries.RemoveRange(dbContext.GeoFeedCacheEntries.ToArray()); + } + await dbContext.AddRangeAsync(geoFeedCacheEntry); + await dbContext.SaveChangesAsync(); + + return true; + } + + public string GetGeoFeed() + { + var results = + dbContext.GeoFeedCacheEntries.ToList(); + List cachedData = []; + results.ForEach(cachedData.Add); + + return cachedData.ToGeoFeedCsv(); + } + + private async Task DBContextMigrate() + { + if (dbContext.Database.GetPendingMigrations().Any()) + { + await dbContext.Database.MigrateAsync(); + } + } + } +} diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/20240113204143_InitialMigration.Designer.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.Designer.cs similarity index 85% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/20240113204143_InitialMigration.Designer.cs rename to AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.Designer.cs index 57f28fc..1aba4ff 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/20240113204143_InitialMigration.Designer.cs +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.Designer.cs @@ -1,6 +1,6 @@ // using System; -using AS1024.GeoFeed.Core.GeoFeedLocalCache; +using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -8,10 +8,10 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace AS1024.GeoFeed.Core.Migrations +namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations { [DbContext(typeof(GeoFeedCacheDbContext))] - [Migration("20240113204143_InitialMigration")] + [Migration("20240114002908_InitialMigration")] partial class InitialMigration { /// @@ -20,7 +20,7 @@ namespace AS1024.GeoFeed.Core.Migrations #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); - modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedLocalCache.GeoFeedCacheEntry", b => + modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache.GeoFeedCacheEntry", b => { b.Property("Id") .ValueGeneratedOnAdd() diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/20240113204143_InitialMigration.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.cs similarity index 96% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/20240113204143_InitialMigration.cs rename to AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.cs index 4662ece..35b52bd 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/20240113204143_InitialMigration.cs +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/20240114002908_InitialMigration.cs @@ -2,7 +2,7 @@ #nullable disable -namespace AS1024.GeoFeed.Core.Migrations +namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations { /// public partial class InitialMigration : Migration diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/GeoFeedCacheDbContextModelSnapshot.cs b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/GeoFeedCacheDbContextModelSnapshot.cs similarity index 87% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/GeoFeedCacheDbContextModelSnapshot.cs rename to AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/GeoFeedCacheDbContextModelSnapshot.cs index 08ab2c9..875969a 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/Migrations/GeoFeedCacheDbContextModelSnapshot.cs +++ b/AS1024.GeoFeed.Core.SqliteGeoFeedCache/Migrations/GeoFeedCacheDbContextModelSnapshot.cs @@ -1,13 +1,13 @@ // using System; -using AS1024.GeoFeed.Core.GeoFeedLocalCache; +using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace AS1024.GeoFeed.Core.Migrations +namespace AS1024.GeoFeed.Core.SqliteGeoFeedCache.Migrations { [DbContext(typeof(GeoFeedCacheDbContext))] partial class GeoFeedCacheDbContextModelSnapshot : ModelSnapshot @@ -17,7 +17,7 @@ namespace AS1024.GeoFeed.Core.Migrations #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); - modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedLocalCache.GeoFeedCacheEntry", b => + modelBuilder.Entity("AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache.GeoFeedCacheEntry", b => { b.Property("Id") .ValueGeneratedOnAdd() diff --git a/AS1024.GeoFeed.Core/AS1024.GeoFeed.Core.csproj b/AS1024.GeoFeed.Core/AS1024.GeoFeed.Core.csproj index fd32e69..c4da98b 100644 --- a/AS1024.GeoFeed.Core/AS1024.GeoFeed.Core.csproj +++ b/AS1024.GeoFeed.Core/AS1024.GeoFeed.Core.csproj @@ -7,6 +7,7 @@ + diff --git a/AS1024.GeoFeed.Core/CacheService/GeoFeedCacheService.cs b/AS1024.GeoFeed.Core/CacheService/GeoFeedCacheService.cs new file mode 100644 index 0000000..9572532 --- /dev/null +++ b/AS1024.GeoFeed.Core/CacheService/GeoFeedCacheService.cs @@ -0,0 +1,58 @@ +using AS1024.GeoFeed.Core.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace AS1024.GeoFeed.Core.CacheService +{ + public class GeoFeedCacheService : IHostedService + { + private readonly ILogger logger; + private readonly IGeoFeedProvider feedProvider; + private readonly IHost host; + + public GeoFeedCacheService(ILogger logger, + IGeoFeedProvider feedProvider, + IHost host) + { + this.logger = logger; + this.feedProvider = feedProvider; + this.host = host; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _ = StartPerioidicSync(cancellationToken); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public async Task StartPerioidicSync(CancellationToken Token) + { + while (!Token.IsCancellationRequested) + { + try + { + var scope = host.Services.CreateScope(); + + var persistentCacheProvider = + scope.ServiceProvider.GetRequiredService(); + + var results = await feedProvider.GetGeoFeedData(); + await persistentCacheProvider.CacheGeoFeed(results); + } + catch (Exception) + { + logger.LogWarning("On disk cache failed to run. Waiting on 30 minutes before retry..."); + } + await Task.Delay(TimeSpan.FromMinutes(30)); + } + + return false; + } + } +} \ No newline at end of file diff --git a/AS1024.GeoFeed.Core/CacheService/GeoFeedLocalFileCache.cs b/AS1024.GeoFeed.Core/CacheService/GeoFeedLocalFileCache.cs new file mode 100644 index 0000000..b258ee6 --- /dev/null +++ b/AS1024.GeoFeed.Core/CacheService/GeoFeedLocalFileCache.cs @@ -0,0 +1,55 @@ +using AS1024.GeoFeed.Core.Interfaces; +using AS1024.GeoFeed.Models; +using AS1024.GeoFeed.Core.Tools; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace AS1024.GeoFeed.Core.CacheService +{ + public class GeoFeedLocalFileCache : IGeoFeedPersistentCacheProvider + { + private readonly IConfiguration configuration; + private readonly ILogger logger; + + public GeoFeedLocalFileCache(IConfiguration _configuration, + ILogger logger) + { + configuration = _configuration; + this.logger = logger; + } + + string IGeoFeedPersistentCacheProvider.ProviderName => "file"; + + Task IGeoFeedPersistentCacheProvider.CacheGeoFeed(IList pGeoFeeds) + { + string? tempPath = GetTempPath(); + logger.LogInformation($"Writing geofeed data to: {tempPath}"); + File.WriteAllText(tempPath, pGeoFeeds.ToList(). + ToGeoFeedCsv()); + + return Task.FromResult(true); + } + + private string GetTempPath() + { + string? tempPath = Path.Combine(Path.GetTempPath(), "geoFeedCache.csv"); + + logger.LogInformation($"Getting GeoFeed data from: {tempPath}"); + + if (!string.IsNullOrEmpty(configuration["TempCache"])) + tempPath = configuration["TempCache"]; + return tempPath; + } + + string IGeoFeedPersistentCacheProvider.GetGeoFeed() + { + return File.ReadAllText(GetTempPath()); + } + } +} diff --git a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedPreloader/PreloadGeoFeed.cs b/AS1024.GeoFeed.Core/GeoFeedPreloader/PreloadGeoFeed.cs similarity index 94% rename from AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedPreloader/PreloadGeoFeed.cs rename to AS1024.GeoFeed.Core/GeoFeedPreloader/PreloadGeoFeed.cs index ee3e9c7..d0aa1e9 100644 --- a/AS1024.GeoFeed.Core.GeoFeedLocalCache/GeoFeedPreloader/PreloadGeoFeed.cs +++ b/AS1024.GeoFeed.Core/GeoFeedPreloader/PreloadGeoFeed.cs @@ -1,4 +1,5 @@ using AS1024.GeoFeed.Core.Interfaces; +using AS1024.GeoFeed.Models; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -40,7 +41,7 @@ namespace AS1024.GeoFeed.Core.GeoFeedPreloader private async Task StartPreLoad() { logger.LogInformation("Preloading GeoFeed data in memory..."); - List feed = await provider.GetGeoFeedData(); + List feed = await provider.GetGeoFeedData(); MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(45)); memoryCache.Set(GeoFeedCacheKey, feed, cacheEntryOptions); } diff --git a/AS1024.GeoFeed.Core/Interfaces/IGeoFeedCacheProvider.cs b/AS1024.GeoFeed.Core/Interfaces/IGeoFeedCacheProvider.cs new file mode 100644 index 0000000..9a0f69b --- /dev/null +++ b/AS1024.GeoFeed.Core/Interfaces/IGeoFeedCacheProvider.cs @@ -0,0 +1,31 @@ +using AS1024.GeoFeed.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AS1024.GeoFeed.Core.Interfaces +{ + /// + /// Represents a persistent cache GeoFeed provider + /// + public interface IGeoFeedPersistentCacheProvider + { + /// + /// Name of the provider + /// + public string ProviderName { get; } + /// + /// Returns the GeoFeed + /// + /// String of the CSV geofeed + public string GetGeoFeed(); + /// + /// Stores the GeoFeed in the cache backend + /// + /// GeoFeed retrieved from the backend + /// + public Task CacheGeoFeed(IList pGeoFeeds); + } +} diff --git a/AS1024.GeoFeed.Core/Interfaces/GeoFeedProvider.cs b/AS1024.GeoFeed.Core/Interfaces/IGeoFeedProvider.cs similarity index 100% rename from AS1024.GeoFeed.Core/Interfaces/GeoFeedProvider.cs rename to AS1024.GeoFeed.Core/Interfaces/IGeoFeedProvider.cs diff --git a/AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj b/AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj index e2a9130..53a525c 100644 --- a/AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj +++ b/AS1024.GeoFeed.MinimalAPI/AS1024.GeoFeed.MinimalAPI.csproj @@ -13,4 +13,8 @@ + + + + diff --git a/AS1024.GeoFeed.MinimalAPI/GeoFeedAoTProvider.cs b/AS1024.GeoFeed.MinimalAPI/GeoFeedAoTProvider.cs new file mode 100644 index 0000000..9f16448 --- /dev/null +++ b/AS1024.GeoFeed.MinimalAPI/GeoFeedAoTProvider.cs @@ -0,0 +1,31 @@ +using AS1024.GeoFeed.Core.GeoFeedProviders; +using AS1024.GeoFeed.Core.Interfaces; +using AS1024.GeoFeed.Models; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using System.Threading.Tasks; + +namespace AS1024.GeoFeed.MinimalAPI +{ + internal class NetboxAoTGeoFeedProvider : NetBoxGeoFeedProvider, IGeoFeedProvider + { + private readonly JsonSerializerOptions appJsonSerializer; + + public NetboxAoTGeoFeedProvider(IConfiguration configuration, + ILogger logger, + IHttpClientFactory httpClientFactory) + : base(configuration, logger, httpClientFactory) + { + } + + protected override NetboxData? DeserializeJsonData(string stringResult) + { + return JsonSerializer.Deserialize(stringResult, AppJsonSerializerContext.Default.NetboxData); + } + } +} diff --git a/AS1024.GeoFeed.MinimalAPI/Program.cs b/AS1024.GeoFeed.MinimalAPI/Program.cs index 7f35304..4bc44c5 100644 --- a/AS1024.GeoFeed.MinimalAPI/Program.cs +++ b/AS1024.GeoFeed.MinimalAPI/Program.cs @@ -1,3 +1,9 @@ +using AS1024.GeoFeed.Core.GeoFeedProviders; +using AS1024.GeoFeed.Core.Interfaces; +using AS1024.GeoFeed.Core.Tools; +using AS1024.GeoFeed.Models; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; using System.Text.Json.Serialization; namespace AS1024.GeoFeed.MinimalAPI @@ -7,12 +13,39 @@ namespace AS1024.GeoFeed.MinimalAPI public static void Main(string[] args) { var builder = WebApplication.CreateSlimBuilder(args); - + builder.Services.AddTransient(); + builder.Services.AddMemoryCache(); + builder.Services.AddLogging(); + builder.Services.AddHttpClient(); + builder.Services.ConfigureHttpJsonOptions(options => { + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); + }); var app = builder.Build(); - var geoFeed = app.MapGroup("/geofeed.csv"); + var geoFeed = app.Map("/geofeed.csv", async (IGeoFeedProvider provider, ILogger logger) => { + try + { + var results = + await provider.GetGeoFeedData(); + return results.ToGeoFeedCsv(); + } + catch (Exception ex) + { + logger.LogError($"Error: {ex}"); + } + return ""; + }); app.Run(); } } + [JsonSerializable(typeof(NetboxData))] + [JsonSerializable(typeof(Result))] + [JsonSerializable(typeof(CustomFields))] + [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)] + internal partial class AppJsonSerializerContext : JsonSerializerContext + { + + } + } diff --git a/AS1024.GeoFeed.sln b/AS1024.GeoFeed.sln index cf44d7b..2fdd42c 100644 --- a/AS1024.GeoFeed.sln +++ b/AS1024.GeoFeed.sln @@ -14,7 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.Core", "AS10 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AS1024.GeoFeed.MinimalAPI", "AS1024.GeoFeed.MinimalAPI\AS1024.GeoFeed.MinimalAPI.csproj", "{36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed.Core.GeoFeedLocalCache", "AS1024.GeoFeed.Core.GeoFeedLocalCache\AS1024.GeoFeed.Core.GeoFeedLocalCache.csproj", "{C474F55D-AE8E-4DF3-A241-FB017551C74A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AS1024.GeoFeed.Core.SqliteGeoFeedCache", "AS1024.GeoFeed.Core.SqliteGeoFeedCache\AS1024.GeoFeed.Core.SqliteGeoFeedCache.csproj", "{3459BB31-FA7A-44D1-872D-C5338ACFBF80}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -38,10 +38,10 @@ Global {36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU {36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU {36F2958C-8D0E-463B-9BF3-D6E55E6FC0B8}.Release|Any CPU.Build.0 = Release|Any CPU - {C474F55D-AE8E-4DF3-A241-FB017551C74A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C474F55D-AE8E-4DF3-A241-FB017551C74A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C474F55D-AE8E-4DF3-A241-FB017551C74A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C474F55D-AE8E-4DF3-A241-FB017551C74A}.Release|Any CPU.Build.0 = Release|Any CPU + {3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3459BB31-FA7A-44D1-872D-C5338ACFBF80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AS1024.GeoFeed/AS1024.GeoFeed.csproj b/AS1024.GeoFeed/AS1024.GeoFeed.csproj index e739f05..258f3ef 100644 --- a/AS1024.GeoFeed/AS1024.GeoFeed.csproj +++ b/AS1024.GeoFeed/AS1024.GeoFeed.csproj @@ -22,7 +22,7 @@ - + diff --git a/AS1024.GeoFeed/Controllers/GeofeedController.cs b/AS1024.GeoFeed/Controllers/GeofeedController.cs index f9aa0a9..62c8ace 100644 --- a/AS1024.GeoFeed/Controllers/GeofeedController.cs +++ b/AS1024.GeoFeed/Controllers/GeofeedController.cs @@ -3,7 +3,6 @@ using AS1024.GeoFeed.Core.Interfaces; using Microsoft.Extensions.Caching.Memory; using AS1024.GeoFeed.Models; using System.Text; -using AS1024.GeoFeed.Core.GeoFeedLocalCache; using AS1024.GeoFeed.Core.Tools; namespace AS1024.GeoFeed.Controllers @@ -18,16 +17,16 @@ namespace AS1024.GeoFeed.Controllers private readonly IMemoryCache memoryCache; private readonly IWebHostEnvironment environment; private readonly ILogger logger; - private readonly GeoFeedCacheDbContext dbContext; + private readonly IGeoFeedPersistentCacheProvider geoFeedPersistentCache; private const string GeoFeedCacheKey = "GeoFeedData"; public GeofeedController(IGeoFeedProvider builder, IMemoryCache memoryCache, IWebHostEnvironment environment, ILogger logger, - GeoFeedCacheDbContext dbContext) { + IGeoFeedPersistentCacheProvider geoFeedPersistentCache) { this.logger = logger; - this.dbContext = dbContext; + this.geoFeedPersistentCache = geoFeedPersistentCache; this.builder = builder; this.memoryCache = memoryCache; this.environment = environment; @@ -54,10 +53,7 @@ namespace AS1024.GeoFeed.Controllers } catch (HttpRequestException ex) { logger.LogWarning($"Temporary failure of retrieving GeoData from upstream. {ex}"); - var results = - dbContext.GeoFeedCacheEntries.ToList(); - List cachedData = []; - results.ForEach(cachedData.Add); + var cachedData = geoFeedPersistentCache.GetGeoFeed(); return ReturnFile(cachedData); } @@ -73,6 +69,12 @@ namespace AS1024.GeoFeed.Controllers private IActionResult ReturnFile(List? feed) { string csvContent = feed.ToGeoFeedCsv(); // Assuming ToGeoFeedCsv() returns a string in CSV format. + return ReturnFile(csvContent); + } + + [NonAction] + private IActionResult ReturnFile(string csvContent) + { byte[] contentBytes = Encoding.UTF8.GetBytes(csvContent); string contentType = "text/csv"; diff --git a/AS1024.GeoFeed/Program.cs b/AS1024.GeoFeed/Program.cs index a203edf..3f6a36b 100644 --- a/AS1024.GeoFeed/Program.cs +++ b/AS1024.GeoFeed/Program.cs @@ -1,8 +1,9 @@ using AS1024.GeoFeed.Core.Interfaces; using AS1024.GeoFeed.Core.GeoFeedPreloader; -using AS1024.GeoFeed.Core.GeoFeedLocalCache; using AS1024.GeoFeed.Core.GeoFeedProviders; using Microsoft.EntityFrameworkCore; +using AS1024.GeoFeed.Core.GeoFeedSqliteLocalCache; +using AS1024.GeoFeed.Core.CacheService; namespace AS1024.GeoFeed { @@ -14,11 +15,13 @@ namespace AS1024.GeoFeed builder.Services.AddHostedService(); builder.Services.AddTransient(); + builder.Services.AddDbContext( + options => + { + options.UseSqlite(builder.Configuration.GetConnectionString("LocalFeedCache")); + }); + builder.Services.AddScoped(); builder.Services.AddHostedService(); - builder.Services.AddDbContext(options => - { - options.UseSqlite(builder.Configuration.GetConnectionString("LocalFeedCache")); - }); builder.Services.AddHttpClient(); builder.Services.AddMemoryCache(); // Add services to the container.