Reworking DbAccess, 70% done!

This commit is contained in:
Max 2023-06-04 19:00:46 +02:00
parent 86337f92a2
commit 6bcc053014
10 changed files with 212 additions and 53 deletions

163
SharpRss/DbAccess.cs Normal file
View File

@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Data.Sqlite;
using Serilog;
using SharpRss.Models;
namespace SharpRss
{
internal static class DbAccess
{
static DbAccess()
{
Initialize();
}
private static readonly string ConnectionString = $"Data Source={Path.Combine(Environment.CurrentDirectory, "sharp_rss.sqlite")};";
private static bool _isInitialized;
public static async Task SetSyndicationAsync(SyndicationContainer synContainer)
{
}
public static async Task<HashSet<CategoryModel>> GetCategoriesAsync()
{
HashSet<CategoryModel> categories = new HashSet<CategoryModel>();
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
await using DbDataReader reader = await dbc.ExecuteReaderAsync("SELECT * FROM category");
while (await reader.ReadAsync())
{
CategoryModel categoryModel = new CategoryModel()
{
Id = reader["id"].ToString(),
Name = reader["name"].ToString(),
HexColor = reader["hex_color"].ToString(),
FeedCount = 0, // Not implemented.
Icon = reader["icon"].ToString()
};
categories.Add(categoryModel);
}
return categories;
}
public static async Task<bool> SetCategoryAsync(CategoryModel category)
{
if (category == null) return false;
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
int affected = await dbc.ExecuteAsync("INSERT OR REPLACE INTO category (id, hex_color, icon, name) VALUES (IFNULL((SELECT id FROM category WHERE name=@Name), @Id), @HexColor, @Icon, @Name)",
new { category.Id, category.HexColor, category.Icon, category.Name });
return affected > 0;
}
public static async Task<bool> DeleteCategory(CategoryModel category)
{
if (category == null) return false;
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
int affected = await dbc.ExecuteAsync("DELETE FROM category WHERE id=@Id; UPDATE feed SET category_id=NULL WHERE category_id=@Id",new { category.Id });
return affected > 0;
}
public static async Task SetFeedAsync(FeedModel feed)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
int affected = await dbc.ExecuteAsync("INSERT OR REPLACE INTO feed (url, title, category_id, feed_type, feed_version, description, language, copyright, publication_date, last_updated, categories, image_url) VALUES (@Url, @Title, @CategoryId, @FeedType, @FeedVersion, @Description, @language, @Copyright, @PublicationDate, @LastUpdated, @Categories, @ImageUrl)", new
{
Url = feed.OriginalUrl,
Title = feed.Title,
CategoryId = feed.CategoryId,
FeedType = feed.FeedType,
Description = feed.Description,
Language = feed.Language,
Copyright = feed.Copyright,
PublicationDate = feed.PublicationDate?.ToUnixTimeMilliseconds() ?? 0,
LastUpdated = feed.LastUpdated?.ToUnixTimeMilliseconds() ?? 0,
Categories = string.Join(',', feed.Categories),
ImageUrl = feed.ImageUrl
});
if (affected == 0)
Log.Warning("Failed to add feed: {FeedUrl}", feed.OriginalUrl);
}
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
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()
{
OriginalUrl = reader["url"].ToString(),
Title = reader["title"].ToString(),
CategoryId = reader["category_id"].ToString(),
FeedType = reader["feed_type"].ToString(),
FeedVersion = 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(','),
ImageUrl = reader["image_url"].ToString()
};
feeds.Add(feedModel);
}
return feeds;
}
public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items)
{
}
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? feedUrls)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
HashSet<FeedItemModel> items = new HashSet<FeedItemModel>();
await using DbDataReader reader = await dbc.ExecuteReaderAsync(feedUrls == null ? "SELECT * FROM feed_item" : "SELECT * FROM feed_item WHERE feed_item.feed_url IN(@Urls)", new { Urls = feedUrls });
while (await reader.ReadAsync())
{
FeedItemModel feedItemModel = new FeedItemModel()
{
Id = reader["id"].ToString(),
FeedUrl = 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()
};
items.Add(feedItemModel);
}
return items;
}
private static async void Initialize()
{
if (_isInitialized) return;
Log.Verbose("Checking database...");
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
Log.Verbose("Checking table: {Table}", "category");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS category (id STRING PRIMARY KEY, name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING)");
Log.Verbose("Checking table: {Table}", "feed");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (url STRING PRIMARY KEY, title STRING, category_id STRING, feed_type STRING, feed_version STRING, description STRING, language STRING, copyright STRING, publication_date INT, last_updated INT, categories STRING, image_url STRING)");
Log.Verbose("Checking table: {Table}", "feed_item");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, feed_url STRING, read INT, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)");
Log.Verbose("Checking database done!");
_isInitialized = true;
}
}
}

View File

@ -149,7 +149,7 @@ namespace SharpRss
{
new SqliteParameter("url", feedModel.OriginalUrl ?? string.Empty),
new SqliteParameter("title", feedModel.Title ?? string.Empty),
new SqliteParameter("groupId", feedModel.GroupId ?? 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),
@ -268,7 +268,7 @@ namespace SharpRss
FeedItemModel feedItemModel = new FeedItemModel()
{
Id = reader["id"].ToString(),
FeedId = reader["feed_id"].ToString(),
FeedUrl = reader["feed_url"].ToString(),
Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0,
Title = reader["title"].ToString(),
Description = reader["description"].ToString(),
@ -281,11 +281,6 @@ namespace SharpRss
Categories = reader["categories"].ToString().Split(','),
Content = reader["content"].ToString()
};
if (feedItemModel is { FeedId: { } })
{
FeedModel? feedModel = await GetFeedAsync(feedItemModel.FeedUrl);
//feedItemModel.Feed = feedModel;
}
feedItems.Add(feedItemModel);
}
return feedItems;
@ -359,8 +354,8 @@ namespace SharpRss
};
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
HashSet<CategoryModel>? groups = null;
if (reader.Read())
groups = await GetCategoriesAsync(reader["group_id"].ToString());
/*if (reader.Read())
groups = await GetCategoriesAsync(reader["group_id"].ToString());*/
if (groups != null && groups.Any())
result = groups.FirstOrDefault();
return result;
@ -371,7 +366,7 @@ namespace SharpRss
{
OriginalUrl = reader["url"].ToString(),
Title = reader["title"].ToString(),
GroupId = reader["group_id"].ToString(),
CategoryId = reader["group_id"].ToString(),
FeedType = reader["feed_type"].ToString(),
Description = reader["description"].ToString(),
Language = reader["language"].ToString(),

View File

@ -1,20 +1,35 @@
using System;
using ToolQit;
using ToolQit.Extensions;
namespace SharpRss.Models
{
public class CategoryModel
{
public CategoryModel()
private string _id = string.Empty;
public string Id
{
HexColor = Utilities.GenerateRandomHexColor();
Id = Guid.NewGuid().ToString();
get
{
if (_id.IsNullEmptyWhiteSpace())
_id = Guid.NewGuid().ToString();
return _id;
}
set => _id = value;
}
public string Name { get; set; } = string.Empty;
public string HexColor { get; set; }
private string _hexColor = string.Empty;
public string HexColor
{
get
{
if (_hexColor.IsNullEmptyWhiteSpace())
_hexColor = Utilities.GenerateRandomHexColor();
return _hexColor;
}
set => _hexColor = value;
}
public int FeedCount { get; set; }
public string Icon { get; set; } = string.Empty;
public string Id { get; set; }
}
}

View File

@ -5,8 +5,6 @@ namespace SharpRss.Models
public class FeedItemModel
{
public string? Id { get; set; } = string.Empty;
// FeedId will be removed
public string? FeedId { get; set; } = string.Empty;
public string FeedUrl { get; set; } = string.Empty;
public bool Read { get; set; }
public string? Type { get; set; } = string.Empty;

View File

@ -6,10 +6,9 @@ namespace SharpRss.Models
{
public class FeedModel
{
public CategoryModel? Category { get; set; }
public string OriginalUrl { get; set; } = string.Empty;
public string? Title { get; set; } = string.Empty;
public string? GroupId { 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? Description { get; set; } = string.Empty;

View File

@ -11,7 +11,7 @@ namespace SharpRss.Services
/// <summary>
/// Managing RSS feeds and groups.
/// </summary>
public class RssService : IDisposable
public class RssService
{
public RssService()
{
@ -26,28 +26,17 @@ namespace SharpRss.Services
return items;
}
public async Task<bool> CreateGroupAsync(CategoryModel group) => await DbAccess_Old.SetCategoryAsync(group);
public async Task<HashSet<CategoryModel>> GetCategoriesAsync() => await DbAccess_Old.GetCategoriesAsync();
public async Task<HashSet<CategoryModel>> GetCategoriesAsync() => await DbAccess.GetCategoriesAsync();
//TODO: Rework this!
// Subscribe to a feed.
public async Task<bool> AddSubscriptionAsync(string url, CategoryModel? group = null)
public async Task<bool> AddSubscriptionAsync(string url, CategoryModel? category = null)
{
/*if (!SyndicationManager.GetFeed(url, out GenericSyndicationFeed? genFeed)) return false;*/
var feed = SyndicationManager.CreateSyndication(url);
// Check if feed exists in db
FeedModel? fModel = await DbAccess_Old.GetFeedAsync(url);
// If not exists fetch feed & add.
if (fModel == null)
{
/*if (!SyndicationManager.TryGetGenericFeed(url, out GenericSyndicationFeed? genFeed)) return false;*/
/*if (genFeed == null) return false;
fModel = FromResource(genFeed.Resource);
fModel.GroupId = group?.Id;*/
// Add feed
//FeedModel? dbFeed = await DbAccess.SetFeedAsync(fModel);
// Update/fetch items
//await DbAccess.FetchFeedItemsAsync(new string[] { fModel.Url });
}
var syndication = SyndicationManager.CreateSyndication(url);
if (category != null)
syndication.Category = category;
if (!syndication.Fetched) return false;
return true;
}
private static FeedModel FromResource(ISyndicationResource resource)
@ -62,7 +51,7 @@ namespace SharpRss.Services
model.Description = rssFeed.Channel.Description;
model.Copyright = rssFeed.Channel.Copyright;
model.OriginalUrl = rssFeed.Channel.SelfLink.ToString();
model.ImageUrl = rssFeed.Channel.Image?.Url.ToString();
model.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
model.Language = rssFeed.Channel.Language?.ToString();
break;
case SyndicationContentFormat.Atom:
@ -81,13 +70,13 @@ namespace SharpRss.Services
var feeds = await GetFeedsAsync();
}
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? groupId = null) => await DbAccess_Old.GetFeedsAsync(groupId);
public async Task<HashSet<FeedModel>> GetUngroupedFeedsAsync() => await DbAccess_Old.GetFeedsAsync("");
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? groupId = null) => await DbAccess.GetFeedsAsync();
public async Task<HashSet<FeedModel>> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync();
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? groupId = null)
{
var items = await DbAccess_Old.GetFeedItemsAsync(feedIds);
var items = await DbAccess.GetFeedItemsAsync(feedIds);
return items;
}
@ -140,7 +129,9 @@ namespace SharpRss.Services
}
private async void SetupTestGroupsAndFeedsAsync()
{
await AddSubscriptionAsync("https://en.wikipedia.org/w/api.php?hidebots=1&hidecategorization=1&hideWikibase=1&urlversion=1&days=7&limit=50&action=feedrecentchanges&feedformat=atom");
/*CategoryModel newModel = new CategoryModel() { Name = "Test" };
bool added = await DbAccess.SetCategoryAsync(newModel);*/
//await AddSubscriptionAsync("https://en.wikipedia.org/w/api.php?hidebots=1&hidecategorization=1&hideWikibase=1&urlversion=1&days=7&limit=50&action=feedrecentchanges&feedformat=atom");
//TODO: Make multiple adding of feed to a transaction, now throws an exception.
/*var groupRes = await CreateGroupAsync(new GroupModel() { Name = "Test" });
groupRes = await CreateGroupAsync(new GroupModel() { Name = "News" });
@ -164,7 +155,7 @@ namespace SharpRss.Services
}*/
/*var groups = await GetGroupsAsync();
CategoryModel testGroup = groups.Single(x => x.Name == "News");*/
/*await AddFeedsAsync(new[]
{
"https://www.nu.nl/rss/Algemeen",
@ -173,9 +164,5 @@ namespace SharpRss.Services
"http://news.google.com/?output=atom"
}, testGroup);*/
}
public void Dispose()
{
}
}
}

View File

@ -10,15 +10,13 @@ using SharpRss.Models;
namespace SharpRss
{
/// <summary>
/// Struct that contains the necessary objects for adding/fetching feeds and items
/// </summary>
public struct SyndicationContainer
{
public GenericSyndicationFeed SyndicationFeed { get; set; }
public CategoryModel Category { get; set; }
public FeedModel FeedModel { get; set; }
public HashSet<FeedItemModel> FeedItems { get; set; }
public bool Fetched;
}
public static class SyndicationManager
{
@ -127,6 +125,7 @@ namespace SharpRss
Log.Warning("Feed implementation missing!");
break;
}
container.Fetched = true;
return container;
}
}

View File

@ -19,7 +19,7 @@ namespace WebSharpRSS
Caretaker.Settings.SetAppDefaultSettings();
SetupLogging();
Log.Information("Starting SharpRSS...");
DbAccess_Old.Initialize();
//DbAccess_Old.Initialize();
_bootstrapped = true;
}

View File

@ -40,6 +40,9 @@
<_ContentIncludedByDefault Remove="logs\log_20230526.json" />
<_ContentIncludedByDefault Remove="logs\log_20230527.json" />
<_ContentIncludedByDefault Remove="logs\log_20230529.json" />
<_ContentIncludedByDefault Remove="logs\log_20230602.json" />
<_ContentIncludedByDefault Remove="logs\log_20230603.json" />
<_ContentIncludedByDefault Remove="logs\log_20230604.json" />
</ItemGroup>
</Project>

Binary file not shown.