diff --git a/SharpRss/Services/DatabaseService.cs b/SharpRss/Services/DatabaseService.cs index c30f7f6..96ba756 100644 --- a/SharpRss/Services/DatabaseService.cs +++ b/SharpRss/Services/DatabaseService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -116,30 +117,35 @@ namespace SharpRss.Services await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); while (reader.Read()) { - feeds.Add(new FeedModel(reader["url"].ToString()) - { - Id = reader["id"].ToString(), - Title = reader["title"].ToString(), - GroupId = reader["group_id"].ToString(), - FeedType = reader["feed_type"].ToString(), - Description = reader["description"].ToString(), - Language = reader["language"].ToString(), - Copyright = reader["copyright"].ToString(), - DateAdded = DateTimeOffset.FromUnixTimeMilliseconds(long.TryParse(reader["date_added"].ToString(), out long parsedVal) ? parsedVal : 0), - LastUpdated = DateTimeOffset.FromUnixTimeMilliseconds(long.TryParse(reader["last_updated"].ToString(), out long lastUpdated) ? lastUpdated : 0), - ImageUrl = reader["image_url"].ToString(), - OriginalDocument = reader["original_document"].ToString() - }); + feeds.Add(ReaderToFeedModel(reader)); } _sqlConn.Close(); return feeds; } - public async Task SetFeedAsync(FeedModel feedModel) + private FeedModel ReaderToFeedModel(SqliteDataReader reader) { - bool result = false; + return new FeedModel(reader["url"].ToString()) + { + Id = reader["id"].ToString(), + Title = reader["title"].ToString(), + GroupId = reader["group_id"].ToString(), + FeedType = reader["feed_type"].ToString(), + Description = reader["description"].ToString(), + Language = reader["language"].ToString(), + Copyright = reader["copyright"].ToString(), + DateAdded = DateTimeOffset.FromUnixTimeMilliseconds(long.TryParse(reader["date_added"].ToString(), out long parsedVal) ? parsedVal : 0), + LastUpdated = DateTimeOffset.FromUnixTimeMilliseconds(long.TryParse(reader["last_updated"].ToString(), out long lastUpdated) ? lastUpdated : 0), + ImageUrl = reader["image_url"].ToString(), + OriginalDocument = reader["original_document"].ToString() + }; + } + + public async Task SetFeedAsync(FeedModel feedModel) + { + FeedModel? resultModel = null; _sqlConn.Open(); - await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_feedTable} (id, url, title, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url, original_document) VALUES (IFNULL((SELECT id FROM {_feedTable} WHERE url=@url), @id), @url, @title, @groupId, @feedType, @description, @language, @copyright, IFNULL((SELECT date_added FROM {_feedItemTable} WHERE id=@id), @dateAdded), @lastUpdated, @imageUrl, @originalDoc)", _sqlConn) + await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_feedTable} (id, url, title, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url, original_document) VALUES (IFNULL((SELECT id FROM {_feedTable} WHERE url=@url), @id), @url, @title, @groupId, @feedType, @description, @language, @copyright, IFNULL((SELECT date_added FROM {_feedTable} WHERE id=@id), @dateAdded), @lastUpdated, @imageUrl, @originalDoc); SELECT * FROM {_feedTable} WHERE url=@url", _sqlConn) { Parameters = { @@ -157,11 +163,11 @@ namespace SharpRss.Services new SqliteParameter("originalDoc", feedModel.OriginalDocument ?? string.Empty) } }; - int affected = await cmd.ExecuteNonQueryAsync(); - if (affected != 0) - result = true; + await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); + if (reader.Read()) + resultModel = ReaderToFeedModel(reader); _sqlConn.Close(); - return result; + return resultModel; } public async Task RemoveFeedAsync(FeedModel feedModel) { @@ -183,14 +189,13 @@ namespace SharpRss.Services // Feed items public async Task> GetFeedItemsAsync(string[]? feedIds = null) { + List? formattedIds = feedIds?.Select(s => $"'{s}'").ToList(); HashSet feedItems = new HashSet(); _sqlConn.Open(); await using SqliteCommand cmd = new SqliteCommand( - feedIds != null - ? $"SELECT * FROM {_feedItemTable} WHERE feed_id IN (@feedIds)" + formattedIds != null + ? $"SELECT * FROM {_feedItemTable} WHERE feed_id IN ({string.Join(", ", formattedIds)})" : $"SELECT * FROM {_feedItemTable}", _sqlConn); - if (feedIds != null) - cmd.Parameters.Add(new SqliteParameter("feedIds", string.Join(',', feedIds))); await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); while (reader.Read()) @@ -217,6 +222,7 @@ namespace SharpRss.Services { int result = 0; _sqlConn.Open(); + await using SqliteTransaction transaction = _sqlConn.BeginTransaction(); await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_feedItemTable} (id, feed_id, read, title, description, link, last_updated, publishing_date, author, categories, content)" + $"VALUES (IFNULL((SELECT id FROM {_feedItemTable} WHERE link=@link), @id), @feedId, @read, @title, @description, @link, @lastUpdated, @publishingDate, @author, @categories, @content)", _sqlConn); foreach (FeedItemModel item in items) @@ -234,12 +240,15 @@ namespace SharpRss.Services cmd.Parameters.Add(new SqliteParameter("author", item.Author ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("categories", item.Categories != null ? string.Join(',', item.Categories) : string.Empty)); cmd.Parameters.Add(new SqliteParameter("content", item.Content ?? string.Empty)); + if (_sqlConn.State != ConnectionState.Open) + _sqlConn.Open(); int affected = await cmd.ExecuteNonQueryAsync(); if (affected == 0) Log.Verbose("Could not set feed item: {FeedLink}", item.Link); else result += affected; } + transaction.Commit(); _sqlConn.Close(); return result; // Return the amount affected rows. } diff --git a/SharpRss/Services/RssService.cs b/SharpRss/Services/RssService.cs index 6f0ac65..af2fd50 100644 --- a/SharpRss/Services/RssService.cs +++ b/SharpRss/Services/RssService.cs @@ -16,7 +16,7 @@ namespace SharpRss.Services { public RssService() { - //SetupTestCategoriesAndFeedsAsync(); + SetupTestGroupsAndFeedsAsync(); } private readonly DatabaseService _dbService = new DatabaseService(); @@ -50,11 +50,12 @@ namespace SharpRss.Services ImageUrl = fetched.ImageUrl, OriginalDocument = fetched.OriginalDocument }; - result = await _dbService.SetFeedAsync(feedModel); - if (!result) + FeedModel? dbFeed = await _dbService.SetFeedAsync(feedModel); + result = dbFeed != null; + if (dbFeed == null) return result; - if (await AddFeedItems(fetched.Items, feedModel) == 0) - Log.Warning("No feed items added to feed: {FeedUrl}", feedModel.Url); + if (await AddFeedItems(fetched.Items, dbFeed) == 0) + Log.Warning("No feed items added to feed: {FeedUrl}", dbFeed.Url); return result; } public async Task> GetFeedsAsync(string? groupId = null) => await _dbService.GetFeedsAsync(groupId); @@ -106,24 +107,38 @@ namespace SharpRss.Services } private async Task FetchFeed(string url) { + Log.Verbose("Fetching feed: {FeedUrl}", url); var urls = await FeedReader.ParseFeedUrlsAsStringAsync(url); - string feedurl = url; + string feedUrl = url; if (urls.Any()) - feedurl = urls.First(); - return await FeedReader.ReadAsync(feedurl); + feedUrl = urls.First(); + return await FeedReader.ReadAsync(feedUrl); } - private async void SetupTestCategoriesAndFeedsAsync() + private async void SetupTestGroupsAndFeedsAsync() { - var groupRes = await CreateGroupAsync(new GroupModel() { Name = "Test" }); + //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" }); - var groups = await GetGroupsAsync(); + groupRes = await CreateGroupAsync(new GroupModel() { Name = "Science" });*/ + /*var groups = await GetGroupsAsync(); GroupModel testGroup = groups.Single(x => x.Name == "Test"); - 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); + await Task.Run(async () => + { + try + { + 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); + } + catch (Exception e) + { + Log.Error(e, "Error fetching feeds!"); + throw; + } + });*/ } public void Dispose() diff --git a/WebSharpRSS/Pages/Read.razor b/WebSharpRSS/Pages/Read.razor index 97c78f7..61849fb 100644 --- a/WebSharpRSS/Pages/Read.razor +++ b/WebSharpRSS/Pages/Read.razor @@ -76,12 +76,12 @@ bool faulted = false; private async void LoadItems() { - //TODO: better group feed fetching! faulted = false; + isLoading = true; if (Fid != null) { var fItems = await _rssService.GetFeedItemsAsync(Fid); - items = fItems.Select(x => FeedItemData.FromModel(x)).ToHashSet(); + items = fItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); isLoading = false; } else if (Gid != null) @@ -89,12 +89,13 @@ var feeds = await _rssService.GetFeedsAsync(Gid); var feedids = feeds.Select(x => x.Id); var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedids.ToArray()); - items = feedItems.Select(x => FeedItemData.FromModel(x)).ToHashSet(); + items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); } else { faulted = true; } + isLoading = false; StateHasChanged(); } protected override void OnInitialized() diff --git a/WebSharpRSS/sharp_rss.sqlite b/WebSharpRSS/sharp_rss.sqlite index ad75249..dadc057 100644 Binary files a/WebSharpRSS/sharp_rss.sqlite and b/WebSharpRSS/sharp_rss.sqlite differ