Reworking backend...

This commit is contained in:
Max 2023-06-16 22:53:26 +02:00
parent 722f6f132a
commit ea91abe629
17 changed files with 155 additions and 565 deletions

View File

@ -12,15 +12,15 @@ namespace SharpRss.Core
FetchFeeds();
}
private readonly SyndicationService _syndicationService = new SyndicationService();
private Dictionary<string, FeedModel> _cachedFeeds = new Dictionary<string, FeedModel>();
private Dictionary<string, SyndicationModel> _cachedFeeds = new Dictionary<string, SyndicationModel>();
private async void FetchFeeds()
{
HashSet<FeedModel> fetchedFeeds = await _syndicationService.GetFeedsAsync();
HashSet<SyndicationModel> 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;
}
}

View File

@ -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<HashSet<CategoryModel>> GetCategoriesAsync()
@ -47,7 +46,7 @@ namespace SharpRss
HexColor = reader["hex_color"].ToString(),
Icon = reader["icon"].ToString()
};
categoryModel.FeedCount = await dbc.ExecuteScalarAsync<int>($"SELECT COUNT(*) FROM feed WHERE category_id=@CatId", new { CatId = categoryModel.Id });
categoryModel.SyndicationCount = await dbc.ExecuteScalarAsync<int>($"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<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null)
public static async Task<HashSet<SyndicationModel>> GetSyndicationsAsync(string[]? categoryIds = null)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
HashSet<SyndicationModel> feeds = new HashSet<SyndicationModel>();
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<int>("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = feedModel.EncodedUrl });
feeds.Add(feedModel);
syndicationModel.ItemCount = await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = syndicationModel.EncodedUrl });
feeds.Add(syndicationModel);
}
return feeds;
}
public static async Task<bool> DeleteFeedAsync(FeedModel feed, bool deleteItems = false)
public static async Task<bool> 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<FeedItemModel> items)
public static async Task SetSyndicationItemsAsync(HashSet<SyndicationItemModel> 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<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? encodedFeedUrls)
public static async Task<HashSet<SyndicationItemModel>> GetSyndicationItemsAsync(string[]? encodedFeedUrls)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
HashSet<FeedItemModel> items = new HashSet<FeedItemModel>();
HashSet<SyndicationItemModel> items = new HashSet<SyndicationItemModel>();
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<int> GetFeedCountAsync(string[] encodedFeedUrls)
public static async Task<int> GetSyndicationCountAsync(string[] encodedSyndicationUrls)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
return await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)");
}
}

View File

@ -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<HashSet<CategoryModel>> 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<CategoryModel> categories = new HashSet<CategoryModel>();
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<bool> 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<bool> 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
/// <summary>
///
/// </summary>
/// <param name="groupId">Empty = ungrouped feeds | null = all feeds | id = grouped feeds</param>
/// <returns></returns>
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string? groupId = null)
{
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
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<FeedModel?> 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<FeedModel?> 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<FeedModel> dbFeeds = new HashSet<FeedModel>();
string query = $"SELECT * FROM {FeedTable}";
if (feedUrls != null)
{
List<string>? 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<FeedItemModel> feedItems = new HashSet<FeedItemModel>();
foreach (var dbFeed in dbFeeds)
{
/*GenericSyndicationFeed syndication = new GenericSyndicationFeed();
syndication.Load(dbFeed.OriginalDocument);*/
//TODO: Get items and add to db
}
}
public static async Task<bool> 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<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? feedIds = null)
{
List<string>? formattedIds = feedIds?.Select(s => $"'{s}'").ToList();
HashSet<FeedItemModel> feedItems = new HashSet<FeedItemModel>();
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<int> SetFeedItemsAsync(HashSet<FeedItemModel> 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<bool> 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<CategoryModel?> 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<CategoryModel>? 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<FeedModel> 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<string> failed = new HashSet<string>();
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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -18,11 +18,11 @@ namespace SharpRss.Services
SetupTestCategoriesAndFeedsAsync();
}
public async Task<HashSet<object>> GetCategoriesAndFeedsAsync()
public async Task<HashSet<object>> GetCategoriesAndSyndicationsAsync()
{
HashSet<object> items = new HashSet<object>();
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<HashSet<FeedModel>> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetFeedsAsync(categoryId == null ? null : new[]{ categoryId });
public async Task<HashSet<FeedModel>> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync(new []{""});
public async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string feedId, string? groupId = null) => await GetFeedItemsFromFeedsAsync(new[] { feedId }, groupId);
public async Task<HashSet<FeedItemModel>> GetFeedItemsFromFeedsAsync(string[] feedIds, string? categoryId = null)
public async Task<HashSet<SyndicationModel>> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetSyndicationsAsync(categoryId == null ? null : new[]{ categoryId });
public async Task<HashSet<SyndicationModel>> GetUngroupedSyndicationsAsync() => await DbAccess.GetSyndicationsAsync(new []{""});
public async Task<HashSet<SyndicationItemModel>> GetSyndicationItemsAsync(string feedId, string? groupId = null) => await GetSyndicationItemsFromSyndicationsAsync(new[] { feedId }, groupId);
public async Task<HashSet<SyndicationItemModel>> 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;

View File

@ -11,9 +11,9 @@
<PackageReference Include="Argotic.Common" Version="3000.0.5" />
<PackageReference Include="Argotic.Core" Version="3000.0.5" />
<PackageReference Include="Argotic.Web" Version="2008.0.2" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Dapper" Version="2.0.138" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.5" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.7" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
</ItemGroup>

View File

@ -15,8 +15,8 @@ namespace SharpRss
{
public GenericSyndicationFeed SyndicationFeed { get; set; }
public CategoryModel Category { get; set; }
public FeedModel FeedModel { get; set; }
public HashSet<FeedItemModel> FeedItems { get; set; }
public SyndicationModel SyndicationModel { get; set; }
public HashSet<SyndicationItemModel> 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<FeedItemModel>();
container.SyndicationModel = new SyndicationModel();
container.SyndicationItems = new HashSet<SyndicationItemModel>();
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<string>();
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<string>();
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<string>();
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<string>();
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<string>(),
Categories = entry.Categories?.Select(cat => cat.Label).ToArray() ?? Array.Empty<string>(),
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();

View File

@ -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<FeedItemData, FeedItemModel>(model);
public static SyndicationItemData? FromModel(SyndicationItemModel model) => Utilities.ConvertFrom<SyndicationItemData, SyndicationItemModel>(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;
}
}

View File

@ -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<TreeItemData>? Children { get; set; }
public string Title { get; set; }

View File

@ -44,22 +44,22 @@
}
}
private string? _cid;
HashSet<FeedItemData> items = new HashSet<FeedItemData>();
HashSet<SyndicationItemData> items = new HashSet<SyndicationItemData>();
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();

View File

@ -46,15 +46,15 @@
@code {
[Parameter]
public HashSet<FeedItemData>? Items { get; set; }
public HashSet<SyndicationItemData>? 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<ReadDialog>("", parameters, _dialogOptions);
Log.Verbose("Item: {ItemId} clicked", feedItem.Id);
Log.Verbose("Item: {ItemId} clicked", syndicationItem.Id);
}
}

View File

@ -25,5 +25,5 @@ else
@code {
[Parameter]
public FeedItemData? FeedItem { get; set; }
public SyndicationItemData? FeedItem { get; set; }
}

View File

@ -8,6 +8,6 @@
@code {
[Parameter]
public FeedItemData? Data { get; set; }
public SyndicationItemData? Data { get; set; }
}

View File

@ -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<object> items = await _syndicationService.GetCategoriesAndFeedsAsync();
HashSet<object> items = await _syndicationService.GetCategoriesAndSyndicationsAsync();
_guideItems.UnionWith(ModelToTreeItem(items));
StateHasChanged();
Log.Verbose("Guide initialized!");
}
private HashSet<TreeItemData> ModelToTreeItem<T>(HashSet<T> 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<TreeItemData> ModelToTreeItem<T>(HashSet<T> 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();
}

View File

@ -16,7 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MudBlazor" Version="6.2.2" />
<PackageReference Include="MudBlazor" Version="6.4.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
</ItemGroup>