SharpRSS/SharpRss/DbAccess.cs

252 lines
14 KiB
C#
Raw Normal View History

2023-06-04 19:00:46 +02:00
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.Core;
2023-06-04 19:00:46 +02:00
using SharpRss.Models;
namespace SharpRss
{
internal static class DbAccess
{
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)
{
2023-06-05 15:15:18 +02:00
if (synContainer.Category != null)
{
CategoryModel? catModel = await SetCategoryAsync(synContainer.Category);
if (catModel != null)
2023-06-16 22:53:26 +02:00
synContainer.SyndicationModel.CategoryId = catModel.Id;
}
2023-06-16 22:53:26 +02:00
if (synContainer.SyndicationModel != null)
await SetSyndicationAsync(synContainer.SyndicationModel);
if (synContainer.SyndicationItems != null && synContainer.SyndicationItems.Any())
await SetSyndicationItemsAsync(synContainer.SyndicationItems);
2023-06-04 19:00:46 +02:00
}
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(),
Icon = reader["icon"].ToString()
};
2023-06-16 22:53:26 +02:00
categoryModel.SyndicationCount = await dbc.ExecuteScalarAsync<int>($"SELECT COUNT(*) FROM feed WHERE category_id=@CatId", new { CatId = categoryModel.Id });
2023-06-04 19:00:46 +02:00
categories.Add(categoryModel);
}
return categories;
}
public static async Task<CategoryModel?> SetCategoryAsync(CategoryModel category)
2023-06-04 19:00:46 +02:00
{
CategoryModel? modelReturn = null;
if (category == null) return modelReturn;
2023-06-04 19:00:46 +02:00
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 });
if (affected <= 0) return modelReturn;
var catModel = await GetCategoriesAsync();
modelReturn = catModel.Where(x => x.Name == category.Name).ToHashSet().FirstOrDefault() ?? null;
return modelReturn;
2023-06-04 19:00:46 +02:00
}
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;
}
2023-06-16 22:53:26 +02:00
public static async Task SetSyndicationAsync(SyndicationModel syndication)
2023-06-04 19:00:46 +02:00
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed
(encoded_url,
title,
category_id,
feed_type,
feed_version,
description,
language,
copyright,
publication_date,
last_updated,
categories,
image_url)
VALUES (
@EncodedUrl,
@Title,
@CategoryId,
@FeedType,
@FeedVersion,
@Description,
@Language,
@Copyright,
@PublicationDate,
@LastUpdated,
@Categories,
@ImageUrl)",
2023-06-05 15:15:18 +02:00
new
{
2023-06-16 22:53:26 +02:00
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)
2023-06-16 22:53:26 +02:00
Log.Warning("Failed to add feed: {FeedUrl}", syndication.EncodedUrl);
2023-06-04 19:00:46 +02:00
}
2023-06-16 22:53:26 +02:00
public static async Task<HashSet<SyndicationModel>> GetSyndicationsAsync(string[]? categoryIds = null)
2023-06-04 19:00:46 +02:00
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
2023-06-16 22:53:26 +02:00
HashSet<SyndicationModel> feeds = new HashSet<SyndicationModel>();
2023-06-10 20:27:26 +02:00
await using DbDataReader reader = await dbc.ExecuteReaderAsync(categoryIds == null ? "SELECT * FROM feed" : "SELECT * FROM feed WHERE category_id IN(@CatIds)", new { CatIds = categoryIds });
2023-06-04 19:00:46 +02:00
while (await reader.ReadAsync())
{
2023-06-16 22:53:26 +02:00
SyndicationModel syndicationModel = new SyndicationModel()
2023-06-04 19:00:46 +02:00
{
EncodedUrl = reader["encoded_url"].ToString(),
2023-06-04 19:00:46 +02:00
Title = reader["title"].ToString(),
CategoryId = reader["category_id"].ToString(),
2023-06-16 22:53:26 +02:00
SyndicationType = reader["feed_type"].ToString(),
SyndicationVersion = reader["feed_version"].ToString(),
2023-06-04 19:00:46 +02:00
Description = reader["description"].ToString(),
Language = reader["language"].ToString(),
Copyright = reader["copyright"].ToString(),
PublicationDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publication_date"].ToString())),
2023-06-16 22:53:26 +02:00
SynUpdatedDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())),
Categories = SyndicationManager.StringToStringArray(reader["categories"].ToString()),
2023-06-04 19:00:46 +02:00
ImageUrl = reader["image_url"].ToString()
};
2023-06-16 22:53:26 +02:00
syndicationModel.ItemCount = await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = syndicationModel.EncodedUrl });
feeds.Add(syndicationModel);
2023-06-04 19:00:46 +02:00
}
return feeds;
}
2023-06-16 22:53:26 +02:00
public static async Task<bool> DeleteSyndicationAsync(SyndicationModel syndication, bool deleteItems = false)
{
2023-06-16 22:53:26 +02:00
if (syndication == null) return false;
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
2023-06-16 22:53:26 +02:00
int affected = await dbc.ExecuteAsync("DELETE FROM feed WHERE encoded_url=@EncodedUrl", new { EncodedUrl = syndication.EncodedUrl });
if (affected > 0 && deleteItems)
2023-06-16 22:53:26 +02:00
await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = syndication.EncodedUrl });
return affected > 0;
}
2023-06-16 22:53:26 +02:00
public static async Task SetSyndicationItemsAsync(HashSet<SyndicationItemModel> items)
2023-06-04 19:00:46 +02:00
{
2023-06-05 15:15:18 +02:00
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
int totalAffected = 0;
await using SqliteTransaction dbTransaction = dbc.BeginTransaction();
2023-06-16 22:53:26 +02:00
foreach (SyndicationItemModel item in items)
2023-06-05 15:15:18 +02:00
{
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)",
2023-06-05 15:15:18 +02:00
transaction: dbTransaction,
param: new
{
Id = item.Id ?? string.Empty,
2023-06-16 22:53:26 +02:00
EncodedFeedUrl = item.EncodedSyndicationUrl ?? string.Empty,
2023-06-05 15:15:18 +02:00
Read = item.Read.ToString(),
Title = item.Title ?? string.Empty,
2023-06-05 15:15:18 +02:00
Description = item.Description ?? string.Empty,
Link = item.Link ?? string.Empty,
2023-06-16 22:53:26 +02:00
LastUpdated = item.ItemUpdatedDate?.ToUnixTimeMilliseconds() ?? 0,
2023-06-05 15:15:18 +02:00
PublishingDate = item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0,
2023-06-16 22:53:26 +02:00
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,
2023-06-05 15:15:18 +02:00
Content = item.Content ?? string.Empty
});
totalAffected += affected;
2023-06-05 15:15:18 +02:00
}
dbTransaction.Commit();
2023-06-04 19:00:46 +02:00
}
2023-06-16 22:53:26 +02:00
public static async Task<HashSet<SyndicationItemModel>> GetSyndicationItemsAsync(string[]? encodedFeedUrls)
2023-06-04 19:00:46 +02:00
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
2023-06-16 22:53:26 +02:00
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.
2023-06-04 19:00:46 +02:00
while (await reader.ReadAsync())
{
2023-06-16 22:53:26 +02:00
SyndicationItemModel syndicationItemModel = new SyndicationItemModel()
2023-06-04 19:00:46 +02:00
{
Id = reader["id"].ToString(),
2023-06-16 22:53:26 +02:00
EncodedSyndicationUrl = reader["encoded_feed_url"].ToString(),
2023-06-05 15:15:18 +02:00
Read = bool.Parse(reader["read"].ToString()),
2023-06-04 19:00:46 +02:00
Title = reader["title"].ToString(),
Description = reader["description"].ToString(),
Link = reader["link"].ToString(),
2023-06-16 22:53:26 +02:00
ItemUpdatedDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())),
2023-06-04 19:00:46 +02:00
PublishingDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["publishing_date"].ToString())),
2023-06-16 22:53:26 +02:00
Authors = SyndicationManager.StringToStringArray(reader["authors"].ToString()),
Categories = SyndicationManager.StringToStringArray(reader["categories"].ToString()),
2023-06-04 19:00:46 +02:00
Content = reader["content"].ToString()
};
2023-06-16 22:53:26 +02:00
syndicationItemModel.SyndicationParent = fCache.CacheFeed(syndicationItemModel.EncodedSyndicationUrl) ?? new SyndicationModel();
items.Add(syndicationItemModel);
2023-06-04 19:00:46 +02:00
}
Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count);
2023-06-04 19:00:46 +02:00
return items;
}
public static async void Initialize()
2023-06-04 19:00:46 +02:00
{
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 (encoded_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)");
2023-06-04 19:00:46 +02:00
Log.Verbose("Checking table: {Table}", "feed_item");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, encoded_feed_url STRING, read STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)");
2023-06-04 19:00:46 +02:00
Log.Verbose("Checking database done!");
_isInitialized = true;
}
private static string FormatParametersFromArray(string[] dbParams)
{
string[] formatted = dbParams.Select(s => $"'{s}'").ToArray();
return string.Join(", ", formatted);
}
2023-06-16 22:53:26 +02:00
public static async Task<int> GetSyndicationCountAsync(string[] encodedSyndicationUrls)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
2023-06-16 22:53:26 +02:00
dbc.Open();
return await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)");
}
2023-06-04 19:00:46 +02:00
}
}