diff --git a/SharpRss/DbAccess.cs b/SharpRss/DbAccess.cs index 813a6c2..357689c 100644 --- a/SharpRss/DbAccess.cs +++ b/SharpRss/DbAccess.cs @@ -77,7 +77,7 @@ namespace SharpRss await using SqliteConnection dbc = new SqliteConnection(ConnectionString); dbc.Open(); int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed - (url, + (encoded_url, title, category_id, feed_type, @@ -90,7 +90,7 @@ namespace SharpRss categories, image_url) VALUES ( - @Url, + @EncodedUrl, @Title, @CategoryId, @FeedType, @@ -104,7 +104,7 @@ namespace SharpRss @ImageUrl)", new { - Url = feed.OriginalUrl ?? string.Empty, + EncodedUrl = feed.EncodedUrl, Title = feed.Title ?? string.Empty, CategoryId = feed.CategoryId ?? string.Empty, FeedType = feed.FeedType ?? string.Empty, @@ -118,7 +118,7 @@ namespace SharpRss ImageUrl = feed.ImageUrl ?? string.Empty }); if (affected == 0) - Log.Warning("Failed to add feed: {FeedUrl}", feed.OriginalUrl); + Log.Warning("Failed to add feed: {FeedUrl}", feed.EncodedUrl); } public static async Task> GetFeedsAsync(string[]? categoryIds = null) { @@ -130,7 +130,7 @@ namespace SharpRss { FeedModel feedModel = new FeedModel() { - OriginalUrl = reader["url"].ToString(), + EncodedUrl = reader["encoded_url"].ToString(), Title = reader["title"].ToString(), CategoryId = reader["category_id"].ToString(), FeedType = reader["feed_type"].ToString(), @@ -143,11 +143,21 @@ namespace SharpRss Categories = reader["categories"].ToString().Split(','), ImageUrl = reader["image_url"].ToString() }; + feedModel.ItemCount = await dbc.ExecuteScalarAsync("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = feedModel.EncodedUrl }); feeds.Add(feedModel); } return feeds; } - + public static async Task DeleteFeedAsync(FeedModel feed, bool deleteItems = false) + { + if (feed == null) return false; + await using SqliteConnection dbc = new SqliteConnection(ConnectionString); + int affected = await dbc.ExecuteAsync("DELETE FROM feed WHERE encoded_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl }); + if (affected > 0 && deleteItems) + await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl }); + return affected > 0; + } + public static async Task SetFeedItemsAsync(HashSet items) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); @@ -156,13 +166,13 @@ namespace SharpRss await using SqliteTransaction dbTransaction = dbc.BeginTransaction(); foreach (FeedItemModel item in items) { - int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed_item (id, feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content) - VALUES (@Id, @FeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)", + int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed_item (id, encoded_feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content) + VALUES (@Id, @EncodedFeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)", transaction: dbTransaction, param: new { Id = item.Id ?? string.Empty, - FeedUrl = item.FeedUrl ?? string.Empty, + EncodedFeedUrl = item.EncodedFeedUrl ?? string.Empty, Read = item.Read.ToString(), Title = item.Title ?? string.Empty, Description = item.Description ?? string.Empty, @@ -177,26 +187,19 @@ namespace SharpRss } dbTransaction.Commit(); } - - private static string FormatParametersFromArray(string[] dbParams) - { - string[] formatted = dbParams.Select(s => $"'{s}'").ToArray(); - return string.Join(", ", formatted); - } - - public static async Task> GetFeedItemsAsync(string[]? feedUrls) + public static async Task> GetFeedItemsAsync(string[]? encodedFeedUrls) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); dbc.Open(); HashSet items = new HashSet(); - await using DbDataReader reader = await dbc.ExecuteReaderAsync(feedUrls == null ? "SELECT * FROM feed_item" : $"SELECT * FROM feed_item WHERE feed_url IN({FormatParametersFromArray(feedUrls)})"); + await using DbDataReader reader = await dbc.ExecuteReaderAsync(encodedFeedUrls == null ? "SELECT * FROM feed_item" : $"SELECT * FROM feed_item WHERE encoded_feed_url IN({FormatParametersFromArray(encodedFeedUrls)})"); //NOTE(dbg): While debugging this part of the function the code below (Reader.ReadAsync) will return 0, because the debugger already reads the items from the reader. while (await reader.ReadAsync()) { FeedItemModel feedItemModel = new FeedItemModel() { Id = reader["id"].ToString(), - FeedUrl = reader["feed_url"].ToString(), + EncodedFeedUrl = reader["encoded_feed_url"].ToString(), Read = bool.Parse(reader["read"].ToString()), Title = reader["title"].ToString(), Description = reader["description"].ToString(), @@ -212,7 +215,6 @@ namespace SharpRss Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count); return items; } - public static async void Initialize() { if (_isInitialized) return; @@ -223,12 +225,23 @@ namespace SharpRss await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS category (id STRING PRIMARY KEY, name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING)"); Log.Verbose("Checking table: {Table}", "feed"); - await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (url STRING PRIMARY KEY, title STRING, category_id STRING, feed_type STRING, feed_version STRING, description STRING, language STRING, copyright STRING, publication_date INT, last_updated INT, categories STRING, image_url STRING)"); + await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (encoded_url STRING PRIMARY KEY, title STRING, category_id STRING, feed_type STRING, feed_version STRING, description STRING, language STRING, copyright STRING, publication_date INT, last_updated INT, categories STRING, image_url STRING)"); Log.Verbose("Checking table: {Table}", "feed_item"); - await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, feed_url STRING, read STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)"); + await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, encoded_feed_url STRING, read STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)"); Log.Verbose("Checking database done!"); _isInitialized = true; } + + private static string FormatParametersFromArray(string[] dbParams) + { + string[] formatted = dbParams.Select(s => $"'{s}'").ToArray(); + return string.Join(", ", formatted); + } + public static async Task GetFeedCountAsync(string[] encodedFeedUrls) + { + await using SqliteConnection dbc = new SqliteConnection(ConnectionString); + return await dbc.ExecuteScalarAsync("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)"); + } } } \ No newline at end of file diff --git a/SharpRss/DbAccess_Old.cs b/SharpRss/DbAccess_Old.cs index 520e18c..924d486 100644 --- a/SharpRss/DbAccess_Old.cs +++ b/SharpRss/DbAccess_Old.cs @@ -147,7 +147,7 @@ namespace SharpRss { Parameters = { - new SqliteParameter("url", feedModel.OriginalUrl ?? string.Empty), + new SqliteParameter("url", feedModel.EncodedUrl ?? string.Empty), new SqliteParameter("title", feedModel.Title ?? string.Empty), new SqliteParameter("groupId", feedModel.CategoryId ?? string.Empty), new SqliteParameter("feedType", feedModel.FeedType ?? string.Empty), @@ -242,7 +242,7 @@ namespace SharpRss { Parameters = { - new SqliteParameter("Url", feedModel.OriginalUrl) + new SqliteParameter("Url", feedModel.EncodedUrl) } }; int affected = await cmd.ExecuteNonQueryAsync(); @@ -268,7 +268,7 @@ namespace SharpRss FeedItemModel feedItemModel = new FeedItemModel() { Id = reader["id"].ToString(), - FeedUrl = reader["feed_url"].ToString(), + EncodedFeedUrl = reader["feed_url"].ToString(), Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0, Title = reader["title"].ToString(), Description = reader["description"].ToString(), @@ -301,7 +301,7 @@ namespace SharpRss { cmd.Parameters.Clear(); cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? string.Empty)); - cmd.Parameters.Add(new SqliteParameter("feedUrl", item.FeedUrl ?? string.Empty)); + cmd.Parameters.Add(new SqliteParameter("feedUrl", item.EncodedFeedUrl ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("read", item.Read ? 1 : 0)); cmd.Parameters.Add(new SqliteParameter("type", item.Type ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("title", item.Title ?? string.Empty)); @@ -364,7 +364,7 @@ namespace SharpRss { FeedModel fetchedFeed = new FeedModel() { - OriginalUrl = reader["url"].ToString(), + EncodedUrl = reader["url"].ToString(), Title = reader["title"].ToString(), CategoryId = reader["group_id"].ToString(), FeedType = reader["feed_type"].ToString(), diff --git a/SharpRss/Models/FeedItemModel.cs b/SharpRss/Models/FeedItemModel.cs index 4ed5b20..b92151a 100644 --- a/SharpRss/Models/FeedItemModel.cs +++ b/SharpRss/Models/FeedItemModel.cs @@ -5,7 +5,7 @@ namespace SharpRss.Models public class FeedItemModel { public string? Id { get; set; } = string.Empty; - public string FeedUrl { get; set; } = string.Empty; + public string EncodedFeedUrl { get; set; } = string.Empty; public bool Read { get; set; } public string? Type { get; set; } = string.Empty; public string? Title { get; set; } = string.Empty; diff --git a/SharpRss/Models/FeedModel.cs b/SharpRss/Models/FeedModel.cs index df187c2..af3a8a8 100644 --- a/SharpRss/Models/FeedModel.cs +++ b/SharpRss/Models/FeedModel.cs @@ -6,7 +6,7 @@ namespace SharpRss.Models { public class FeedModel { - public string OriginalUrl { get; set; } = string.Empty; + public string EncodedUrl { get; set; } = string.Empty; public string? Title { get; set; } = string.Empty; public string? CategoryId { get; set; } = string.Empty; public string? FeedType { get; set; } = string.Empty; @@ -17,13 +17,14 @@ namespace SharpRss.Models public DateTimeOffset? PublicationDate { get; set; } public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now; public string[]? Categories { get; set; } + public int ItemCount { get; set; } private string _imageUrl = string.Empty; public string ImageUrl { get { if (_imageUrl.IsNullEmptyWhiteSpace()) - _imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(OriginalUrl).Host); + _imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedUrl)).Host); return _imageUrl; } set diff --git a/SharpRss/Services/RssService.cs b/SharpRss/Services/RssService.cs index a61663d..0fc22a5 100644 --- a/SharpRss/Services/RssService.cs +++ b/SharpRss/Services/RssService.cs @@ -9,36 +9,62 @@ using SharpRss.Models; namespace SharpRss.Services { /// - /// Managing RSS feeds and groups. + /// Managing RSS feeds and categories. /// public class RssService { public RssService() { - SetupTestGroupsAndFeedsAsync(); + SetupTestCategoriesAndFeedsAsync(); } - public async Task> GetCategoriesFeedsAsync() + public async Task> GetCategoriesAndFeedsAsync() { HashSet items = new HashSet(); items.UnionWith(await GetCategoriesAsync()); items.UnionWith(await GetUngroupedFeedsAsync()); return items; } - public async Task CreateGroupAsync(CategoryModel group) => await DbAccess_Old.SetCategoryAsync(group); + + public async Task CreateCategoryAsync(CategoryModel category) => + await DbAccess.SetCategoryAsync(category); public async Task> GetCategoriesAsync() => await DbAccess.GetCategoriesAsync(); - - //TODO: Rework this! - // Subscribe to a feed. public async Task AddSubscriptionAsync(string url, CategoryModel? category = null) { var syndication = SyndicationManager.CreateSyndication(url); + if (!syndication.Fetched) + { + Log.Debug("Failed to fetch syndication feed!"); + return false; + } if (category != null) syndication.Category = category; - if (!syndication.Fetched) return false; - await DbAccess.SetSyndicationAsync(syndication); + try + { + await DbAccess.SetSyndicationAsync(syndication); + } + catch (Exception e) + { + Log.Error(e,"Error adding feed: {FeedUrl} to database!", url); + } return true; } + public async Task UpdateFeeds() + { + Log.Verbose("Fetching feeds..."); + var feeds = await GetFeedsAsync(); + } + public async Task> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetFeedsAsync(categoryId == null ? null : new[]{ categoryId }); + public async Task> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync(new []{""}); + public async Task> GetFeedItemsAsync(string feedId, string? groupId = null) => await GetFeedItemsFromFeedsAsync(new[] { feedId }, groupId); + public async Task> GetFeedItemsFromFeedsAsync(string[] feedIds, string? groupId = null) + { + var items = await DbAccess.GetFeedItemsAsync(feedIds); + return items; + } + + + private static FeedModel FromResource(ISyndicationResource resource) { FeedModel model = new FeedModel(); @@ -50,7 +76,7 @@ namespace SharpRss.Services model.Title = rssFeed.Channel.Title; model.Description = rssFeed.Channel.Description; model.Copyright = rssFeed.Channel.Copyright; - model.OriginalUrl = rssFeed.Channel.SelfLink.ToString(); + model.EncodedUrl = rssFeed.Channel.SelfLink.ToString(); model.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty; model.Language = rssFeed.Channel.Language?.ToString(); break; @@ -63,23 +89,6 @@ namespace SharpRss.Services } return model; } - - public async Task UpdateFeeds() - { - Log.Verbose("Fetching feeds..."); - var feeds = await GetFeedsAsync(); - } - - public async Task> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetFeedsAsync(categoryId == null ? null : new[]{ categoryId }); - public async Task> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync(new []{""}); - - public async Task> GetFeedItemsAsync(string feedId, string? groupId = null) => await GetFeedItemsFromFeedsAsync(new[] { feedId }, groupId); - public async Task> GetFeedItemsFromFeedsAsync(string[] feedIds, string? groupId = null) - { - var items = await DbAccess.GetFeedItemsAsync(feedIds); - return items; - } - private GenericSyndicationFeed? CreateFeed(string url) { Uri feedUri = new Uri(url); @@ -92,44 +101,41 @@ namespace SharpRss.Services Log.Verbose("Fetching feed: {FeedUrl}", feedUri.ToString()); return GenericSyndicationFeed.Create(new Uri(url)); } - private async void SetupTestGroupsAndFeedsAsync() + private async void SetupTestCategoriesAndFeedsAsync() { - CategoryModel NewsCat = new CategoryModel() { Name = "News" }; - await AddSubscriptionAsync("https://www.nu.nl/rss/Algemeen", NewsCat); - await AddSubscriptionAsync("https://www.nu.nl/rss/Economie", NewsCat); - await AddSubscriptionAsync("http://fedoramagazine.org/feed/"); - await AddSubscriptionAsync("https://itsfoss.com/feed"); - //TODO: Make multiple adding of feed to a transaction, now throws an exception. - /*var groupRes = await CreateGroupAsync(new GroupModel() { Name = "Test" }); - groupRes = await CreateGroupAsync(new GroupModel() { Name = "News" }); - groupRes = await CreateGroupAsync(new GroupModel() { Name = "Tech" }); - groupRes = await CreateGroupAsync(new GroupModel() { Name = "Science" });*/ - /*Log.Verbose("Fetching feeds..."); - var groups = await GetGroupsAsync(); - GroupModel testGroup = groups.Single(x => x.Name == "Test"); - try + /*CategoryModel? newsCategory = await CreateCategoryAsync(new CategoryModel() { Name = "News" }); + if (newsCategory != null) { - var res = await AddFeed("http://fedoramagazine.org/feed/", testGroup); - res = await AddFeed("https://www.nasa.gov/rss/dyn/breaking_news.rss", testGroup); - res = await AddFeed("https://journals.plos.org/plosone/feed/atom", testGroup); - res = await AddFeed("https://itsfoss.com/feed", testGroup); - res = await AddFeed("https://advisories.ncsc.nl/rss/advisories", testGroup); + await AddSubscriptionAsync("https://www.nu.nl/rss/Algemeen", newsCategory); + await AddSubscriptionAsync("https://www.nasa.gov/rss/dyn/breaking_news.rss", newsCategory); + await AddSubscriptionAsync("http://news.google.com/?output=atom", newsCategory); + await AddSubscriptionAsync("https://www.ad.nl/home/rss.xml", newsCategory); } - catch (Exception e) - { - Log.Error(e, "Error fetching feeds!"); - throw; - }*/ - /*var groups = await GetGroupsAsync(); - CategoryModel testGroup = groups.Single(x => x.Name == "News");*/ - /*await AddFeedsAsync(new[] + CategoryModel? techCategory = await CreateCategoryAsync(new CategoryModel() { Name = "Tech" }); + if (techCategory != null) { - "https://www.nu.nl/rss/Algemeen", - "https://www.nu.nl/rss/Economie", - "https://www.nu.nl/rss/Sport", - "http://news.google.com/?output=atom" - }, testGroup);*/ + await AddSubscriptionAsync("https://itsfoss.com/feed", techCategory); + await AddSubscriptionAsync("http://fedoramagazine.org/feed/", techCategory); + await AddSubscriptionAsync("https://arstechnica.com/feed/", techCategory); + await AddSubscriptionAsync("https://feeds.arstechnica.com/arstechnica/gadgets", techCategory); + } + + CategoryModel? youtubeCategory = await CreateCategoryAsync(new CategoryModel() { Name = "YouTube" }); + if (youtubeCategory != null) + { + await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UCXuqSBlHAE6Xw-yeJA0Tunw", youtubeCategory); + await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UC1Et9K-hHf-P_LzQkE_Q3Jw", youtubeCategory); + await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UCsXVk37bltHxD1rDPwtNM8Q", youtubeCategory); + } + + await AddSubscriptionAsync("http://www.digitaleoverheid.nl/feed/"); + await AddSubscriptionAsync("http://www.digitaleoverheid.nl/agenda/feed/"); + await AddSubscriptionAsync("https://feeds.rijksoverheid.nl/nieuws.rss"); + await AddSubscriptionAsync("https://nl.wikipedia.org/w/index.php?title=Speciaal:RecenteWijzigingen&feed=atom"); + await AddSubscriptionAsync("https://feeds.aivd.nl/nieuws.rss"); + await AddSubscriptionAsync("https://blogs.microsoft.com/feed"); + await AddSubscriptionAsync("https://www.europarl.europa.eu/rss/doc/top-stories/nl.xml");*/ } } } \ No newline at end of file diff --git a/SharpRss/SyndicationManager.cs b/SharpRss/SyndicationManager.cs index 85726a0..b60b0da 100644 --- a/SharpRss/SyndicationManager.cs +++ b/SharpRss/SyndicationManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Argotic.Common; using Argotic.Extensions.Core; using Argotic.Syndication; @@ -33,7 +34,7 @@ namespace SharpRss { Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl); } - return ConstructSyndicationContainer(syndicationFeed); + return ConstructSyndicationContainer(syndicationFeed, feedUrl); } public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed) @@ -46,7 +47,7 @@ namespace SharpRss return memStream; } - private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed) + private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string feedUrl) { SyndicationContainer container = new SyndicationContainer(); if (syndicationFeed == null) @@ -62,14 +63,14 @@ namespace SharpRss case SyndicationContentFormat.Rss: RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource; if (rssFeed.Channel == null) break; - container.FeedModel.OriginalUrl = rssFeed.Channel.SelfLink?.ToString() ?? string.Empty; + container.FeedModel.EncodedUrl = EncodeUrl(feedUrl); container.FeedModel.Title = rssFeed.Channel.Title ?? string.Empty; container.FeedModel.FeedType = rssFeed.Format.ToString() ?? string.Empty; container.FeedModel.FeedVersion = rssFeed.Version?.ToString() ?? string.Empty; container.FeedModel.Description = rssFeed.Channel.Description ?? string.Empty; container.FeedModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty; container.FeedModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty; - container.FeedModel.PublicationDate = rssFeed.Channel.LastBuildDate is not { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.LastBuildDate) : DateTimeOffset.MinValue; + container.FeedModel.PublicationDate = rssFeed.Channel.PublicationDate is { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.PublicationDate) : DateTimeOffset.MinValue; container.FeedModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty(); container.FeedModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty; foreach (var rssItem in rssFeed.Channel.Items) @@ -77,7 +78,7 @@ namespace SharpRss FeedItemModel itemModel = new FeedItemModel() { Id = rssItem.Link?.ToString() ?? string.Empty, - FeedUrl = container.FeedModel.OriginalUrl ?? string.Empty, + EncodedFeedUrl = container.FeedModel.EncodedUrl ?? string.Empty, Type = container.FeedModel.FeedType ?? string.Empty, Title = rssItem.Title ?? string.Empty, Description = rssItem.Description ?? string.Empty, @@ -93,14 +94,14 @@ namespace SharpRss break; case SyndicationContentFormat.Atom: AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource; - container.FeedModel.OriginalUrl = atomFeed.Id?.Uri.ToString() ?? string.Empty; + container.FeedModel.EncodedUrl = EncodeUrl(feedUrl); container.FeedModel.Title = atomFeed.Title?.Content ?? string.Empty; container.FeedModel.FeedType = atomFeed.Format.ToString() ?? string.Empty; container.FeedModel.FeedVersion = atomFeed.Version?.ToString() ?? string.Empty; container.FeedModel.Description = atomFeed.Subtitle?.Content ?? string.Empty; container.FeedModel.Language = atomFeed.Language?.ToString() ?? string.Empty; container.FeedModel.Copyright = atomFeed.Rights?.Content ?? string.Empty; - container.FeedModel.PublicationDate = atomFeed.UpdatedOn != null ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue; + container.FeedModel.PublicationDate = atomFeed.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue; container.FeedModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty(); container.FeedModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty; foreach (var entry in atomFeed.Entries) @@ -108,7 +109,7 @@ namespace SharpRss FeedItemModel itemModel = new FeedItemModel() { Id = entry.Id?.Uri.ToString() ?? string.Empty, - FeedUrl = container.FeedModel?.OriginalUrl ?? string.Empty, + EncodedFeedUrl = container.FeedModel?.EncodedUrl ?? string.Empty, Type = container.FeedModel?.FeedType ?? string.Empty, Title = entry.Title?.Content ?? string.Empty, Description = entry.Summary?.Content ?? string.Empty, @@ -129,7 +130,12 @@ namespace SharpRss container.Fetched = true; return container; } - + public static string EncodeUrl(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url)); + public static string DecodeUrl(string base64) + { + byte[] bytes = Convert.FromBase64String(base64); + return Encoding.Default.GetString(bytes); + } public static void Init() { DbAccess.Initialize(); diff --git a/WebSharpRSS/Models/FeedItemData.cs b/WebSharpRSS/Models/FeedItemData.cs index af1a6be..9271d04 100644 --- a/WebSharpRSS/Models/FeedItemData.cs +++ b/WebSharpRSS/Models/FeedItemData.cs @@ -1,4 +1,5 @@ using System; +using SharpRss; using SharpRss.Models; using ToolQit; @@ -17,7 +18,7 @@ namespace WebSharpRSS.Models get { if (Link == null || _faviconUrl != null) return _faviconUrl; - _faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(Link).Host); + _faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedFeedUrl)).Host); return _faviconUrl; } } diff --git a/WebSharpRSS/Models/TreeItemData.cs b/WebSharpRSS/Models/TreeItemData.cs index b8c99e3..2677537 100644 --- a/WebSharpRSS/Models/TreeItemData.cs +++ b/WebSharpRSS/Models/TreeItemData.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Configuration; using System.Linq; using MudBlazor; +using SharpRss; using SharpRss.Models; using ToolQit; @@ -22,9 +23,10 @@ namespace WebSharpRSS.Models { FeedModel = feedModel; Title = feedModel.Title ?? string.Empty; - if (FeedModel.OriginalUrl == null) return; - FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(FeedModel.OriginalUrl).Host); - } + if (FeedModel.EncodedUrl == null) return; + FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(FeedModel.EncodedUrl)).Host); + TotalItems = FeedModel.ItemCount; + } public readonly CategoryModel? CategoryModel; public readonly FeedModel? FeedModel; @@ -36,5 +38,6 @@ namespace WebSharpRSS.Models public bool HasChildren { get; set; } public bool Loading { get; set; } public bool IsExpanded { get; set; } + public int TotalItems { get; set; } } } \ No newline at end of file diff --git a/WebSharpRSS/Pages/List.razor b/WebSharpRSS/Pages/List.razor index c2d2fd4..6c1fcc8 100644 --- a/WebSharpRSS/Pages/List.razor +++ b/WebSharpRSS/Pages/List.razor @@ -57,7 +57,7 @@ else if (Cid != null) { var feeds = await _rssService.GetFeedsAsync(Cid == string.Empty ? null : Cid); - var feedIds = feeds.Select(x => x.OriginalUrl); + var feedIds = feeds.Select(x => x.EncodedUrl); var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedIds.ToArray()); items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); } diff --git a/WebSharpRSS/Settings.json b/WebSharpRSS/Settings.json new file mode 100644 index 0000000..184f421 --- /dev/null +++ b/WebSharpRSS/Settings.json @@ -0,0 +1,6 @@ +{ + "Paths": { + "FaviconResolveUrl": "http://www.google.com/s2/favicons?domain={0}", + "LogPath": "/home/max/GitHub/SharpRSS/WebSharpRSS/logs/log_.json" + } +} \ No newline at end of file diff --git a/WebSharpRSS/Shared/SideGuide.razor b/WebSharpRSS/Shared/SideGuide.razor index c5d0ed6..5929d11 100644 --- a/WebSharpRSS/Shared/SideGuide.razor +++ b/WebSharpRSS/Shared/SideGuide.razor @@ -51,7 +51,7 @@ if (_selectedItem == null) return; if (_selectedItem.FeedModel != null) { - _navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.OriginalUrl}"); + _navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.EncodedUrl}"); } else if (_selectedItem.CategoryModel != null) { @@ -71,7 +71,7 @@ { Log.Verbose("Loading guide data..."); _guideItems.Add(new TreeItemData(new CategoryModel() { Name = "All", Icon = Icons.Material.Filled.Home, HexColor = Colors.Blue.Accent1, Id = string.Empty })); - HashSet items = await _rssService.GetCategoriesFeedsAsync(); + HashSet items = await _rssService.GetCategoriesAndFeedsAsync(); _guideItems.UnionWith(ModelToTreeItem(items)); StateHasChanged(); diff --git a/WebSharpRSS/sharp_rss.sqlite b/WebSharpRSS/sharp_rss.sqlite index c6deccd..40c2704 100644 Binary files a/WebSharpRSS/sharp_rss.sqlite and b/WebSharpRSS/sharp_rss.sqlite differ