diff --git a/SharpRss/Models/FeedItemModel.cs b/SharpRss/Models/FeedItemModel.cs index 6e51999..7cad094 100644 --- a/SharpRss/Models/FeedItemModel.cs +++ b/SharpRss/Models/FeedItemModel.cs @@ -4,6 +4,7 @@ namespace SharpRss.Models { public class FeedItemModel { + public FeedModel? Feed { get; set; } public string? Id { get; set; } = string.Empty; public string? FeedId { get; set; } = string.Empty; public bool Read { get; set; } @@ -16,6 +17,6 @@ namespace SharpRss.Models public string? Author { get; set; } = string.Empty; public string[]? Categories { get; set; } public string? Content { get; set; } = string.Empty; - public string? HexColor { get; set; } = string.Empty; + public string? HexColor => Feed?.Group?.HexColor; } } diff --git a/SharpRss/Models/FeedModel.cs b/SharpRss/Models/FeedModel.cs index 2dad140..296f9d3 100644 --- a/SharpRss/Models/FeedModel.cs +++ b/SharpRss/Models/FeedModel.cs @@ -9,6 +9,7 @@ namespace SharpRss.Models Url = rssUrl; Id = Guid.NewGuid().ToString(); } + public GroupModel? Group { get; set; } public string? Id { get; set; } public string? Url { get; set; } public string? Title { get; set; } = string.Empty; diff --git a/SharpRss/Services/DatabaseService.cs b/SharpRss/Services/DatabaseService.cs index 96ba756..0ff0f12 100644 --- a/SharpRss/Services/DatabaseService.cs +++ b/SharpRss/Services/DatabaseService.cs @@ -15,10 +15,8 @@ namespace SharpRss.Services { internal DatabaseService() { - _sqlConn = new SqliteConnection(_connectionString); InitializeDb(); } - private readonly SqliteConnection _sqlConn; private readonly string _connectionString = $"Data Source={Path.Combine(Environment.CurrentDirectory, "sharp_rss.sqlite")};"; private readonly string _groupTable = "group_data"; private readonly string _feedTable = "feed_data"; @@ -27,8 +25,9 @@ namespace SharpRss.Services // Groups public async Task> GetGroupsAsync(string? groupId = null) { - _sqlConn.Open(); - await using SqliteCommand cmd = new SqliteCommand(groupId != null ? $"SELECT * FROM {_groupTable} WHERE id=@gId;" : $"SELECT * FROM {_groupTable}", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteCommand cmd = new SqliteCommand(groupId != null ? $"SELECT * FROM {_groupTable} WHERE id=@gId;" : $"SELECT * FROM {_groupTable}", dbc) { Parameters = { @@ -37,7 +36,7 @@ namespace SharpRss.Services }; await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); HashSet groups = new HashSet(); - await using SqliteCommand cmdFeedCount = new SqliteCommand($"SELECT COUNT(*) FROM {_feedTable} WHERE group_id=@groupId", _sqlConn); + await using SqliteCommand cmdFeedCount = new SqliteCommand($"SELECT COUNT(*) FROM {_feedTable} WHERE group_id=@groupId", dbc); while (reader.Read()) { cmdFeedCount.Parameters.Clear(); @@ -54,20 +53,14 @@ namespace SharpRss.Services Id = reader["id"].ToString() }); } - _sqlConn.Close(); return groups; } - - /// - /// Creates a group if not exists then update the group. - /// - /// - /// public async Task SetGroupAsync(GroupModel groupModel) { bool result = false; - _sqlConn.Open(); - await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_groupTable} (id, hex_color, icon, name) VALUES (IFNULL((SELECT id FROM {_groupTable} WHERE name=@name), @id), @hexColor, @icon, @name)", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_groupTable} (id, hex_color, icon, name) VALUES (IFNULL((SELECT id FROM {_groupTable} WHERE name=@name), @id), @hexColor, @icon, @name)", dbc) { Parameters = { @@ -80,16 +73,15 @@ namespace SharpRss.Services int affected = await cmd.ExecuteNonQueryAsync(); if (affected != 0) result = true; - _sqlConn.Close(); return result; } - public async Task RemoveGroupAsync(GroupModel groupModel) { bool result = false; - _sqlConn.Open(); + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); // Remove the group and remove the feeds that were part of the group. - await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_groupTable} WHERE id=@id; UPDATE {_feedTable} SET group_id=NULL WHERE group_id=@id", _sqlConn) + await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_groupTable} WHERE id=@id; UPDATE {_feedTable} SET group_id=NULL WHERE group_id=@id", dbc) { Parameters = { @@ -99,53 +91,51 @@ namespace SharpRss.Services int affected = await cmd.ExecuteNonQueryAsync(); if (affected != 0) result = true; - _sqlConn.Close(); return result; } // Feeds + /// + /// + /// + /// Empty = ungrouped feeds | null = all feeds | id = grouped feeds + /// public async Task> GetFeedsAsync(string? groupId = null) { HashSet feeds = new HashSet(); - _sqlConn.Open(); - await using SqliteCommand cmd = new SqliteCommand(groupId != null ? $"SELECT * FROM {_feedTable} WHERE group_id=@groupId" : $"SELECT * FROM {_feedTable}", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteCommand cmd = new SqliteCommand(groupId != null ? $"SELECT * FROM {_feedTable} WHERE group_id=@groupId" : $"SELECT * FROM {_feedTable}", dbc) { Parameters = { - new SqliteParameter("groupId", groupId == null ? string.Empty : groupId) + new SqliteParameter("groupId", groupId ?? string.Empty) } }; await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - while (reader.Read()) - { - feeds.Add(ReaderToFeedModel(reader)); - } - _sqlConn.Close(); + while (!reader.IsClosed && reader.Read()) + feeds.Add(await ReaderToFeedModel(reader)); return feeds; } - - private FeedModel ReaderToFeedModel(SqliteDataReader reader) + public async Task GetFeedAsync(string feedId) { - return new FeedModel(reader["url"].ToString()) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + FeedModel? feed = null; + await using SqliteCommand cmd = new SqliteCommand($"SELECT * FROM {_feedTable} WHERE id=@id", dbc) { - 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() + Parameters = { new SqliteParameter("id", feedId) } }; + await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); + if (reader.Read()) + feed = await ReaderToFeedModel(reader); + return feed; } - 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 {_feedTable} WHERE id=@id), @dateAdded), @lastUpdated, @imageUrl, @originalDoc); SELECT * FROM {_feedTable} WHERE url=@url", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.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 {_feedTable} WHERE id=@id), @dateAdded), @lastUpdated, @imageUrl, @originalDoc); SELECT * FROM {_feedTable} WHERE url=@url", dbc) { Parameters = { @@ -165,15 +155,15 @@ namespace SharpRss.Services }; await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); if (reader.Read()) - resultModel = ReaderToFeedModel(reader); - _sqlConn.Close(); + resultModel = await ReaderToFeedModel(reader); return resultModel; } public async Task RemoveFeedAsync(FeedModel feedModel) { bool result = false; - _sqlConn.Open(); // After removing the feed unset the feed id from the feed items - await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_feedTable} WHERE id=@id; UPDATE {_feedItemTable} SET feed_id=NULL WHERE feed_id=@id", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_feedTable} WHERE id=@id; UPDATE {_feedItemTable} SET feed_id=NULL WHERE feed_id=@id", dbc) { Parameters = { @@ -183,7 +173,6 @@ namespace SharpRss.Services int affected = await cmd.ExecuteNonQueryAsync(); if (affected != 0) result = true; - _sqlConn.Close(); return result; } // Feed items @@ -191,16 +180,17 @@ namespace SharpRss.Services { List? formattedIds = feedIds?.Select(s => $"'{s}'").ToList(); HashSet feedItems = new HashSet(); - _sqlConn.Open(); + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); await using SqliteCommand cmd = new SqliteCommand( formattedIds != null ? $"SELECT * FROM {_feedItemTable} WHERE feed_id IN ({string.Join(", ", formattedIds)})" - : $"SELECT * FROM {_feedItemTable}", _sqlConn); + : $"SELECT * FROM {_feedItemTable}", dbc); await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); while (reader.Read()) { - feedItems.Add(new FeedItemModel() + FeedItemModel feedItemModel = new FeedItemModel() { Id = reader["id"].ToString(), FeedId = reader["feed_id"].ToString(), @@ -208,23 +198,31 @@ namespace SharpRss.Services Title = reader["title"].ToString(), Description = reader["description"].ToString(), Link = reader["link"].ToString(), - LastUpdated = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), - PublishingDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publishing_date"].ToString())), + LastUpdated = + DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), + PublishingDate = + DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publishing_date"].ToString())), Author = reader["author"].ToString(), Categories = reader["categories"].ToString().Split(','), Content = reader["content"].ToString() - }); + }; + if (feedItemModel is { FeedId: { } }) + { + FeedModel? feedModel = await GetFeedAsync(feedItemModel.FeedId); + feedItemModel.Feed = feedModel; + } + feedItems.Add(feedItemModel); } - _sqlConn.Close(); return feedItems; } public async Task SetFeedItemsAsync(HashSet items) { int result = 0; - _sqlConn.Open(); - await using SqliteTransaction transaction = _sqlConn.BeginTransaction(); + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteTransaction transaction = dbc.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); + $"VALUES (IFNULL((SELECT id FROM {_feedItemTable} WHERE link=@link), @id), @feedId, @read, @title, @description, @link, @lastUpdated, @publishingDate, @author, @categories, @content)", dbc); foreach (FeedItemModel item in items) { cmd.Parameters.Clear(); @@ -240,8 +238,8 @@ 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(); + if (dbc.State != ConnectionState.Open) + dbc.Open(); int affected = await cmd.ExecuteNonQueryAsync(); if (affected == 0) Log.Verbose("Could not set feed item: {FeedLink}", item.Link); @@ -249,14 +247,14 @@ namespace SharpRss.Services result += affected; } transaction.Commit(); - _sqlConn.Close(); return result; // Return the amount affected rows. } public async Task RemoveFeedItemAsync(FeedItemModel itemModel) { bool result = false; - _sqlConn.Open(); - await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_feedItemTable} WHERE id=@id", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {_feedItemTable} WHERE id=@id", dbc) { Parameters = { @@ -266,14 +264,14 @@ namespace SharpRss.Services int affected = await cmd.ExecuteNonQueryAsync(); if (affected != 0) result = true; - _sqlConn.Close(); return result; } public async Task GetGroupFromFeedItemAsync(FeedItemModel feedItem) { GroupModel? result = null; - _sqlConn.Open(); - await using SqliteCommand cmd = new SqliteCommand($"SELECT * FROM {_groupTable} WHERE id=(SELECT group_id FROM {_feedTable} WHERE id=@fId)", _sqlConn) + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); + await using SqliteCommand cmd = new SqliteCommand($"SELECT * FROM {_groupTable} WHERE id=(SELECT group_id FROM {_feedTable} WHERE id=@fId)", dbc) { Parameters = { @@ -286,28 +284,49 @@ namespace SharpRss.Services groups = await GetGroupsAsync(reader["group_id"].ToString()); if (groups != null && groups.Any()) result = groups.FirstOrDefault(); - _sqlConn.Close(); return result; } - + private async Task ReaderToFeedModel(SqliteDataReader reader) + { + FeedModel fetchedFeed = 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() + }; + var groupFetch = await GetGroupsAsync(fetchedFeed.GroupId); + if (groupFetch.Any()) + fetchedFeed.Group = groupFetch.First(); + else + Log.Warning("Could not get group from feed: {FeedId}", fetchedFeed.Id); + return fetchedFeed; + } private async void InitializeDb() { Log.Verbose("Checking database..."); HashSet failed = new HashSet(); - _sqlConn.Open(); + await using SqliteConnection dbc = new SqliteConnection(_connectionString); + dbc.Open(); Log.Verbose("Checking table: {Table}", _groupTable); - var queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_groupTable} (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY)"); + var queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS {_groupTable} (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY)"); if (queryResponse.Any()) failed.Add("category_data"); Log.Verbose("Checking table: {Table}", _feedTable); - queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedTable} (id STRING PRIMARY KEY, url STRING NOT NULL, title STRING, group_id STRING, feed_type STRING, description STRING, language STRING, copyright STRING, date_added INT, last_updated INT, image_url STRING, original_document STRING)"); + queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedTable} (id STRING PRIMARY KEY, url STRING NOT NULL, title STRING, group_id STRING, feed_type STRING, description STRING, language STRING, copyright STRING, date_added INT, last_updated INT, image_url STRING, original_document STRING)"); if (queryResponse.Any()) failed.Add("feed_data"); Log.Verbose("Checking table: {Table}", _feedItemTable); - queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedItemTable} (id STRING PRIMARY KEY, feed_id STRING, read INT, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, author STRING, categories STRING, content STRING)"); + queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedItemTable} (id STRING PRIMARY KEY, feed_id STRING, read INT, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, author STRING, categories STRING, content STRING)"); if (queryResponse.Any()) failed.Add("feed_item_data"); - _sqlConn.Close(); if (failed.Any()) { var joined = string.Join(',', failed); @@ -316,10 +335,8 @@ namespace SharpRss.Services else Log.Verbose("Checking database done!"); } - public void Dispose() { - _sqlConn.Dispose(); } } } \ No newline at end of file diff --git a/SharpRss/Services/RssService.cs b/SharpRss/Services/RssService.cs index 88c8fb8..8e803c6 100644 --- a/SharpRss/Services/RssService.cs +++ b/SharpRss/Services/RssService.cs @@ -64,18 +64,6 @@ namespace SharpRss.Services public async Task> GetFeedItemsFromFeedsAsync(string[] feedIds, string? groupId = null) { var items = await _dbService.GetFeedItemsAsync(feedIds); - GroupModel? group = null; - if (groupId != null) - { - var model = await _dbService.GetGroupsAsync(groupId); - if (model != null && model.Any()) group = model.FirstOrDefault(); - } - // Update the items with the group data - if (group != null) - { - foreach (FeedItemModel feedItem in items) - feedItem.HexColor = group.HexColor; - } return items; } diff --git a/WebSharpRSS/Pages/List.razor b/WebSharpRSS/Pages/List.razor index fffa28b..29a6413 100644 --- a/WebSharpRSS/Pages/List.razor +++ b/WebSharpRSS/Pages/List.razor @@ -58,8 +58,8 @@ else if (Gid != null) { var feeds = await _rssService.GetFeedsAsync(Gid); - var feedids = feeds.Select(x => x.Id); - var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedids.ToArray()); + var feedIds = feeds.Select(x => x.Id); + var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedIds.ToArray()); items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); } _isLoading = false;