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 dfe2158..37c9ca3 100644 Binary files a/WebSharpRSS/sharp_rss.db and b/WebSharpRSS/sharp_rss.db differ