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.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 )
2023-06-10 19:06:30 +02:00
{
CategoryModel ? catModel = await SetCategoryAsync ( synContainer . Category ) ;
if ( catModel ! = null )
synContainer . FeedModel . CategoryId = catModel . Id ;
}
2023-06-05 15:15:18 +02:00
if ( synContainer . FeedModel ! = null )
await SetFeedAsync ( synContainer . FeedModel ) ;
if ( synContainer . FeedItems ! = null & & synContainer . FeedItems . Any ( ) )
await SetFeedItemsAsync ( synContainer . FeedItems ) ;
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-10 20:27:26 +02:00
categoryModel . FeedCount = 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 ;
}
2023-06-10 19:06:30 +02:00
public static async Task < CategoryModel ? > SetCategoryAsync ( CategoryModel category )
2023-06-04 19:00:46 +02:00
{
2023-06-10 19:06:30 +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 } ) ;
2023-06-10 19:06:30 +02:00
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 ;
}
public static async Task SetFeedAsync ( FeedModel feed )
{
await using SqliteConnection dbc = new SqliteConnection ( ConnectionString ) ;
dbc . Open ( ) ;
2023-06-10 19:06:30 +02:00
int affected = await dbc . ExecuteAsync ( @ "INSERT OR REPLACE INTO feed
2023-06-11 02:28:16 +02:00
( encoded_url ,
2023-06-10 19:06:30 +02:00
title ,
category_id ,
feed_type ,
feed_version ,
description ,
language ,
copyright ,
publication_date ,
last_updated ,
categories ,
image_url )
VALUES (
2023-06-11 02:28:16 +02:00
@EncodedUrl ,
2023-06-10 19:06:30 +02:00
@Title ,
@CategoryId ,
@FeedType ,
@FeedVersion ,
@Description ,
@Language ,
@Copyright ,
@PublicationDate ,
@LastUpdated ,
@Categories ,
@ImageUrl ) ",
2023-06-05 15:15:18 +02:00
new
{
2023-06-11 02:28:16 +02:00
EncodedUrl = feed . EncodedUrl ,
2023-06-05 15:15:18 +02:00
Title = feed . Title ? ? string . Empty ,
CategoryId = feed . CategoryId ? ? string . Empty ,
FeedType = feed . FeedType ? ? string . Empty ,
2023-06-09 15:13:53 +02:00
FeedVersion = feed . FeedVersion ? ? string . Empty ,
2023-06-05 15:15:18 +02:00
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 ,
2023-06-10 19:06:30 +02:00
Categories = feed . Categories ! = null & & feed . Categories . Any ( ) ? string . Join ( ',' , feed . Categories ) : string . Empty ,
2023-06-05 15:15:18 +02:00
ImageUrl = feed . ImageUrl ? ? string . Empty
2023-06-09 15:13:53 +02:00
} ) ;
if ( affected = = 0 )
2023-06-11 02:28:16 +02:00
Log . Warning ( "Failed to add feed: {FeedUrl}" , feed . EncodedUrl ) ;
2023-06-04 19:00:46 +02:00
}
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 > ( ) ;
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 ( ) )
{
FeedModel feedModel = new FeedModel ( )
{
2023-06-11 02:28:16 +02:00
EncodedUrl = reader [ "encoded_url" ] . ToString ( ) ,
2023-06-04 19:00:46 +02:00
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 ( )
} ;
2023-06-11 02:28:16 +02:00
feedModel . ItemCount = await dbc . ExecuteScalarAsync < int > ( "SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl" , new { EncodedFeedUrl = feedModel . EncodedUrl } ) ;
2023-06-04 19:00:46 +02:00
feeds . Add ( feedModel ) ;
}
return feeds ;
}
2023-06-11 02:28:16 +02:00
public static async Task < bool > DeleteFeedAsync ( FeedModel feed , bool deleteItems = false )
{
if ( feed = = 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 } ) ;
if ( affected > 0 & & deleteItems )
await dbc . ExecuteAsync ( "DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl" , new { EncodedUrl = feed . EncodedUrl } ) ;
return affected > 0 ;
}
2023-06-04 19:00:46 +02:00
public static async Task SetFeedItemsAsync ( HashSet < FeedItemModel > items )
{
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 ( ) ;
foreach ( FeedItemModel item in items )
{
2023-06-11 02:28:16 +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-11 02:28:16 +02:00
EncodedFeedUrl = item . EncodedFeedUrl ? ? string . Empty ,
2023-06-05 15:15:18 +02:00
Read = item . Read . ToString ( ) ,
2023-06-09 15:13:53 +02:00
Title = item . Title ? ? string . Empty ,
2023-06-05 15:15:18 +02:00
Description = item . Description ? ? string . Empty ,
Link = item . Link ? ? string . Empty ,
LastUpdated = item . LastUpdated ? . ToUnixTimeMilliseconds ( ) ? ? 0 ,
PublishingDate = item . PublishingDate ? . ToUnixTimeMilliseconds ( ) ? ? 0 ,
2023-06-10 19:06:30 +02:00
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 ,
2023-06-05 15:15:18 +02:00
Content = item . Content ? ? string . Empty
2023-06-09 15:13:53 +02:00
} ) ;
2023-06-10 19:06:30 +02:00
totalAffected + = affected ;
2023-06-05 15:15:18 +02:00
}
dbTransaction . Commit ( ) ;
2023-06-04 19:00:46 +02:00
}
2023-06-11 02:28:16 +02:00
public static async Task < HashSet < FeedItemModel > > GetFeedItemsAsync ( string [ ] ? encodedFeedUrls )
2023-06-04 19:00:46 +02:00
{
await using SqliteConnection dbc = new SqliteConnection ( ConnectionString ) ;
dbc . Open ( ) ;
HashSet < FeedItemModel > items = new HashSet < FeedItemModel > ( ) ;
2023-06-11 02:28:16 +02:00
await using DbDataReader reader = await dbc . ExecuteReaderAsync ( encodedFeedUrls = = null ? "SELECT * FROM feed_item" : $"SELECT * FROM feed_item WHERE encoded_feed_url IN({FormatParametersFromArray(encodedFeedUrls)})" ) ;
2023-06-10 19:43:30 +02:00
//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 ( ) )
{
FeedItemModel feedItemModel = new FeedItemModel ( )
{
Id = reader [ "id" ] . ToString ( ) ,
2023-06-11 02:28:16 +02:00
EncodedFeedUrl = 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 ( ) ,
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 ) ;
}
2023-06-10 19:43:30 +02:00
Log . Debug ( "Fetching feed items resulted: {ItemCount} item(s)" , items . Count ) ;
2023-06-04 19:00:46 +02:00
return items ;
}
2023-06-10 19:06:30 +02:00
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" ) ;
2023-06-11 02:28:16 +02:00
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" ) ;
2023-06-11 02:28:16 +02:00
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 ;
}
2023-06-11 02:28:16 +02:00
private static string FormatParametersFromArray ( string [ ] dbParams )
{
string [ ] formatted = dbParams . Select ( s = > $"'{s}'" ) . ToArray ( ) ;
return string . Join ( ", " , formatted ) ;
}
public static async Task < int > GetFeedCountAsync ( string [ ] encodedFeedUrls )
{
await using SqliteConnection dbc = new SqliteConnection ( ConnectionString ) ;
return await dbc . ExecuteScalarAsync < int > ( "SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)" ) ;
}
2023-06-04 19:00:46 +02:00
}
}