From 24d62d79dc264886add07f527eed113e6872379a Mon Sep 17 00:00:00 2001 From: Max <51083570+DRdrProfessor@users.noreply.github.com> Date: Thu, 18 May 2023 20:15:31 +0200 Subject: [PATCH] Implemented db backend base, added UI features --- SharpRss/FeedCache.cs | 7 +- SharpRss/Models/CategoryModel.cs | 2 +- SharpRss/Models/FeedModel.cs | 28 ++------ SharpRss/Services/DatabaseService.cs | 23 ++----- SharpRss/Services/RssService.cs | 81 ++++++++++++++--------- WebSharpRSS/Models/FeedStateContainer.cs | 10 ++- WebSharpRSS/Models/TreeItemData.cs | 53 ++++++++++++--- WebSharpRSS/Pages/Index.razor | 43 ++++++------ WebSharpRSS/Program.cs | 6 +- WebSharpRSS/Shared/SideGuide.razor | 30 ++++----- WebSharpRSS/sharp_rss.db | Bin 20480 -> 28672 bytes 11 files changed, 147 insertions(+), 136 deletions(-) diff --git a/SharpRss/FeedCache.cs b/SharpRss/FeedCache.cs index fd12ea6..fe25ed0 100644 --- a/SharpRss/FeedCache.cs +++ b/SharpRss/FeedCache.cs @@ -13,7 +13,6 @@ namespace SharpRss public static class FeedCache { private static readonly Dictionary CachedFeeds = new Dictionary(); - public static async Task GetFeed(string urlKey) { Log.Verbose("Fetching feed: {UrlKey}", urlKey); @@ -33,9 +32,11 @@ namespace SharpRss feedUrl = urls.First().Url; Feed feed = await FeedReader.ReadAsync(feedUrl); - if (feed == null) + if (feed != null) + { Log.Warning("Could not get feed: {FeedUrl}", feedUrl); - CachedFeeds.Add(urlKey, feed); + CachedFeeds[feedUrl] = feed; + } return feed; } } diff --git a/SharpRss/Models/CategoryModel.cs b/SharpRss/Models/CategoryModel.cs index 8766b49..91c7f85 100644 --- a/SharpRss/Models/CategoryModel.cs +++ b/SharpRss/Models/CategoryModel.cs @@ -1,5 +1,4 @@ using System; -using ToolQit.Extensions; namespace SharpRss.Models { @@ -28,6 +27,7 @@ namespace SharpRss.Models public string Name { get; set; } public string HexColor { get; set; } + public string PathIcon { get; set; } public string CategoryId { get; private set; } } } diff --git a/SharpRss/Models/FeedModel.cs b/SharpRss/Models/FeedModel.cs index 887966a..18d40c7 100644 --- a/SharpRss/Models/FeedModel.cs +++ b/SharpRss/Models/FeedModel.cs @@ -9,13 +9,15 @@ namespace SharpRss.Models { } - public FeedModel(Feed feed) + public FeedModel(string rssFeedUrl, CategoryModel? category = null) { + if (category != null) + CategoryId = category.CategoryId; FeedId = Guid.NewGuid().ToString(); - Url = feed.Link; + FeedUrl = rssFeedUrl; } - public string Url { get; set; } + public string FeedUrl { get; set; } public string FeedId { get; private set; } public string CategoryId { get; set; } = ""; @@ -23,29 +25,11 @@ namespace SharpRss.Models { FeedModel feedModel = new FeedModel() { - Url = url, + FeedUrl = url, FeedId = feedId, CategoryId = categoryId }; return feedModel; } - - //private readonly Task _fetchTask; - //public async Task FetchAsync() => _feed = await FeedCache.GetFeed(Url); - /*private Feed? _feed; - public Feed Base { - get - { - if (_feed == null) - { - if (_fetchTask.IsFaulted) - { return new Feed(); } - if (_fetchTask.Status is not (TaskStatus.Running or TaskStatus.WaitingForActivation)) - _fetchTask.Start(); - _fetchTask.Wait(); - } - return _feed ?? new Feed(); - } - }*/ } } diff --git a/SharpRss/Services/DatabaseService.cs b/SharpRss/Services/DatabaseService.cs index 7a032fe..2b8f028 100644 --- a/SharpRss/Services/DatabaseService.cs +++ b/SharpRss/Services/DatabaseService.cs @@ -10,7 +10,7 @@ using SharpRss.Models; namespace SharpRss.Services { - public class DatabaseService : IDisposable + internal class DatabaseService : IDisposable { private string _connectionString => $"Data Source={Path.Combine(Environment.CurrentDirectory, "sharp_rss.db")};"; internal DatabaseService() @@ -24,22 +24,11 @@ namespace SharpRss.Services { bool result = true; _sqlConn.Open(); - /*var queryResult = await _sqlConn.QueryAsync("SELECT * FROM category_data WHERE name=@catName", new { catName = category.Name }); - var enumerable = queryResult.ToList(); - if (queryResult != null && enumerable.Any()) // Category already exists! - result = false;*/ foreach (var categoryModel in categories) { - await _sqlConn.QueryAsync("INSERT OR IGNORE INTO category_data (name, hex_color, category_id) VALUES(@catName, @hexColor, @categoryId)", - new { catName = categoryModel.Name, hexColor = categoryModel.HexColor, categoryId = categoryModel.CategoryId }); + await _sqlConn.QueryAsync("INSERT INTO category_data (name, hex_color, path_icon, category_id) VALUES(@catName, @hexColor, @pathIcon, @categoryId) ON CONFLICT(name) DO UPDATE SET hex_color=@hexColor, path_icon=@pathIcon", + new { catName = categoryModel.Name, hexColor = categoryModel.HexColor, pathIcon = categoryModel.PathIcon, categoryId = categoryModel.CategoryId }); } - /*if (result) - { - - - if (createResult.Any()) // Did not create the category - result = false; - }*/ _sqlConn.Close(); return result; } @@ -50,7 +39,7 @@ namespace SharpRss.Services _sqlConn.Open(); foreach (var feedModel in feeds) { - await _sqlConn.QueryAsync("INSERT OR IGNORE INTO feed_data(url, feed_id, category_id) VALUES(@url, @feedId, @categoryId)", new { url = feedModel.Url, feedId = feedModel.FeedId, categoryId = feedModel.CategoryId }); + await _sqlConn.QueryAsync("INSERT OR REPLACE INTO feed_data(url, feed_id, category_id) VALUES(@url, @feedId, @categoryId) ON CONFLICT(url) DO UPDATE SET category_id=@categoryId", new { url = feedModel.FeedUrl, feedId = feedModel.FeedId, categoryId = feedModel.CategoryId }); } _sqlConn.Close(); return result; @@ -90,14 +79,14 @@ namespace SharpRss.Services { Log.Verbose("Checking database..."); _sqlConn.Open(); - var queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS category_data (name STRING NOT NULL, hex_color STRING NOT NULL, category_id STRING PRIMARY KEY)"); + var queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS category_data (name STRING NOT NULL, hex_color STRING NOT NULL, path_icon STRING, category_id STRING PRIMARY KEY, CONSTRAINT name UNIQUE (name))"); if (queryResponse.Any()) { _sqlConn.Close(); _sqlConn.Dispose(); throw new SqliteException("Error initializing database!", 0); } - queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS feed_data (url STRING NOT NULL, feed_id STRING PRIMARY KEY, category_id STRING DEFAULT '')"); + queryResponse = await _sqlConn.QueryAsync("CREATE TABLE IF NOT EXISTS feed_data (url STRING NOT NULL, feed_id STRING PRIMARY KEY, category_id STRING NOT NULL DEFAULT '', CONSTRAINT url, UNIQUE (url))"); if (queryResponse.Any()) { _sqlConn.Close(); diff --git a/SharpRss/Services/RssService.cs b/SharpRss/Services/RssService.cs index a2423a6..4549552 100644 --- a/SharpRss/Services/RssService.cs +++ b/SharpRss/Services/RssService.cs @@ -1,6 +1,9 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using CodeHollow.FeedReader; using SharpRss.Models; +using ToolQit.Extensions; namespace SharpRss.Services { @@ -11,64 +14,76 @@ namespace SharpRss.Services { public RssService() { - _dbService = new DatabaseService(); - Initialize(); + //SetupTestCategoriesAndFeedsAsync(); } - private readonly DatabaseService _dbService; + private readonly DatabaseService _dbService = new DatabaseService(); - private async void Initialize() + private async void SetupTestCategoriesAndFeedsAsync() { - //HashSet categoryModels = await _dbService.GetCategoriesAsync(); + await _dbService.AddCategoriesAsync(new HashSet() + { + new CategoryModel() { Name = "All" }, + new CategoryModel() { Name = "RSS" }, + new CategoryModel() { Name = "Tech" }, + new CategoryModel() { Name = "News" } + }); + await _dbService.AddFeedsAsync(new HashSet() + { + new FeedModel("http://fedoramagazine.org/feed/"), + new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss"), + new FeedModel("https://journals.plos.org/plosone/feed/atom"), + new FeedModel("https://itsfoss.com/feed") + }); } - public async Task> GetAllAsync() + public async Task GetFeedAsync(string rssUrl) + { + return await FeedCache.GetFeed(rssUrl); + } + + public async Task> GetAllUnsortedAsync() { HashSet items = new HashSet(); + var categories = await _dbService.GetCategoriesAsync(); + var feeds = await _dbService.GetFeedsAsync(string.Empty); + items.UnionWith(categories); + items.UnionWith(feeds); return items; } public async Task> GetCategoriesAsync() { - return new HashSet(); + var result = await _dbService.GetCategoriesAsync(); + return result.OrderBy(x => x.Name).ToHashSet(); } public async Task> GetFeedsAsync(CategoryModel? categoryModel = null) { - HashSet feeds = new HashSet(); + HashSet feeds; if (categoryModel != null) - { - // Get feeds from the category. - } + feeds = await _dbService.GetFeedsAsync(categoryModel.CategoryId); else - { - // Get all the feeds. - } + feeds = await _dbService.GetFeedsAsync(); return feeds; } - public async void AddCategoryAsync(string name) + public async void AddCategoryAsync(string name, string? icon, string hexColor) { + CategoryModel categoryModel = new CategoryModel() + { + Name = name + }; + if (icon != null) + categoryModel.PathIcon = icon; + if (!hexColor.IsNullEmptyWhiteSpace()) + categoryModel.HexColor = hexColor; + await _dbService.AddCategoriesAsync(new HashSet() { categoryModel }); } - public async void AddFeedAsync(string rssUrl) + public async void AddFeedAsync(string rssUrl, CategoryModel? category = null) { + FeedModel feedModel = new FeedModel(rssUrl, category); + await _dbService.AddFeedsAsync(new HashSet() { feedModel }); } - - /*private static HashSet feedSet = new HashSet() - { - new FeedModel("http://fedoramagazine.org/feed/"), - new FeedModel("https://www.nasa.gov/rss/dyn/breaking_news.rss"), - }; - private static HashSet feedSet2 = new HashSet() - { - new FeedModel("https://journals.plos.org/plosone/feed/atom"), - new FeedModel("https://itsfoss.com/feed") - };*/ - - /*HashSet set = new HashSet() - { - new CategoryModel("RSS", feedSet), - new CategoryModel("Tech", feedSet2) - };*/ } } \ No newline at end of file diff --git a/WebSharpRSS/Models/FeedStateContainer.cs b/WebSharpRSS/Models/FeedStateContainer.cs index e67f191..762200d 100644 --- a/WebSharpRSS/Models/FeedStateContainer.cs +++ b/WebSharpRSS/Models/FeedStateContainer.cs @@ -1,16 +1,14 @@ using System; -using System.Collections.Generic; -using SharpRss.Models; namespace WebSharpRSS.Models { public class FeedStateContainer { - public HashSet Feeds { get; set; } - public event Action StateChanged; - public void SetValue(HashSet feedSet) + public TreeItemData? TreeItem { get; set; } + public event Action? StateChanged; + public void SetValue(TreeItemData treeItemSet) { - Feeds = feedSet; + TreeItem = treeItemSet; Invoke(); } diff --git a/WebSharpRSS/Models/TreeItemData.cs b/WebSharpRSS/Models/TreeItemData.cs index 6aed67e..544ef19 100644 --- a/WebSharpRSS/Models/TreeItemData.cs +++ b/WebSharpRSS/Models/TreeItemData.cs @@ -3,32 +3,33 @@ using System.Collections.Generic; using System.Linq; using CodeHollow.FeedReader; using MudBlazor; +using Serilog; using SharpRss.Models; +using SharpRss.Services; using ToolQit; namespace WebSharpRSS.Models { public class TreeItemData { - public TreeItemData(CategoryModel catModel) + public TreeItemData(CategoryModel catModel, RssService rssService) { + _service = rssService; CategoryModel = catModel; - //Feeds = CategoryModel.Feeds.Where(x => x.Base != null).Select(x => new TreeItemData(x)).ToHashSet(); - Title = CategoryModel.Name; - Icon = Icons.Material.Filled.RssFeed; + Initialize(); } - public TreeItemData(FeedModel feedModel) + public TreeItemData(FeedModel feedModel, RssService rssService) { + _service = rssService; FeedModel = feedModel; - //Feed = FeedModel.Base; - Title = Feed.Title; - string faviconAddress = Feed.Link.Remove(Feed.Link.IndexOf("http", StringComparison.Ordinal), Feed.Link.IndexOf("://", StringComparison.Ordinal) + 3); - FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), faviconAddress); + Initialize(); } + private readonly RssService _service; public readonly CategoryModel? CategoryModel; public readonly FeedModel? FeedModel; + private HashSet _feedModels; public string Title { get; set; } = string.Empty; public bool IsSelected { get; set; } @@ -36,11 +37,11 @@ namespace WebSharpRSS.Models public string? FaviconUrl { get; set; } // Category - public bool HasChild => Feeds != null; + public bool HasChild => _feedModels != null && _feedModels.Any(); public bool IsExpanded { get; set; } public HashSet? Feeds { get; set; } // Feed - public Feed? Feed { get; set; } + public Feed? Feed { get; private set; } public int FeeditemCount { get @@ -60,5 +61,35 @@ namespace WebSharpRSS.Models return 0; } } + + private async void Initialize() + { + if (CategoryModel != null) + { + Title = CategoryModel.Name; + Icon = Icons.Material.Filled.Category; + _feedModels = await _service.GetFeedsAsync(CategoryModel); + if (_feedModels.Any()) + Feeds = _feedModels.Select(x => new TreeItemData(x, _service)).OrderBy(x => x.Title).ToHashSet(); + } + + if (FeedModel != null) + { + try + { + Feed = await _service.GetFeedAsync(FeedModel.FeedUrl); + } + catch (Exception e) + { + Log.Error(e, "Error fetching feed: {FeedUrl}", FeedModel.FeedUrl); + Feed = null; + return; + } + Icon = Icons.Material.Filled.RssFeed; + Title = Feed.Title; + string faviconAddress = Feed.Link.Remove(Feed.Link.IndexOf("http", StringComparison.Ordinal), Feed.Link.IndexOf("://", StringComparison.Ordinal) + 3); + FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), faviconAddress); + } + } } } \ No newline at end of file diff --git a/WebSharpRSS/Pages/Index.razor b/WebSharpRSS/Pages/Index.razor index 3cb14bb..0bc5d24 100644 --- a/WebSharpRSS/Pages/Index.razor +++ b/WebSharpRSS/Pages/Index.razor @@ -4,33 +4,28 @@ @using WebSharpRSS.Models; @using SharpRss.Services -@inject RssService _rssService; +@*@inject RssService _rssService;*@ @inject FeedStateContainer _stateContainer; - @if (Feeds != null) + @foreach (var feedItem in _items) { - foreach (var feedItem in _items) - { - - - - @feedItem.Title - @feedItem.Description - @feedItem.PublishingDate.ToString() - - - - } + + + + @feedItem.Title + @feedItem.Description + @feedItem.PublishingDate.ToString() + + + } @code { - [Parameter] - public HashSet? Feeds { get; set; } private HashSet _items = new HashSet(); - protected override async void OnInitialized() + protected override void OnInitialized() { UpdateFeeds(); _stateContainer.StateChanged += FeedsChanged; @@ -43,11 +38,17 @@ private void UpdateFeeds() { - Feeds = _stateContainer.Feeds; - if (Feeds == null) return; - foreach (var feedmodel in Feeds) + if (_stateContainer.TreeItem == null) return; + if (_stateContainer.TreeItem.Feed != null) + _items = _stateContainer.TreeItem.Feed.Items.ToHashSet(); + if (_stateContainer.TreeItem.Feeds != null) { - //_items = feedmodel.Base.Items.OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); + _items = new HashSet(); + foreach (var itemData in _stateContainer.TreeItem.Feeds) + { + if (itemData.Feed == null) continue; + _items.UnionWith(itemData.Feed.Items); + } } } } \ No newline at end of file diff --git a/WebSharpRSS/Program.cs b/WebSharpRSS/Program.cs index cf5d286..6c0969b 100644 --- a/WebSharpRSS/Program.cs +++ b/WebSharpRSS/Program.cs @@ -4,8 +4,6 @@ using Microsoft.Extensions.Hosting; using MudBlazor; using MudBlazor.Services; using Serilog; -using SharpRss; -using SharpRss.Models; using SharpRss.Services; using ToolQit; using WebSharpRSS; @@ -13,12 +11,12 @@ using WebSharpRSS.Models; Caretaker.Settings.SetAppDefaultSettings(); Bootstrapper.SetupLogging(); -Log.Information("Starting application...."); +Log.Information("Starting..."); var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddTransient(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddMudServices(config => { diff --git a/WebSharpRSS/Shared/SideGuide.razor b/WebSharpRSS/Shared/SideGuide.razor index d269e74..4ba6f56 100644 --- a/WebSharpRSS/Shared/SideGuide.razor +++ b/WebSharpRSS/Shared/SideGuide.razor @@ -11,16 +11,16 @@ @inject RssService _rssService - + - +
- + @if (context.FaviconUrl.IsNullEmptyWhiteSpace() && context.Icon != null) { - + } else { @@ -39,36 +39,30 @@ @code { - public HashSet GuideItems = new HashSet(); - private TreeItemData? _selecteditem; + private MudTheme Theme = new MudTheme(); + private readonly HashSet _guideItems = new HashSet(); + private TreeItemData? _selectedItem; private TreeItemData? SelectedItem { - get => _selecteditem; + get => _selectedItem; set { - _selecteditem = value; + _selectedItem = value; ItemClicked(); } } private void ItemClicked() { if (SelectedItem == null) return; - if (SelectedItem.FeedModel != null) - _stateContainer.SetValue(new HashSet() { SelectedItem.FeedModel }); - if (SelectedItem.Feeds != null) - { - - } + _stateContainer.SetValue(SelectedItem); } protected override async void OnInitialized() { Log.Verbose("Loading guide data..."); - - /*HashSet cats = await _rssService.GetAllAsync(); - await Task.Run(() => Categories.UnionWith(cats.Select(x => new TreeItemData(x)).ToHashSet()));*/ + HashSet items = await _rssService.GetAllUnsortedAsync(); + _guideItems.UnionWith(items.Select(x => x is CategoryModel model ? new TreeItemData(model, _rssService) : x is FeedModel feedModel ? new TreeItemData(feedModel, _rssService) : throw new ArgumentException("Arg x is invalid!"))); StateHasChanged(); Log.Verbose(" Guide initialized!"); - //await Task.Run(() => Categories = cats.Select(x => new TreeItemData(x)).ToHashSet()); } } \ No newline at end of file diff --git a/WebSharpRSS/sharp_rss.db b/WebSharpRSS/sharp_rss.db index dfe2158ea714e02c95610e78bde0da1e2405478d..37c9ca3c3e88717797b0b23e608586bdc441e7e3 100644 GIT binary patch literal 28672 zcmeI(zi!(`90zbxq~$n(%mPN>p#@CTgYCj6@y}5t3k0ZQ%Yk7>X>4i85U5Aq(N-gi zt|Zrqw|EG;7w8*g>Cy)%x)0H z-y1&|=cO0(U(L6!s(|YdfB*y_009U<00Izz00geJz%!!HE-o#p=W9b2bwwfsZ*w+e zlf=x%UcJ_=o6Xvt-MTrsY_6X4yXJm#Z@Y2VY}{{}jn?k&`bBw%j|)EC+rC%ZduZ;| zAFi7*8;XOp|FnHsKCWnP*0*Y{-KM#+vTknNZ^-A?wi``Twy|!u8ru(A^-+UsYj&}? zSYJ|=PLGQxgD<-B4YSQohUqBZo^-G6Op>$qOuj1iq)PGGraHU0vZ9{f9jQ!up2A(uUU;+8(U?WwY5+y zF5X+Y3TvvjZD%U0hlTuA?WwAo{6F2u{!=A+>c&Tk@ssh5@tI8EfB*y_009U<00Izz z00bZa0SH`c0jkg3dS7WZ#OYw!54P&{Y7|kA`i@PQ<;R3t0V6E5T;h1bk2$wQ=vS#W zs?`+n;j-=cZf(={8FM^0Abvs}LRmGC^{G!h#|kW#L{%=hTbNmpZSC#vFV{CLujW|3 zOA|j12p5r0Xe3)vo4@zH|ALjD6D9$x%}_{uN31CY&$*kdDW@%Czi_`7FeMZ1{;O^+|1@OgUD@3HQn zeB4b3<+OiL$r5Q#jKnHzm>y9^Ez5N{33=?wn0)S#(6*|?ErT;WyYB$0auY&mBZ^+;&N9`R#~$6O??+>VgP zZ4&?gTWae_1}GXnzy>qG^6QDz3cWJX^89}FyfxeWa3{CoLw`Mzux6!^)@)yTrk zE-o+6*cd)}J+C+;%jDy{Da@LNER(zV47gbvg&En!)zujrQzswg%au<~EJ;nzFRF~s zOi>693G(!FR|p95^mPo1RPc6<)SSGR-<^$-{|p2FnazR*hxqM`7+Hj+LxX~Yl^qSt z3=Iq{(o7AKQd7-!jS>x$bWIE`({wG8%#w61%+k^l4HFX$4bzOdfR1M5zrnzN18BxM cem!;OP)=g>@dEWR2!QoWoS?%7G6ul~0Qet9r2qf`