Working on feed caching & models.

This commit is contained in:
Max 2023-05-12 23:58:49 +02:00
parent fcda58d30f
commit 87f46e2178
14 changed files with 188 additions and 105 deletions

42
SharpRss/FeedCache.cs Normal file
View File

@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CodeHollow.FeedReader;
using Serilog;
using ToolQit.Extensions;
namespace SharpRss
{
/// <summary>
/// Global memory feed cache.
/// </summary>
public static class FeedCache
{
private static readonly Dictionary<string, Feed> CachedFeeds = new Dictionary<string, Feed>();
public static async Task<Feed> GetFeed(string urlKey)
{
Log.Verbose("Request for: {UrlKey}", urlKey);
if (urlKey.IsNullEmptyWhiteSpace())
{
Log.Error("RSS Url is empty!");
return new Feed();
}
if (CachedFeeds.TryGetValue(urlKey, out Feed? fModel))
return fModel;
string feedUrl;
var urls = await FeedReader.GetFeedUrlsFromUrlAsync(urlKey);
if (!urls.Any())
feedUrl = urlKey;
else
feedUrl = urls.First().Url;
Feed feed = await FeedReader.ReadAsync(feedUrl);
if (feed == null)
Log.Warning("Could not get feed: {FeedUrl}", feedUrl);
CachedFeeds.Add(urlKey, feed);
return feed;
}
}
}

View File

@ -1,15 +1,26 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text;
namespace SharpRss.Models namespace SharpRss.Models
{ {
/// <summary> /// <summary>
/// To store and load data from file/database /// To store and load data from file/database
/// </summary> /// </summary>
public class CategoryModel public class CategoryModel : IGuideItem
{ {
public CategoryModel(CategoryModel model)
{
Name = model.Name;
Feeds = model.Feeds;
}
public CategoryModel(string name, HashSet<FeedModel> feeds)
{
Name = name;
Feeds = feeds;
}
public string Name { get; set; } public string Name { get; set; }
public HashSet<FeedModel> Feeds { get; set; } public HashSet<FeedModel> Feeds { get; set; }
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
} }
} }

View File

@ -1,15 +1,43 @@
using System; using System.Dynamic;
using System.Collections.Generic; using System.Linq;
using System.Text; using System.Threading.Tasks;
using CodeHollow.FeedReader;
using Serilog;
using ToolQit.Extensions;
namespace SharpRss.Models namespace SharpRss.Models
{ {
public class FeedModel public class FeedModel : IGuideItem
{ {
public string Type { get; set; } public FeedModel(string rssUrl)
public string Text { get; set; } {
public string Title { get; set; } _rssUrl = rssUrl;
public string XmlUrl { get; set; } Task.Run(async () => Base = await FeedCache.GetFeed(_rssUrl));
public string HtmlUrl { get; set; } }
public Feed Base { get; private set; }
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
public bool IsReady { get; private set; }
private readonly string _rssUrl;
/*public async void Load(bool reload = false)
{
if (Base != null && !reload || !IsReady) return;
if (_rssUrl.IsNullEmptyWhiteSpace())
{
Log.Error("RSS Url is empty!");
return;
}
IsReady = false;
string feedUrl;
var urls = await FeedReader.GetFeedUrlsFromUrlAsync(_rssUrl);
if (!urls.Any())
feedUrl = _rssUrl;
else
feedUrl = urls.First().Url;
Log.Verbose("Creating feed: {FeedUrl}", feedUrl);
Base = await FeedReader.ReadAsync(feedUrl);
IsReady = true;
}*/
} }
} }

View File

@ -1,6 +1,6 @@
namespace WebSharpRSS.Models namespace SharpRss.Models
{ {
public interface ISelectableGuideItem public interface IGuideItem
{ {
public bool IsSelected { get; set; } public bool IsSelected { get; set; }
public bool IsExpanded { get; set; } public bool IsExpanded { get; set; }

View File

@ -19,9 +19,27 @@ namespace SharpRss
/// - Memory /// - Memory
} }
private static HashSet<FeedModel> feedSet = new HashSet<FeedModel>()
{
new FeedModel("http://fedoramagazine.org/feed/"),
new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss")
};
private static HashSet<FeedModel> feedSet2 = new HashSet<FeedModel>()
{
new FeedModel("https://journals.plos.org/plosone/feed/atom"),
new FeedModel("https://cyberciti.biz/feed"),
new FeedModel("https://itsfoss.com/feed")
};
HashSet<CategoryModel> set = new HashSet<CategoryModel>()
{
new CategoryModel("RSS", feedSet),
new CategoryModel("Tech", feedSet2)
};
public async Task<HashSet<CategoryModel>> GetCategories() public async Task<HashSet<CategoryModel>> GetCategories()
{ {
return new HashSet<CategoryModel>(); return set;
} }
public async Task<HashSet<FeedModel>> GetAllFeeds() public async Task<HashSet<FeedModel>> GetAllFeeds()

View File

@ -11,8 +11,15 @@ namespace WebSharpRSS
{ {
public static void SetAppDefaultSettings(this DataContainer dataCon) public static void SetAppDefaultSettings(this DataContainer dataCon)
{ {
dataCon.Set("FaviconResolveUrl", "https://icons.duckduckgo.com/ip3/{0}.ico", false); var paths = dataCon["Paths"];
dataCon.Set("LogPath", Path.Combine(Environment.CurrentDirectory, "logs", "log_.json"), false); paths.Set("FaviconResolveUrl", "https://icons.duckduckgo.com/ip3/{0}.ico", false);
paths.Set("LogPath", Path.Combine(Environment.CurrentDirectory, "logs", "log_.json"), false);
var dbSql = dataCon["SQL"];
dbSql.Set("Host", "localhost", false);
dbSql.Set("Port", "6969", false);
dbSql.Set("Username", "sharpUser", false);
dbSql.Set("Password", "sh@rP@s$", false);
} }
private static LoggerConfiguration? _configuration; private static LoggerConfiguration? _configuration;
@ -21,8 +28,9 @@ namespace WebSharpRSS
if (_configuration != null) return; if (_configuration != null) return;
_configuration = new LoggerConfiguration() _configuration = new LoggerConfiguration()
.WriteTo.Console() .WriteTo.Console()
.WriteTo.File(new JsonFormatter(), Caretaker.Settings.GetString("LogPath"), rollingInterval: RollingInterval.Day) .WriteTo.File(new JsonFormatter(), Caretaker.Settings["Paths"].GetString("LogPath"), rollingInterval: RollingInterval.Day)
.MinimumLevel.Verbose(); .MinimumLevel.Verbose();
Log.Logger = _configuration.CreateLogger(); Log.Logger = _configuration.CreateLogger();
} }
} }

View File

@ -1,22 +0,0 @@
using System.Collections.Generic;
using CodeHollow.FeedReader;
namespace WebSharpRSS.Models
{
public class CategoryGuideItem : ISelectableGuideItem
{
public string CategoryTitle { get; set; }
public string CategoryIcon { get; set; }
private string _hexColor;
public string CategoryHexColor
{
get => _hexColor;
set => _hexColor = value.Insert(7, "80");
}
public bool IsExpanded { get; set; }
public bool IsSelected { get; set; }
public HashSet<FeedGuideItem> FeedItems { get; set; } = new HashSet<FeedGuideItem>() { new FeedGuideItem(FeedReader.ReadAsync("http://fedoramagazine.org/feed/").Result) };
}
}

View File

@ -1,15 +0,0 @@
using SharpRss.Models;
namespace WebSharpRSS.Models
{
public class CategoryItem : CategoryModel, ISelectableGuideItem
{
public CategoryItem(CategoryModel model)
{
Name = model.Name;
Feeds = model.Feeds;
}
public bool IsSelected { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public bool IsExpanded { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
}

View File

@ -1,15 +0,0 @@
using CodeHollow.FeedReader;
namespace WebSharpRSS.Models
{
public class FeedGuideItem : ISelectableGuideItem
{
public FeedGuideItem(Feed feed)
{
Feed = feed;
}
public readonly Feed Feed;
public bool IsSelected { get; set; }
public bool IsExpanded { get; set; }
}
}

View File

@ -10,7 +10,7 @@ using WebSharpRSS;
Caretaker.Settings.SetAppDefaultSettings(); Caretaker.Settings.SetAppDefaultSettings();
Bootstrapper.SetupLogging(); Bootstrapper.SetupLogging();
Log.Information("Starting app...."); Log.Information("Starting application....");
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
@ -18,7 +18,6 @@ builder.Services.AddServerSideBlazor();
builder.Services.AddTransient<RssService>(); builder.Services.AddTransient<RssService>();
builder.Services.AddMudServices(config => builder.Services.AddMudServices(config =>
{ {
Log.Debug("Configuring MudServices");
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight; config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
config.SnackbarConfiguration.PreventDuplicates = true; config.SnackbarConfiguration.PreventDuplicates = true;
config.SnackbarConfiguration.ShowCloseIcon = true; config.SnackbarConfiguration.ShowCloseIcon = true;

12
WebSharpRSS/Settings.json Normal file
View File

@ -0,0 +1,12 @@
{
"Paths": {
"FaviconResolveUrl": "https://icons.duckduckgo.com/ip3/{0}.ico",
"LogPath": "/home/max/GitHub/SharpRSS/WebSharpRSS/logs/log_.json"
},
"SQL": {
"Host": "localhost",
"Port": "6969",
"Username": "sharpUser",
"Password": "sh@rP@s$"
}
}

View File

@ -1,5 +1,8 @@
@using WebSharpRSS.Models
@using CodeHollow.FeedReader @using CodeHollow.FeedReader
@using SharpRss.Models
@using ToolQit
@using ToolQit.Containers
@using ToolQit.Extensions
<style> <style>
.cat-item { .cat-item {
background: var(--background-color); background: var(--background-color);
@ -61,28 +64,37 @@
<div> <div>
<MudText>@HeaderText</MudText> <MudText>@HeaderText</MudText>
@foreach (CategoryGuideItem catItem in Categories) @foreach (CategoryModel guideCategory in Categories)
{ {
<div> <div>
<div @onclick="@(() => ItemClicked(catItem))" class="cat-item mud-ripple" style="--hover-bg-color: @catItem.CategoryHexColor; --background-color: @(catItem.IsSelected ? catItem.CategoryHexColor : "transparent")"> <div @onclick="@(() => ItemClicked(guideCategory))" class="cat-item mud-ripple" style="--hover-bg-color: @Colors.Blue.Accent1; --background-color: @(guideCategory.IsSelected ? Colors.Blue.Accent2 : "transparent")">
<div class="cat-item-icon"> <div class="cat-item-icon">
<MudIcon Class="pointer-events-none" Icon="@catItem.CategoryIcon" Size="Size.Medium"/> <MudIcon Class="pointer-events-none" Icon="@Icons.Material.Filled.RssFeed" Size="Size.Medium"/>
</div> </div>
<div class="cat-item-text"> <div class="cat-item-text">
<MudText Class="pointer-events-none" Typo="Typo.subtitle1">@catItem.CategoryTitle</MudText> <MudText Class="pointer-events-none" Typo="Typo.subtitle1">@guideCategory.Name</MudText>
</div> </div>
</div> </div>
@* Feeds *@ @* Feeds *@
@if (catItem.IsExpanded && catItem.FeedItems != null) @if (guideCategory.IsExpanded && guideCategory.Feeds != null)
{ {
foreach (FeedGuideItem feedItem in catItem.FeedItems) foreach (FeedModel feed in guideCategory.Feeds)
{ {
<div @onclick="() => ItemClicked(feedItem)" class="feed-item mud-ripple" style="--hover-bg-color: @catItem.CategoryHexColor; --background-color: @(feedItem.IsSelected ? catItem.CategoryHexColor : "transparent")"> if (feed == null || feed.Base == null) continue;
<div @onclick="() => ItemClicked(feed)" class="feed-item mud-ripple" style="--hover-bg-color: @Colors.Blue.Accent1; --background-color: @(feed.IsSelected ? Colors.Blue.Accent2 : "transparent")">
<div class="feed-item-icon"> <div class="feed-item-icon">
<MudImage ObjectFit="ObjectFit.Contain" Src="http://www.google.com/s2/favicons?domain=wikipedia.com"/> @*@if (!guideFeed.FaviconUrl.IsNullEmptyWhiteSpace())
{
<MudImage ObjectFit="ObjectFit.ScaleDown" Src="@guideFeed.FaviconUrl"/>
}
else
{
<MudIcon Icon="@Icons.Material.Filled.RssFeed" Size="Size.Medium"/>
}*@
<MudIcon Icon="@Icons.Material.Filled.RssFeed" Size="Size.Medium"/>
</div> </div>
<div class="feed-item-text"> <div class="feed-item-text">
<MudText Class="pointer-events-none">@feedItem.Feed.Title</MudText> <MudText Class="pointer-events-none">@feed.Base.Title</MudText>
</div> </div>
</div> </div>
} }
@ -95,31 +107,31 @@
[Parameter] [Parameter]
public string HeaderText { get; set; } public string HeaderText { get; set; }
[Parameter] [Parameter]
public HashSet<CategoryGuideItem> Categories { get; set; } = new HashSet<CategoryGuideItem>(); public HashSet<CategoryModel> Categories { get; set; } = new HashSet<CategoryModel>();
[Parameter] [Parameter]
public Action<CategoryGuideItem>? CatItemClicked { get; set; } public Action<CategoryModel>? CatItemClicked { get; set; }
[Parameter] [Parameter]
public Action<FeedGuideItem>? FeedItemClicked { get; set; } public Action<FeedModel>? FeedItemClicked { get; set; }
ISelectableGuideItem? _selectedCategory; IGuideItem? _selectedItem;
void ItemClicked(ISelectableGuideItem categoryItem) void ItemClicked(IGuideItem categoryItem)
{ {
categoryItem.IsExpanded = !categoryItem.IsExpanded; categoryItem.IsExpanded = !categoryItem.IsExpanded;
if (_selectedCategory != categoryItem) if (_selectedItem != categoryItem)
{ {
if (_selectedCategory != null) if (_selectedItem != null)
_selectedCategory.IsSelected = false; _selectedItem.IsSelected = false;
_selectedCategory = categoryItem; _selectedItem = categoryItem;
_selectedCategory.IsSelected = true; _selectedItem.IsSelected = true;
} }
switch (categoryItem) switch (categoryItem)
{ {
case CategoryGuideItem catGuideItem: case CategoryModel catGuideItem:
CatItemClicked?.Invoke(catGuideItem); CatItemClicked?.Invoke(catGuideItem);
break; break;
case FeedGuideItem feedGuideItem: case FeedModel feedGuideItem:
FeedItemClicked?.Invoke(feedGuideItem); FeedItemClicked?.Invoke(feedGuideItem);
break; break;
} }

View File

@ -1,5 +1,4 @@
@using SharpRss.Models; @using SharpRss.Models;
@using WebSharpRSS.Models
@using MudBlazor.Utilities @using MudBlazor.Utilities
@using CodeHollow.FeedReader @using CodeHollow.FeedReader
@using Serilog @using Serilog
@ -14,33 +13,34 @@
</MudStack> </MudStack>
@code { @code {
public HashSet<CategoryGuideItem> Categories = new HashSet<CategoryGuideItem>(); public HashSet<CategoryModel> Categories = new HashSet<CategoryModel>();
public HashSet<CategoryItem> Cats = new HashSet<CategoryItem>();
protected override void OnInitialized() protected override void OnInitialized()
{ {
Log.Verbose("Setting up test data"); Log.Verbose("Setting up test data");
Categories.Add(new CategoryGuideItem() { CategoryTitle = "Social", CategoryIcon = Icons.Material.Filled.People }); Categories = _rssService.GetCategories().Result;
/*Cats = _rssService.GetCategories().Result.Select(x => new GuideModel(x)).ToHashSet();*/
/*Categories.Add(new CategoryGuideItem() { CategoryTitle = "Social", CategoryIcon = Icons.Material.Filled.People });
Categories.Add(new CategoryGuideItem() { CategoryTitle = "Blogs", CategoryIcon = Icons.Material.Filled.RssFeed, CategoryHexColor = Colors.Green.Accent1 }); Categories.Add(new CategoryGuideItem() { CategoryTitle = "Blogs", CategoryIcon = Icons.Material.Filled.RssFeed, CategoryHexColor = Colors.Green.Accent1 });
Categories.Add(new CategoryGuideItem() { CategoryTitle = "Tech", CategoryIcon = Icons.Material.Filled.Computer, CategoryHexColor = Colors.Brown.Lighten1 }); Categories.Add(new CategoryGuideItem() { CategoryTitle = "Tech", CategoryIcon = Icons.Material.Filled.Computer, CategoryHexColor = Colors.Brown.Lighten1 });
Categories.Add(new CategoryGuideItem() { CategoryTitle = "News", CategoryIcon = Icons.Material.Filled.Newspaper, CategoryHexColor = Colors.Red.Accent1 }); Categories.Add(new CategoryGuideItem() { CategoryTitle = "News", CategoryIcon = Icons.Material.Filled.Newspaper, CategoryHexColor = Colors.Red.Accent1 });*/
} }
private void Callback(MudListItem obj) private void Callback(MudListItem obj)
{ {
switch (obj.Value) switch (obj.Value)
{ {
case CategoryGuideItem catTreeItem: case CategoryModel catModel:
break; break;
case Feed feed: case FeedModel feedModel:
break; break;
} }
} }
private void CategoryClicked(CategoryGuideItem catItem) private void CategoryClicked(CategoryModel cat)
{ {
} }
private void FeedClicked(FeedGuideItem feedItem) private void FeedClicked(FeedModel guideFeedItem)
{ {
} }

View File

@ -15,4 +15,9 @@
<ProjectReference Include="..\SharpRss\SharpRss.csproj" /> <ProjectReference Include="..\SharpRss\SharpRss.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Core" />
<Folder Include="Models" />
</ItemGroup>
</Project> </Project>