From ea91abe629342bd647df757cc9411da72fc6e01c Mon Sep 17 00:00:00 2001 From: Max <51083570+DRdrProfessor@users.noreply.github.com> Date: Fri, 16 Jun 2023 22:53:26 +0200 Subject: [PATCH] Reworking backend... --- SharpRss/Core/FeedCache.cs | 6 +- SharpRss/DbAccess.cs | 101 +++-- SharpRss/DbAccess_Old.cs | 417 ------------------ SharpRss/Models/CategoryModel.cs | 5 +- ...edItemModel.cs => SyndicationItemModel.cs} | 11 +- .../{FeedModel.cs => SyndicationModel.cs} | 15 +- SharpRss/Services/SyndicationService.cs | 20 +- SharpRss/SharpRss.csproj | 4 +- SharpRss/SyndicationManager.cs | 85 ++-- ...FeedItemData.cs => SyndicationItemData.cs} | 8 +- WebSharpRSS/Models/TreeItemData.cs | 16 +- WebSharpRSS/Pages/List.razor | 10 +- WebSharpRSS/Shared/FeedItemList.razor | 8 +- WebSharpRSS/Shared/ItemView.razor | 2 +- WebSharpRSS/Shared/ReadDialog.razor | 2 +- WebSharpRSS/Shared/SideGuide.razor | 8 +- WebSharpRSS/WebSharpRSS.csproj | 2 +- 17 files changed, 155 insertions(+), 565 deletions(-) delete mode 100644 SharpRss/DbAccess_Old.cs rename SharpRss/Models/{FeedItemModel.cs => SyndicationItemModel.cs} (65%) rename SharpRss/Models/{FeedModel.cs => SyndicationModel.cs} (72%) rename WebSharpRSS/Models/{FeedItemData.cs => SyndicationItemData.cs} (64%) diff --git a/SharpRss/Core/FeedCache.cs b/SharpRss/Core/FeedCache.cs index d43a2ff..f296986 100644 --- a/SharpRss/Core/FeedCache.cs +++ b/SharpRss/Core/FeedCache.cs @@ -12,15 +12,15 @@ namespace SharpRss.Core FetchFeeds(); } private readonly SyndicationService _syndicationService = new SyndicationService(); - private Dictionary _cachedFeeds = new Dictionary(); + private Dictionary _cachedFeeds = new Dictionary(); private async void FetchFeeds() { - HashSet fetchedFeeds = await _syndicationService.GetFeedsAsync(); + HashSet fetchedFeeds = await _syndicationService.GetFeedsAsync(); _cachedFeeds = fetchedFeeds.ToDictionary(x => x.EncodedUrl); } - public FeedModel? CacheFeed(string encodedUrl) => _cachedFeeds.TryGetValue(encodedUrl, out FeedModel model) ? model : null; + public SyndicationModel? CacheFeed(string encodedUrl) => _cachedFeeds.TryGetValue(encodedUrl, out SyndicationModel model) ? model : null; } } \ No newline at end of file diff --git a/SharpRss/DbAccess.cs b/SharpRss/DbAccess.cs index ffc983b..bc41b45 100644 --- a/SharpRss/DbAccess.cs +++ b/SharpRss/DbAccess.cs @@ -9,7 +9,6 @@ using Microsoft.Data.Sqlite; using Serilog; using SharpRss.Core; using SharpRss.Models; -using ToolQit.Containers; namespace SharpRss { @@ -24,12 +23,12 @@ namespace SharpRss { CategoryModel? catModel = await SetCategoryAsync(synContainer.Category); if (catModel != null) - synContainer.FeedModel.CategoryId = catModel.Id; + synContainer.SyndicationModel.CategoryId = catModel.Id; } - if (synContainer.FeedModel != null) - await SetFeedAsync(synContainer.FeedModel); - if (synContainer.FeedItems != null && synContainer.FeedItems.Any()) - await SetFeedItemsAsync(synContainer.FeedItems); + if (synContainer.SyndicationModel != null) + await SetSyndicationAsync(synContainer.SyndicationModel); + if (synContainer.SyndicationItems != null && synContainer.SyndicationItems.Any()) + await SetSyndicationItemsAsync(synContainer.SyndicationItems); } public static async Task> GetCategoriesAsync() @@ -47,7 +46,7 @@ namespace SharpRss HexColor = reader["hex_color"].ToString(), Icon = reader["icon"].ToString() }; - categoryModel.FeedCount = await dbc.ExecuteScalarAsync($"SELECT COUNT(*) FROM feed WHERE category_id=@CatId", new { CatId = categoryModel.Id }); + categoryModel.SyndicationCount = await dbc.ExecuteScalarAsync($"SELECT COUNT(*) FROM feed WHERE category_id=@CatId", new { CatId = categoryModel.Id }); categories.Add(categoryModel); } return categories; @@ -74,7 +73,7 @@ namespace SharpRss return affected > 0; } - public static async Task SetFeedAsync(FeedModel feed) + public static async Task SetSyndicationAsync(SyndicationModel syndication) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); dbc.Open(); @@ -106,67 +105,67 @@ namespace SharpRss @ImageUrl)", new { - EncodedUrl = feed.EncodedUrl, - Title = feed.Title ?? string.Empty, - CategoryId = feed.CategoryId ?? string.Empty, - FeedType = feed.FeedType ?? string.Empty, - FeedVersion = feed.FeedVersion ?? string.Empty, - Description = feed.Description ?? string.Empty, - Language = feed.Language ?? string.Empty, - Copyright = feed.Copyright ?? string.Empty, - PublicationDate = feed.PublicationDate?.ToUnixTimeMilliseconds() ?? 0, - LastUpdated = feed.LastUpdated?.ToUnixTimeMilliseconds() ?? 0, - Categories = feed.Categories != null && feed.Categories.Any() ? string.Join(',', feed.Categories) : string.Empty, - ImageUrl = feed.ImageUrl ?? string.Empty + EncodedUrl = syndication.EncodedUrl, + Title = syndication.Title ?? string.Empty, + CategoryId = syndication.CategoryId ?? string.Empty, + FeedType = syndication.SyndicationType ?? string.Empty, + FeedVersion = syndication.SyndicationVersion ?? string.Empty, + Description = syndication.Description ?? string.Empty, + Language = syndication.Language ?? string.Empty, + Copyright = syndication.Copyright ?? string.Empty, + PublicationDate = syndication.PublicationDate?.ToUnixTimeMilliseconds() ?? 0, + LastUpdated = syndication.SynUpdatedDate?.ToUnixTimeMilliseconds() ?? 0, + Categories = syndication.Categories != null && syndication.Categories.Any() ? SyndicationManager.FormatStringArrayToString(syndication.Categories) : string.Empty, + ImageUrl = syndication.ImageUrl ?? string.Empty }); if (affected == 0) - Log.Warning("Failed to add feed: {FeedUrl}", feed.EncodedUrl); + Log.Warning("Failed to add feed: {FeedUrl}", syndication.EncodedUrl); } - public static async Task> GetFeedsAsync(string[]? categoryIds = null) + public static async Task> GetSyndicationsAsync(string[]? categoryIds = null) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); dbc.Open(); - HashSet feeds = new HashSet(); + HashSet feeds = new HashSet(); await using DbDataReader reader = await dbc.ExecuteReaderAsync(categoryIds == null ? "SELECT * FROM feed" : "SELECT * FROM feed WHERE category_id IN(@CatIds)", new { CatIds = categoryIds }); while (await reader.ReadAsync()) { - FeedModel feedModel = new FeedModel() + SyndicationModel syndicationModel = new SyndicationModel() { EncodedUrl = reader["encoded_url"].ToString(), Title = reader["title"].ToString(), CategoryId = reader["category_id"].ToString(), - FeedType = reader["feed_type"].ToString(), - FeedVersion = reader["feed_version"].ToString(), + SyndicationType = reader["feed_type"].ToString(), + SyndicationVersion = reader["feed_version"].ToString(), Description = reader["description"].ToString(), Language = reader["language"].ToString(), Copyright = reader["copyright"].ToString(), PublicationDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publication_date"].ToString())), - LastUpdated = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), - Categories = reader["categories"].ToString().Split(','), + SynUpdatedDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), + Categories = SyndicationManager.StringToStringArray(reader["categories"].ToString()), 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); + syndicationModel.ItemCount = await dbc.ExecuteScalarAsync("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = syndicationModel.EncodedUrl }); + feeds.Add(syndicationModel); } return feeds; } - public static async Task DeleteFeedAsync(FeedModel feed, bool deleteItems = false) + public static async Task DeleteSyndicationAsync(SyndicationModel syndication, bool deleteItems = false) { - if (feed == null) return false; + if (syndication == 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 }); + int affected = await dbc.ExecuteAsync("DELETE FROM feed WHERE encoded_url=@EncodedUrl", new { EncodedUrl = syndication.EncodedUrl }); if (affected > 0 && deleteItems) - await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl }); + await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = syndication.EncodedUrl }); return affected > 0; } - public static async Task SetFeedItemsAsync(HashSet items) + public static async Task SetSyndicationItemsAsync(HashSet items) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); dbc.Open(); int totalAffected = 0; await using SqliteTransaction dbTransaction = dbc.BeginTransaction(); - foreach (FeedItemModel item in items) + foreach (SyndicationItemModel item in items) { 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)", @@ -174,15 +173,15 @@ namespace SharpRss param: new { Id = item.Id ?? string.Empty, - EncodedFeedUrl = item.EncodedFeedUrl ?? string.Empty, + EncodedFeedUrl = item.EncodedSyndicationUrl ?? string.Empty, Read = item.Read.ToString(), Title = item.Title ?? string.Empty, Description = item.Description ?? string.Empty, Link = item.Link ?? string.Empty, - LastUpdated = item.LastUpdated?.ToUnixTimeMilliseconds() ?? 0, + LastUpdated = item.ItemUpdatedDate?.ToUnixTimeMilliseconds() ?? 0, PublishingDate = item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0, - Authors = item.Authors != null && item.Authors.Any() ? string.Join(',', item.Authors) : string.Empty, - Categories = item.Categories != null && item.Categories.Any() ? string.Join(',', item.Categories) : string.Empty, + Authors = item.Authors != null && item.Authors.Any() ? SyndicationManager.FormatStringArrayToString(item.Authors) : string.Empty, + Categories = item.Categories != null && item.Categories.Any() ? SyndicationManager.FormatStringArrayToString(item.Categories) : string.Empty, Content = item.Content ?? string.Empty }); totalAffected += affected; @@ -190,33 +189,32 @@ namespace SharpRss dbTransaction.Commit(); } - public static async Task> GetFeedItemsAsync(string[]? encodedFeedUrls) + public static async Task> GetSyndicationItemsAsync(string[]? encodedFeedUrls) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); dbc.Open(); - HashSet items = new HashSet(); + HashSet items = new HashSet(); FeedCache fCache = new FeedCache(); 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() + SyndicationItemModel syndicationItemModel = new SyndicationItemModel() { Id = reader["id"].ToString(), - EncodedFeedUrl = reader["encoded_feed_url"].ToString(), + EncodedSyndicationUrl = reader["encoded_feed_url"].ToString(), Read = bool.Parse(reader["read"].ToString()), Title = reader["title"].ToString(), Description = reader["description"].ToString(), Link = reader["link"].ToString(), - LastUpdated = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), + ItemUpdatedDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), PublishingDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publishing_date"].ToString())), - Authors = reader["authors"].ToString().ToString().Split(','), - Categories = reader["categories"].ToString().Split(','), + Authors = SyndicationManager.StringToStringArray(reader["authors"].ToString()), + Categories = SyndicationManager.StringToStringArray(reader["categories"].ToString()), Content = reader["content"].ToString() }; - feedItemModel.ParentFeed = fCache.CacheFeed(feedItemModel.EncodedFeedUrl); - // Get feed - items.Add(feedItemModel); + syndicationItemModel.SyndicationParent = fCache.CacheFeed(syndicationItemModel.EncodedSyndicationUrl) ?? new SyndicationModel(); + items.Add(syndicationItemModel); } Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count); return items; @@ -244,9 +242,10 @@ namespace SharpRss string[] formatted = dbParams.Select(s => $"'{s}'").ToArray(); return string.Join(", ", formatted); } - public static async Task GetFeedCountAsync(string[] encodedFeedUrls) + public static async Task GetSyndicationCountAsync(string[] encodedSyndicationUrls) { await using SqliteConnection dbc = new SqliteConnection(ConnectionString); + dbc.Open(); return await dbc.ExecuteScalarAsync("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)"); } } diff --git a/SharpRss/DbAccess_Old.cs b/SharpRss/DbAccess_Old.cs deleted file mode 100644 index 924d486..0000000 --- a/SharpRss/DbAccess_Old.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Dapper; -using Microsoft.Data.Sqlite; -using Serilog; -using SharpRss.Models; - -namespace SharpRss -{ - public static class DbAccess_Old - { - //TODO: Rename group => category. - //TODO: Reworking feed => model/db implementation. - private static bool _isInitialized; - private static readonly string ConnectionString = $"Data Source={Path.Combine(Environment.CurrentDirectory, "sharp_rss.sqlite")};"; - private const string GroupTable = "group_data"; - private const string FeedTable = "feed_data"; - private const string FeedItemTable = "feed_item_data"; - - // Groups - public static async Task> GetCategoriesAsync(string? categoryId = null) - { - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - await using SqliteCommand cmd = new SqliteCommand(categoryId != null ? $"SELECT * FROM {GroupTable} WHERE id=@gId;" : $"SELECT * FROM {GroupTable}", dbc) - { - Parameters = - { - new SqliteParameter("gId", categoryId) - } - }; - await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - HashSet categories = new HashSet(); - await using SqliteCommand cmdFeedCount = new SqliteCommand($"SELECT COUNT(*) FROM {FeedTable} WHERE group_id=@groupId", dbc); - while (reader.Read()) - { - cmdFeedCount.Parameters.Clear(); - cmdFeedCount.Parameters.Add(new SqliteParameter("groupId", reader["id"].ToString())); - using SqliteDataReader countReader = await cmdFeedCount.ExecuteReaderAsync(); - int count = countReader.Read() ? countReader.GetInt32(0) : 0; - - categories.Add(new CategoryModel() - { - Name = reader["name"].ToString(), - FeedCount = count, - HexColor = reader["hex_color"].ToString(), - Icon = reader["icon"].ToString(), - Id = reader["id"].ToString() - }); - } - return categories; - } - public static async Task SetCategoryAsync(CategoryModel categoryModel) - { - bool result = false; - 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 = - { - new SqliteParameter("id", categoryModel.Id), - new SqliteParameter("hexColor", categoryModel.HexColor), - new SqliteParameter("icon", categoryModel.Icon), - new SqliteParameter("name", categoryModel.Name) - } - }; - int affected = await cmd.ExecuteNonQueryAsync(); - if (affected != 0) - result = true; - return result; - } - public static async Task RemoveCategoryAsync(CategoryModel categoryModel) - { - bool result = false; - 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", dbc) - { - Parameters = - { - new SqliteParameter("id", categoryModel.Id) - } - }; - int affected = await cmd.ExecuteNonQueryAsync(); - if (affected != 0) - result = true; - return result; - } - // Feeds - - /// - /// - /// - /// Empty = ungrouped feeds | null = all feeds | id = grouped feeds - /// - public static async Task> GetFeedsAsync(string? groupId = null) - { - HashSet feeds = new HashSet(); - 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 ?? string.Empty) - } - }; - await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - while (!reader.IsClosed && reader.Read()) - feeds.Add(await ReaderToFeedModel(reader)); - return feeds; - } - public static async Task GetFeedAsync(string url) - { - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - FeedModel? feed = null; - //TODO: Use dapper to simplify this query. - await using SqliteCommand cmd = new SqliteCommand($"SELECT * FROM {FeedTable} WHERE url=@Url", dbc) - { - Parameters = { new SqliteParameter("Url", url) } - }; - try - { - await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - if (reader.Read()) - feed = await ReaderToFeedModel(reader); - } - catch (Exception ex) - { - Log.Error(ex, "Error while fetching feed from db."); - } - return feed; - } - public static async Task SetFeedAsync(FeedModel feedModel) - { - FeedModel? feed = null; - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {FeedTable} (url, title, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url, original_document) VALUES (@url, @title, @groupId, @feedType, @description, @language, @copyright, @dateAdded, @lastUpdated, @imageUrl, @originalDoc); SELECT * FROM {FeedTable} WHERE url=@url", dbc) - { - Parameters = - { - 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), - new SqliteParameter("description", feedModel.Description ?? string.Empty), - new SqliteParameter("language", feedModel.Language ?? string.Empty), - new SqliteParameter("copyright", feedModel.Copyright ?? string.Empty), - new SqliteParameter("dateAdded", feedModel.PublicationDate?.ToUnixTimeMilliseconds() ?? 0), - new SqliteParameter("lastUpdated", feedModel.LastUpdated?.ToUnixTimeMilliseconds() ?? 0), - new SqliteParameter("imageUrl", feedModel.ImageUrl ?? string.Empty) - } - }; - try - { - await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - feed = await ReaderToFeedModel(reader); - } - catch (Exception ex) - { - Log.Error(ex, "Database error, adding feed model to database failed!"); - return feed; - } - return feed; - /*await using SqliteTransaction transaction = dbc.BeginTransaction(); - try - { - foreach (var feedModel in feedModels) - { - //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) - 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)", dbc) - { - Parameters = - { - new SqliteParameter("id", feedModel.Id ?? string.Empty), - new SqliteParameter("url", feedModel.Url ?? string.Empty), - new SqliteParameter("title", feedModel.Title ?? string.Empty), - new SqliteParameter("groupId", feedModel.GroupId ?? string.Empty), - new SqliteParameter("feedType", feedModel.FeedType ?? string.Empty), - new SqliteParameter("description", feedModel.Description ?? string.Empty), - new SqliteParameter("language", feedModel.Language ?? string.Empty), - new SqliteParameter("copyright", feedModel.Copyright ?? string.Empty), - new SqliteParameter("dateAdded", feedModel.DateAdded?.ToUnixTimeMilliseconds()), - new SqliteParameter("lastUpdated", feedModel.LastUpdated?.ToUnixTimeMilliseconds()), - new SqliteParameter("imageUrl", feedModel.ImageUrl ?? string.Empty), - new SqliteParameter("originalDoc", feedModel.OriginalDocument ?? string.Empty) - }, - Transaction = transaction - }; - await cmd.ExecuteNonQueryAsync(); - } - await transaction.CommitAsync(); - } - catch (Exception ex) - { - await transaction.RollbackAsync(); - Log.Error(ex, "Error on inserting feeds to db."); - return false; - }*/ - } - - public static async Task FetchFeedItemsAsync(string[]? feedUrls = null) - { - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - HashSet dbFeeds = new HashSet(); - string query = $"SELECT * FROM {FeedTable}"; - if (feedUrls != null) - { - List? feedUrlsFormatted = feedUrls.Select(s => $"'{s}'").ToList(); - query = $"SELECT * FROM {FeedTable} WHERE url IN(({string.Join(", ", feedUrlsFormatted)}))"; - } - await using SqliteCommand fetchCmd = new SqliteCommand(query, dbc); - await using SqliteDataReader reader = await fetchCmd.ExecuteReaderAsync(); - while (reader.Read()) - { - dbFeeds.Add(await ReaderToFeedModel(reader)); - } - HashSet feedItems = new HashSet(); - foreach (var dbFeed in dbFeeds) - { - /*GenericSyndicationFeed syndication = new GenericSyndicationFeed(); - syndication.Load(dbFeed.OriginalDocument);*/ - //TODO: Get items and add to db - } - } - - public static async Task RemoveFeedAsync(FeedModel feedModel) - { - bool result = false; - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {FeedTable} WHERE url=@Url; UPDATE {FeedItemTable} SET feed_id=NULL WHERE feed_id=@Url", dbc) - { - Parameters = - { - new SqliteParameter("Url", feedModel.EncodedUrl) - } - }; - int affected = await cmd.ExecuteNonQueryAsync(); - if (affected != 0) - result = true; - return result; - } - // Feed items - public static async Task> GetFeedItemsAsync(string[]? feedIds = null) - { - List? formattedIds = feedIds?.Select(s => $"'{s}'").ToList(); - HashSet feedItems = new HashSet(); - 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}", dbc); - - await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - while (reader.Read()) - { - FeedItemModel feedItemModel = new FeedItemModel() - { - Id = reader["id"].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(), - Link = reader["link"].ToString(), - LastUpdated = - DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())), - PublishingDate = - DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publishing_date"].ToString())), - Authors = reader["authors"].ToString().ToString().Split(','), - Categories = reader["categories"].ToString().Split(','), - Content = reader["content"].ToString() - }; - feedItems.Add(feedItemModel); - } - return feedItems; - } - - public static async Task SetFeedItemsAsync(HashSet items) - { - int result = 0; - 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_url, read, title, description, link, last_updated, publishing_date, authors, categories, content)" + - $"VALUES (IFNULL((SELECT id FROM {FeedItemTable} WHERE link=@link), @id), @feedUrl, @read, @title, @description, @link, @lastUpdated, @publishingDate, @authors, @categories, @content)", dbc) - { - Transaction = transaction - }; - foreach (FeedItemModel item in items) - { - cmd.Parameters.Clear(); - cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? 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)); - cmd.Parameters.Add(new SqliteParameter("description", item.Description ?? string.Empty)); - cmd.Parameters.Add(new SqliteParameter("link", item.Link ?? string.Empty)); - cmd.Parameters.Add(new SqliteParameter("lastUpdated", item.LastUpdated?.ToUnixTimeMilliseconds())); - cmd.Parameters.Add(new SqliteParameter("publishingDate", item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0)); - cmd.Parameters.Add(new SqliteParameter("authors", item.Authors != null ? string.Join(',', item.Authors) : 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 (dbc.State != ConnectionState.Open) - dbc.Open(); - int affected = await cmd.ExecuteNonQueryAsync(); - if (affected == 0) - Log.Verbose("Could not set feed item: {FeedLink}", item.Link); - else - result += affected; - } - transaction.Commit(); - return result; - } - public static async Task RemoveFeedItemAsync(FeedItemModel itemModel) - { - bool result = false; - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {FeedItemTable} WHERE id=@id", dbc) - { - Parameters = - { - new SqliteParameter("id", itemModel.Id) - } - }; - int affected = await cmd.ExecuteNonQueryAsync(); - if (affected != 0) - result = true; - return result; - } - public static async Task GetGroupFromFeedItemAsync(FeedItemModel feedItem) - { - CategoryModel? result = null; - 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 = - { - new SqliteParameter ("fId", feedItem.Id) - } - }; - await using SqliteDataReader reader = await cmd.ExecuteReaderAsync(); - HashSet? groups = null; - /*if (reader.Read()) - groups = await GetCategoriesAsync(reader["group_id"].ToString());*/ - if (groups != null && groups.Any()) - result = groups.FirstOrDefault(); - return result; - } - private static async Task ReaderToFeedModel(SqliteDataReader reader) - { - FeedModel fetchedFeed = new FeedModel() - { - EncodedUrl = reader["url"].ToString(), - Title = reader["title"].ToString(), - CategoryId = reader["group_id"].ToString(), - FeedType = reader["feed_type"].ToString(), - Description = reader["description"].ToString(), - Language = reader["language"].ToString(), - Copyright = reader["copyright"].ToString(), - PublicationDate = 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() - }; - //TODO: Set group on insert - /*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; - } - - //=== - public static async void Initialize() - { - if (_isInitialized) return; - Log.Verbose("Checking database..."); - HashSet failed = new HashSet(); - await using SqliteConnection dbc = new SqliteConnection(ConnectionString); - dbc.Open(); - Log.Verbose("Checking table: {Table}", "category"); - var queryResponse = await dbc.QueryAsync("CREATE TABLE IF NOT EXISTS category (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY)"); - if (queryResponse.Any()) failed.Add("category"); - - Log.Verbose("Checking table: {Table}", "feed"); - queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS feed (url STRING PRIMARY KEY, 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"); - - Log.Verbose("Checking table: {Table}", "feed_item"); - queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, feed_url STRING, read INT, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)"); - if (queryResponse.Any()) failed.Add("feed_item"); - - if (failed.Any()) - { - var joined = string.Join(',', failed); - Log.Error("Failed to initialize table(s): {TableNames}", joined); - } - else - Log.Verbose("Checking database done!"); - _isInitialized = true; - } - } -} \ No newline at end of file diff --git a/SharpRss/Models/CategoryModel.cs b/SharpRss/Models/CategoryModel.cs index e24a925..caf2131 100644 --- a/SharpRss/Models/CategoryModel.cs +++ b/SharpRss/Models/CategoryModel.cs @@ -10,7 +10,7 @@ namespace SharpRss.Models { Id = Guid.NewGuid().ToString(); } - /*private string _id = string.Empty;*/ + public string Id { get; set; } public string Name { get; set; } = string.Empty; private string _hexColor = string.Empty; @@ -24,7 +24,8 @@ namespace SharpRss.Models } set => _hexColor = value; } - public int FeedCount { get; set; } public string Icon { get; set; } = string.Empty; + public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now; + public int SyndicationCount { get; set; } } } diff --git a/SharpRss/Models/FeedItemModel.cs b/SharpRss/Models/SyndicationItemModel.cs similarity index 65% rename from SharpRss/Models/FeedItemModel.cs rename to SharpRss/Models/SyndicationItemModel.cs index a843376..4852700 100644 --- a/SharpRss/Models/FeedItemModel.cs +++ b/SharpRss/Models/SyndicationItemModel.cs @@ -2,22 +2,23 @@ namespace SharpRss.Models { - public class FeedItemModel + public class SyndicationItemModel { - public FeedModel? ParentFeed { get; set; } + public SyndicationModel SyndicationParent { get; set; } = new SyndicationModel(); + // Db props public string? Id { get; set; } = string.Empty; - public string EncodedFeedUrl { get; set; } = string.Empty; + public string EncodedSyndicationUrl { get; set; } = string.Empty; public bool Read { get; set; } public string? Type { get; set; } = string.Empty; public string? Title { get; set; } = string.Empty; public string? Description { get; set; } = string.Empty; public string? Link { get; set; } = string.Empty; - public DateTimeOffset? LastUpdated { get; set; } + public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now; + public DateTimeOffset? ItemUpdatedDate { get; set; } public DateTimeOffset? PublishingDate { get; set; } public string[]? Authors { get; set; } public string[]? Categories { get; set; } public string? Content { get; set; } = string.Empty; public string? CommentsUrl { get; set; } = string.Empty; - public string? HexColor { get; set; } = string.Empty; } } diff --git a/SharpRss/Models/FeedModel.cs b/SharpRss/Models/SyndicationModel.cs similarity index 72% rename from SharpRss/Models/FeedModel.cs rename to SharpRss/Models/SyndicationModel.cs index 595e10a..5cb67ec 100644 --- a/SharpRss/Models/FeedModel.cs +++ b/SharpRss/Models/SyndicationModel.cs @@ -4,18 +4,21 @@ using ToolQit.Extensions; namespace SharpRss.Models { - public class FeedModel + public class SyndicationModel { + public CategoryModel? Category { get; set; } + // DB props 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; - public string? FeedVersion { get; set; } = string.Empty; + public string? SyndicationType { get; set; } = string.Empty; + public string? SyndicationVersion { get; set; } = string.Empty; public string? Description { get; set; } = string.Empty; public string? Language { get; set; } = string.Empty; public string? Copyright { get; set; } = string.Empty; + public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now; public DateTimeOffset? PublicationDate { get; set; } - public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now; + public DateTimeOffset? SynUpdatedDate { get; set; } public string[]? Categories { get; set; } public int ItemCount { get; set; } private string _imageUrl = string.Empty; @@ -24,7 +27,7 @@ namespace SharpRss.Models get { if (_imageUrl.IsNullEmptyWhiteSpace()) - _imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedUrl)).Host); + _imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrlFromBase64(EncodedUrl)).Host); return _imageUrl; } set @@ -36,7 +39,7 @@ namespace SharpRss.Models public override bool Equals(object obj) { - if (obj is FeedModel objModel) + if (obj is SyndicationModel objModel) return EncodedUrl.Equals(objModel.EncodedUrl); return false; } diff --git a/SharpRss/Services/SyndicationService.cs b/SharpRss/Services/SyndicationService.cs index ad7cfa3..76dca14 100644 --- a/SharpRss/Services/SyndicationService.cs +++ b/SharpRss/Services/SyndicationService.cs @@ -18,11 +18,11 @@ namespace SharpRss.Services SetupTestCategoriesAndFeedsAsync(); } - public async Task> GetCategoriesAndFeedsAsync() + public async Task> GetCategoriesAndSyndicationsAsync() { HashSet items = new HashSet(); items.UnionWith(await GetCategoriesAsync()); - items.UnionWith(await GetUngroupedFeedsAsync()); + items.UnionWith(await GetUngroupedSyndicationsAsync()); return items; } @@ -54,25 +54,25 @@ namespace SharpRss.Services 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? categoryId = null) + public async Task> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetSyndicationsAsync(categoryId == null ? null : new[]{ categoryId }); + public async Task> GetUngroupedSyndicationsAsync() => await DbAccess.GetSyndicationsAsync(new []{""}); + public async Task> GetSyndicationItemsAsync(string feedId, string? groupId = null) => await GetSyndicationItemsFromSyndicationsAsync(new[] { feedId }, groupId); + public async Task> GetSyndicationItemsFromSyndicationsAsync(string[] feedIds, string? categoryId = null) { - var items = await DbAccess.GetFeedItemsAsync(feedIds); + var items = await DbAccess.GetSyndicationItemsAsync(feedIds); return items; } - private static FeedModel FromResource(ISyndicationResource resource) + private static SyndicationModel FromResource(ISyndicationResource resource) { - FeedModel model = new FeedModel(); + SyndicationModel model = new SyndicationModel(); switch (resource.Format) { case SyndicationContentFormat.Rss: RssFeed rssFeed = (RssFeed)resource; - model.FeedType = rssFeed.Format.ToString(); + model.SyndicationType = rssFeed.Format.ToString(); model.Title = rssFeed.Channel.Title; model.Description = rssFeed.Channel.Description; model.Copyright = rssFeed.Channel.Copyright; diff --git a/SharpRss/SharpRss.csproj b/SharpRss/SharpRss.csproj index 52fbb39..6b6e01d 100644 --- a/SharpRss/SharpRss.csproj +++ b/SharpRss/SharpRss.csproj @@ -11,9 +11,9 @@ - + - + diff --git a/SharpRss/SyndicationManager.cs b/SharpRss/SyndicationManager.cs index a4959be..a0aa30e 100644 --- a/SharpRss/SyndicationManager.cs +++ b/SharpRss/SyndicationManager.cs @@ -15,8 +15,8 @@ namespace SharpRss { public GenericSyndicationFeed SyndicationFeed { get; set; } public CategoryModel Category { get; set; } - public FeedModel FeedModel { get; set; } - public HashSet FeedItems { get; set; } + public SyndicationModel SyndicationModel { get; set; } + public HashSet SyndicationItems { get; set; } public bool Fetched; } public static class SyndicationManager @@ -27,27 +27,27 @@ namespace SharpRss Uri feedUri = new Uri(feedUrl); try { - Log.Debug("Fetching feed: {FeedUri}", feedUri.ToString()); + Log.Debug("Fetching syndication: {SyndicationUri}", feedUri.ToString()); syndicationFeed = GenericSyndicationFeed.Create(feedUri); } catch (Exception e) { - Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl); + Log.Error(e,"Could not get syndication: {SyndicationUrl}", feedUrl); } return ConstructSyndicationContainer(syndicationFeed, feedUrl); } - public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed) + public static Stream SyndicationToStream(GenericSyndicationFeed syndicationFeed) { MemoryStream memStream = new MemoryStream(); syndicationFeed.Resource.Save(memStream); if (memStream.Length <= 0) - Log.Warning("Failed to serialize {FeedType} feed: {FeedUri}", syndicationFeed.Format.ToString(), syndicationFeed.Title); + Log.Warning("Failed to serialize {SyndicationType} feed: {SyndicationUri}", syndicationFeed.Format.ToString(), syndicationFeed.Title); memStream.Position = 0; return memStream; } - private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string feedUrl) + private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string syndicationUrl) { SyndicationContainer container = new SyndicationContainer(); if (syndicationFeed == null) @@ -56,30 +56,30 @@ namespace SharpRss return container; } container.SyndicationFeed = syndicationFeed; - container.FeedModel = new FeedModel(); - container.FeedItems = new HashSet(); + container.SyndicationModel = new SyndicationModel(); + container.SyndicationItems = new HashSet(); switch (syndicationFeed.Resource.Format) { case SyndicationContentFormat.Rss: RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource; if (rssFeed.Channel == null) break; - 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.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; + container.SyndicationModel.EncodedUrl = EncodeUrlToBase64(syndicationUrl); + container.SyndicationModel.Title = rssFeed.Channel.Title ?? string.Empty; + container.SyndicationModel.SyndicationType = rssFeed.Format.ToString() ?? string.Empty; + container.SyndicationModel.SyndicationVersion = rssFeed.Version?.ToString() ?? string.Empty; + container.SyndicationModel.Description = rssFeed.Channel.Description ?? string.Empty; + container.SyndicationModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty; + container.SyndicationModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty; + container.SyndicationModel.PublicationDate = rssFeed.Channel.PublicationDate is { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.PublicationDate) : DateTimeOffset.MinValue; + container.SyndicationModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty(); + container.SyndicationModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty; foreach (var rssItem in rssFeed.Channel.Items) { - FeedItemModel itemModel = new FeedItemModel() + SyndicationItemModel itemModel = new SyndicationItemModel() { Id = rssItem.Link?.ToString() ?? string.Empty, - EncodedFeedUrl = container.FeedModel.EncodedUrl ?? string.Empty, - Type = container.FeedModel.FeedType ?? string.Empty, + EncodedSyndicationUrl = container.SyndicationModel.EncodedUrl ?? string.Empty, + Type = container.SyndicationModel.SyndicationType ?? string.Empty, Title = rssItem.Title ?? string.Empty, Description = rssItem.Description ?? string.Empty, Link = rssItem.Link?.ToString() ?? string.Empty, @@ -89,53 +89,56 @@ namespace SharpRss Content = rssItem.Extensions?.Where(x => x is SiteSummaryContentSyndicationExtension).Select(x => (x as SiteSummaryContentSyndicationExtension)?.Context.Encoded).FirstOrDefault() ?? string.Empty, CommentsUrl = rssItem.Extensions?.Where(x => x is WellFormedWebCommentsSyndicationExtension).Select(x => (x as WellFormedWebCommentsSyndicationExtension)?.Context.CommentsFeed.ToString()).FirstOrDefault() ?? string.Empty }; - container.FeedItems.Add(itemModel); + container.SyndicationItems.Add(itemModel); } break; case SyndicationContentFormat.Atom: AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource; - 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 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; + container.SyndicationModel.EncodedUrl = EncodeUrlToBase64(syndicationUrl); + container.SyndicationModel.Title = atomFeed.Title?.Content ?? string.Empty; + container.SyndicationModel.SyndicationType = atomFeed.Format.ToString() ?? string.Empty; + container.SyndicationModel.SyndicationVersion = atomFeed.Version?.ToString() ?? string.Empty; + container.SyndicationModel.Description = atomFeed.Subtitle?.Content ?? string.Empty; + container.SyndicationModel.Language = atomFeed.Language?.ToString() ?? string.Empty; + container.SyndicationModel.Copyright = atomFeed.Rights?.Content ?? string.Empty; + container.SyndicationModel.PublicationDate = atomFeed.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue; + container.SyndicationModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty(); + container.SyndicationModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty; foreach (var entry in atomFeed.Entries) { - FeedItemModel itemModel = new FeedItemModel() + SyndicationItemModel itemModel = new SyndicationItemModel() { Id = entry.Id?.Uri.ToString() ?? string.Empty, - EncodedFeedUrl = container.FeedModel?.EncodedUrl ?? string.Empty, - Type = container.FeedModel?.FeedType ?? string.Empty, + EncodedSyndicationUrl = container.SyndicationModel?.EncodedUrl ?? string.Empty, + Type = container.SyndicationModel?.SyndicationType ?? string.Empty, Title = entry.Title?.Content ?? string.Empty, Description = entry.Summary?.Content ?? string.Empty, Link = entry.Id?.Uri.ToString() ?? string.Empty, - LastUpdated = entry.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.UpdatedOn) : DateTimeOffset.Now, + ItemUpdatedDate = entry.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.UpdatedOn) : DateTimeOffset.Now, PublishingDate = entry.PublishedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.PublishedOn) : entry.UpdatedOn, Authors = entry.Authors?.Select(auth => auth.Name).ToArray() ?? Array.Empty(), Categories = entry.Categories?.Select(cat => cat.Label).ToArray() ?? Array.Empty(), Content = entry.Content?.Content ?? string.Empty }; - container.FeedItems.Add(itemModel); + container.SyndicationItems.Add(itemModel); } break; default: - Log.Warning("Feed implementation missing!"); + Log.Warning("Syndication implementation missing!"); break; } container.Fetched = true; return container; } - public static string EncodeUrl(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url)); - public static string DecodeUrl(string base64) + public static string EncodeUrlToBase64(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url)); + public static string DecodeUrlFromBase64(string base64) { byte[] bytes = Convert.FromBase64String(base64); return Encoding.Default.GetString(bytes); } + public static string FormatStringArrayToString(string[] sArr) => string.Join('|', sArr); + public static string[] StringToStringArray(string stringFormat) => stringFormat.Split('|'); + public static void Init() { DbAccess.Initialize(); diff --git a/WebSharpRSS/Models/FeedItemData.cs b/WebSharpRSS/Models/SyndicationItemData.cs similarity index 64% rename from WebSharpRSS/Models/FeedItemData.cs rename to WebSharpRSS/Models/SyndicationItemData.cs index 9271d04..7dfe740 100644 --- a/WebSharpRSS/Models/FeedItemData.cs +++ b/WebSharpRSS/Models/SyndicationItemData.cs @@ -5,20 +5,20 @@ using ToolQit; namespace WebSharpRSS.Models { - public class FeedItemData : FeedItemModel + public class SyndicationItemData : SyndicationItemModel { - public FeedItemData() + public SyndicationItemData() { } - public static FeedItemData? FromModel(FeedItemModel model) => Utilities.ConvertFrom(model); + public static SyndicationItemData? FromModel(SyndicationItemModel model) => Utilities.ConvertFrom(model); private string? _faviconUrl; public string? FaviconUrl { get { if (Link == null || _faviconUrl != null) return _faviconUrl; - _faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedFeedUrl)).Host); + _faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrlFromBase64(EncodedSyndicationUrl)).Host); return _faviconUrl; } } diff --git a/WebSharpRSS/Models/TreeItemData.cs b/WebSharpRSS/Models/TreeItemData.cs index 2677537..fe3dd8b 100644 --- a/WebSharpRSS/Models/TreeItemData.cs +++ b/WebSharpRSS/Models/TreeItemData.cs @@ -16,19 +16,19 @@ namespace WebSharpRSS.Models CategoryModel = categoryModel; Title = categoryModel.Name; Icon = categoryModel.Icon == string.Empty ? Icons.Material.Filled.RssFeed : categoryModel.Icon; - HasChildren = CategoryModel.FeedCount > 0; + HasChildren = CategoryModel.SyndicationCount > 0; } - public TreeItemData(FeedModel feedModel) + public TreeItemData(SyndicationModel syndicationModel) { - FeedModel = feedModel; - Title = feedModel.Title ?? string.Empty; - if (FeedModel.EncodedUrl == null) return; - FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(FeedModel.EncodedUrl)).Host); - TotalItems = FeedModel.ItemCount; + SyndicationModel = syndicationModel; + Title = syndicationModel.Title ?? string.Empty; + if (SyndicationModel.EncodedUrl == null) return; + FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrlFromBase64(SyndicationModel.EncodedUrl)).Host); + TotalItems = SyndicationModel.ItemCount; } public readonly CategoryModel? CategoryModel; - public readonly FeedModel? FeedModel; + public readonly SyndicationModel? SyndicationModel; public HashSet? Children { get; set; } public string Title { get; set; } diff --git a/WebSharpRSS/Pages/List.razor b/WebSharpRSS/Pages/List.razor index 82b2abc..c36c8e7 100644 --- a/WebSharpRSS/Pages/List.razor +++ b/WebSharpRSS/Pages/List.razor @@ -44,22 +44,22 @@ } } private string? _cid; - HashSet items = new HashSet(); + HashSet items = new HashSet(); bool _isLoading = true; private async void LoadItems() { _isLoading = true; if (Fid != null) { - var fItems = await _syndicationService.GetFeedItemsAsync(Fid); - items = fItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); + var fItems = await _syndicationService.GetSyndicationItemsAsync(Fid); + items = fItems.Select(x => SyndicationItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); } else if (Cid != null) { var feeds = await _syndicationService.GetFeedsAsync(Cid == string.Empty ? null : Cid); var feedIds = feeds.Select(x => x.EncodedUrl); - var feedItems = await _syndicationService.GetFeedItemsFromFeedsAsync(feedIds.ToArray()); - items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); + var feedItems = await _syndicationService.GetSyndicationItemsFromSyndicationsAsync(feedIds.ToArray()); + items = feedItems.Select(x => SyndicationItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); } _isLoading = false; StateHasChanged(); diff --git a/WebSharpRSS/Shared/FeedItemList.razor b/WebSharpRSS/Shared/FeedItemList.razor index b0cf4e6..02efa6c 100644 --- a/WebSharpRSS/Shared/FeedItemList.razor +++ b/WebSharpRSS/Shared/FeedItemList.razor @@ -46,15 +46,15 @@ @code { [Parameter] - public HashSet? Items { get; set; } + public HashSet? Items { get; set; } DialogOptions _dialogOptions = new DialogOptions() { FullWidth = true, MaxWidth = MaxWidth.ExtraLarge, NoHeader = true, CloseButton = true, CloseOnEscapeKey = true }; - private void Callback(FeedItemData feedItem) + private void Callback(SyndicationItemData syndicationItem) { var parameters = new DialogParameters(); - parameters.Add("Data", feedItem); + parameters.Add("Data", syndicationItem); _dialogService.Show("", parameters, _dialogOptions); - Log.Verbose("Item: {ItemId} clicked", feedItem.Id); + Log.Verbose("Item: {ItemId} clicked", syndicationItem.Id); } } \ No newline at end of file diff --git a/WebSharpRSS/Shared/ItemView.razor b/WebSharpRSS/Shared/ItemView.razor index 25281e6..3e21285 100644 --- a/WebSharpRSS/Shared/ItemView.razor +++ b/WebSharpRSS/Shared/ItemView.razor @@ -25,5 +25,5 @@ else @code { [Parameter] - public FeedItemData? FeedItem { get; set; } + public SyndicationItemData? FeedItem { get; set; } } \ No newline at end of file diff --git a/WebSharpRSS/Shared/ReadDialog.razor b/WebSharpRSS/Shared/ReadDialog.razor index a4e3edb..1982e2a 100644 --- a/WebSharpRSS/Shared/ReadDialog.razor +++ b/WebSharpRSS/Shared/ReadDialog.razor @@ -8,6 +8,6 @@ @code { [Parameter] - public FeedItemData? Data { get; set; } + public SyndicationItemData? Data { get; set; } } \ No newline at end of file diff --git a/WebSharpRSS/Shared/SideGuide.razor b/WebSharpRSS/Shared/SideGuide.razor index afbf98d..e3d35e8 100644 --- a/WebSharpRSS/Shared/SideGuide.razor +++ b/WebSharpRSS/Shared/SideGuide.razor @@ -49,9 +49,9 @@ private void ItemClicked() { if (_selectedItem == null) return; - if (_selectedItem.FeedModel != null) + if (_selectedItem.SyndicationModel != null) { - _navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.EncodedUrl}"); + _navigation.NavigateTo($"/list?fid={_selectedItem.SyndicationModel.EncodedUrl}"); } else if (_selectedItem.CategoryModel != null) { @@ -71,11 +71,11 @@ { 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 _syndicationService.GetCategoriesAndFeedsAsync(); + HashSet items = await _syndicationService.GetCategoriesAndSyndicationsAsync(); _guideItems.UnionWith(ModelToTreeItem(items)); StateHasChanged(); Log.Verbose("Guide initialized!"); } - private HashSet ModelToTreeItem(HashSet model) => model.Select(x => x is CategoryModel model ? new TreeItemData(model) : x is FeedModel feedModel ? new TreeItemData(feedModel) : throw new ArgumentException("Item arg is invalid!")).ToHashSet(); + private HashSet ModelToTreeItem(HashSet model) => model.Select(x => x is CategoryModel model ? new TreeItemData(model) : x is SyndicationModel feedModel ? new TreeItemData(feedModel) : throw new ArgumentException("Item arg is invalid!")).ToHashSet(); } \ No newline at end of file diff --git a/WebSharpRSS/WebSharpRSS.csproj b/WebSharpRSS/WebSharpRSS.csproj index 7fd8070..be2aca9 100644 --- a/WebSharpRSS/WebSharpRSS.csproj +++ b/WebSharpRSS/WebSharpRSS.csproj @@ -16,7 +16,7 @@ - +