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 ;
2023-06-15 20:04:15 +02:00
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 )
2023-06-10 19:06:30 +02:00
{
CategoryModel ? catModel = await SetCategoryAsync ( synContainer . Category ) ;
if ( catModel ! = null )
2023-06-16 22:53:26 +02:00
synContainer . SyndicationModel . CategoryId = catModel . Id ;
2023-06-10 19:06:30 +02:00
}
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 ;
}
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 ;
}
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 ( ) ;
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-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
2023-06-09 15:13:53 +02:00
} ) ;
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
{
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 ( ) ,
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-11 02:28:16 +02:00
{
2023-06-16 22:53:26 +02:00
if ( syndication = = null ) return false ;
2023-06-11 02:28:16 +02:00
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 } ) ;
2023-06-11 02:28:16 +02:00
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 } ) ;
2023-06-11 02:28:16 +02:00
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
{
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-16 22:53:26 +02:00
EncodedFeedUrl = item . EncodedSyndicationUrl ? ? 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 ,
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
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-15 20:04:15 +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 > ( ) ;
2023-06-15 20:04:15 +02:00
FeedCache fCache = new FeedCache ( ) ;
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 ( ) )
{
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
}
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 ) ;
}
2023-06-16 22:53:26 +02:00
public static async Task < int > GetSyndicationCountAsync ( string [ ] encodedSyndicationUrls )
2023-06-11 02:28:16 +02:00
{
await using SqliteConnection dbc = new SqliteConnection ( ConnectionString ) ;
2023-06-16 22:53:26 +02:00
dbc . Open ( ) ;
2023-06-11 02:28:16 +02:00
return await dbc . ExecuteScalarAsync < int > ( "SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)" ) ;
}
2023-06-04 19:00:46 +02:00
}
}