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 ;
2023-06-17 14:04:26 +02:00
using SharpRss.Core.Cache ;
2023-06-04 19:00:46 +02:00
using SharpRss.Models ;
2023-08-27 19:54:36 +02:00
using ToolQit ;
using ToolQit.Logging ;
2023-06-04 19:00:46 +02:00
namespace SharpRss
{
2023-07-16 20:10:02 +02:00
public class ItemFetchState
{
public string [ ] ? SyndicationUrls { get ; internal set ; }
public int TakeAmount { get ; internal set ; } = 20 ;
public int TakenCount { get ; internal set ; }
public HashSet < SyndicationItemModel > Items { get ; internal set ; } = new HashSet < SyndicationItemModel > ( ) ;
}
2023-06-04 19:00:46 +02:00
internal static class DbAccess
{
2023-08-27 19:54:36 +02:00
private static readonly ILog _log = LogManager . CreateLogger ( typeof ( DbAccess ) ) ;
2023-06-04 19:00:46 +02:00
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 ( ) ,
2023-06-17 14:04:26 +02:00
Icon = reader [ "icon" ] . ToString ( ) ,
LastUpdated = DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( reader [ "last_updated" ] . ToString ( ) ) )
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 ( ) ;
2023-06-24 11:22:14 +02:00
int affected = await dbc . ExecuteAsync ( "INSERT OR REPLACE INTO category (id, name, hex_color, icon, last_updated) VALUES (IFNULL((SELECT id FROM category WHERE name=@Name), @Id), @Name, @HexColor, @Icon, @LastUpdated)" ,
new { Id = category . Id , Name = category . Name , HexColor = category . HexColor , Icon = category . Icon , LastUpdated = category . LastUpdated . ToUnixTimeMilliseconds ( ) } ) ;
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 ( ) ;
2023-06-17 14:04:26 +02:00
int affected = await dbc . ExecuteAsync ( "DELETE FROM category WHERE id=@Id; UPDATE syndication SET category_id=NULL WHERE category_id=@Id" , new { category . Id } ) ;
2023-06-04 19:00:46 +02:00
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-17 14:04:26 +02:00
int affected = await dbc . ExecuteAsync ( @ "INSERT OR REPLACE INTO syndication
2023-06-11 02:28:16 +02:00
( encoded_url ,
2023-06-10 19:06:30 +02:00
title ,
category_id ,
2023-06-17 14:04:26 +02:00
syndication_type ,
version ,
2023-06-10 19:06:30 +02:00
description ,
language ,
copyright ,
last_updated ,
2023-06-17 14:04:26 +02:00
publication_date ,
syn_updated_date ,
2023-06-10 19:06:30 +02:00
categories ,
image_url )
VALUES (
2023-06-11 02:28:16 +02:00
@EncodedUrl ,
2023-06-10 19:06:30 +02:00
@Title ,
@CategoryId ,
2023-06-17 14:04:26 +02:00
@SyndicationType ,
@Version ,
2023-06-10 19:06:30 +02:00
@Description ,
@Language ,
@Copyright ,
@LastUpdated ,
2023-06-17 14:04:26 +02:00
@PublicationDate ,
@SynUpdatedDate ,
2023-06-10 19:06:30 +02:00
@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 ,
2023-06-17 14:04:26 +02:00
SyndicationType = syndication . SyndicationType ? ? string . Empty ,
Version = syndication . Version ? ? string . Empty ,
2023-06-16 22:53:26 +02:00
Description = syndication . Description ? ? string . Empty ,
Language = syndication . Language ? ? string . Empty ,
Copyright = syndication . Copyright ? ? string . Empty ,
2023-06-17 14:04:26 +02:00
LastUpdated = syndication . LastUpdated . ToUnixTimeMilliseconds ( ) ,
2023-06-16 22:53:26 +02:00
PublicationDate = syndication . PublicationDate ? . ToUnixTimeMilliseconds ( ) ? ? 0 ,
2023-06-17 14:04:26 +02:00
SynUpdatedDate = syndication . SynUpdatedDate ? . ToUnixTimeMilliseconds ( ) ? ? 0 ,
2023-06-16 22:53:26 +02:00
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-08-27 19:54:36 +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-17 14:04:26 +02:00
await TempCache . UpdateCache ( CacheFetch . Category ) ;
2023-06-16 22:53:26 +02:00
HashSet < SyndicationModel > feeds = new HashSet < SyndicationModel > ( ) ;
2023-06-17 14:04:26 +02:00
await using DbDataReader reader = await dbc . ExecuteReaderAsync ( categoryIds = = null ? "SELECT * FROM syndication" : "SELECT * FROM syndication 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-17 14:04:26 +02:00
SyndicationType = reader [ "syndication_type" ] . ToString ( ) ,
Version = reader [ "version" ] . ToString ( ) ,
2023-06-04 19:00:46 +02:00
Description = reader [ "description" ] . ToString ( ) ,
Language = reader [ "language" ] . ToString ( ) ,
Copyright = reader [ "copyright" ] . ToString ( ) ,
2023-06-17 14:04:26 +02:00
LastUpdated = DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( reader [ "last_updated" ] . ToString ( ) ) ) ,
2023-06-04 19:00:46 +02:00
PublicationDate = DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( reader [ "publication_date" ] . ToString ( ) ) ) ,
2023-06-17 14:04:26 +02:00
SynUpdatedDate = DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( reader [ "syn_updated_date" ] . ToString ( ) ) ) ,
2023-06-16 22:53:26 +02:00
Categories = SyndicationManager . StringToStringArray ( reader [ "categories" ] . ToString ( ) ) ,
2023-06-04 19:00:46 +02:00
ImageUrl = reader [ "image_url" ] . ToString ( )
} ;
2023-06-17 14:04:26 +02:00
syndicationModel . Category = TempCache . GetCategory ( syndicationModel . CategoryId ) ;
syndicationModel . ItemCount = await dbc . ExecuteScalarAsync < int > ( "SELECT COUNT(*) FROM syndication_item WHERE encoded_syndication_url=@EncodedFeedUrl" , new { EncodedFeedUrl = syndicationModel . EncodedUrl } ) ;
2023-06-16 22:53:26 +02:00
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-08-27 19:54:36 +02:00
_log . Information ( "Removing syndication..." ) ;
2023-06-11 02:28:16 +02:00
await using SqliteConnection dbc = new SqliteConnection ( ConnectionString ) ;
2023-06-17 14:04:26 +02:00
int affected = await dbc . ExecuteAsync ( "DELETE FROM syndication WHERE encoded_url=@EncodedUrl" , new { EncodedUrl = syndication . EncodedUrl } ) ;
2023-06-11 02:28:16 +02:00
if ( affected > 0 & & deleteItems )
2023-06-17 14:04:26 +02:00
await dbc . ExecuteAsync ( "DELETE FROM syndication_item WHERE encoded_syndication_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-08-27 19:54:36 +02:00
_log . Information ( "Inserting syndication 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 ( ) ;
2023-06-16 22:53:26 +02:00
foreach ( SyndicationItemModel item in items )
2023-06-05 15:15:18 +02:00
{
2023-06-17 14:04:26 +02:00
int affected = await dbc . ExecuteAsync ( @ "INSERT OR REPLACE INTO syndication_item (link, encoded_syndication_url, read, type, title, description, last_updated, item_updated_date, publishing_date, authors, categories, content, comments_url)
VALUES ( @Link , @EncodedSyndicationUrl , @Read , @Type , @Title , @Description , @LastUpdated , @ItemUpdatedDate , @PublishingDate , @Authors , @Categories , @Content , @CommentsUrl ) ",
2023-06-05 15:15:18 +02:00
transaction : dbTransaction ,
param : new
{
2023-06-17 14:04:26 +02:00
Link = item . Link ? ? string . Empty ,
EncodedSyndicationUrl = item . EncodedSyndicationUrl ? ? string . Empty ,
2023-06-05 15:15:18 +02:00
Read = item . Read . ToString ( ) ,
2023-06-17 14:04:26 +02:00
Type = item . Type ? ? string . Empty ,
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 ,
2023-06-17 14:04:26 +02:00
LastUpdated = item . LastUpdated . ToUnixTimeMilliseconds ( ) ,
ItemUpdatedDate = 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-17 14:04:26 +02:00
Content = item . Content ? ? string . Empty ,
CommentsUrl = item . CommentsUrl ? ? 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-07-16 20:10:02 +02:00
2023-06-15 20:04:15 +02:00
2023-07-16 20:10:02 +02:00
public static async Task < bool > GetSyndicationItemsAsync ( ItemFetchState state )
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-17 14:04:26 +02:00
await TempCache . UpdateCache ( CacheFetch . Syndication ) ;
2023-07-16 20:10:02 +02:00
await using DbDataReader reader = await dbc . ExecuteReaderAsync ( state . SyndicationUrls = = null ? "SELECT * FROM syndication_item" : $"SELECT * FROM syndication_item WHERE encoded_syndication_url IN({FormatParametersFromArray(state.SyndicationUrls)})" ) ;
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
{
2023-06-17 14:04:26 +02:00
Link = reader [ "link" ] . ToString ( ) ,
EncodedSyndicationUrl = reader [ "encoded_syndication_url" ] . ToString ( ) ,
2023-06-05 15:15:18 +02:00
Read = bool . Parse ( reader [ "read" ] . ToString ( ) ) ,
2023-06-17 14:04:26 +02:00
Type = reader [ "type" ] . ToString ( ) ,
2023-06-04 19:00:46 +02:00
Title = reader [ "title" ] . ToString ( ) ,
Description = reader [ "description" ] . ToString ( ) ,
2023-06-17 14:04:26 +02:00
LastUpdated = DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( reader [ "last_updated" ] . ToString ( ) ) ) ,
ItemUpdatedDate = DateTimeOffset . FromUnixTimeMilliseconds ( long . Parse ( reader [ "item_updated_date" ] . 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-17 14:04:26 +02:00
Content = reader [ "content" ] . ToString ( ) ,
CommentsUrl = reader [ "comments_url" ] . ToString ( )
2023-06-04 19:00:46 +02:00
} ;
2023-06-17 14:04:26 +02:00
syndicationItemModel . SyndicationParent = TempCache . GetSyndication ( syndicationItemModel . EncodedSyndicationUrl ) ? ? new SyndicationModel ( ) ; // The new syndication should never be initialized, if this hits then the date is not valid for some reason!!!
2023-06-16 22:53:26 +02:00
items . Add ( syndicationItemModel ) ;
2023-06-04 19:00:46 +02:00
}
2023-08-27 19:54:36 +02:00
_log . Information ( "Fetching feed items resulted: {ItemCount} item(s)" , items . Count ) ;
2023-07-16 20:10:02 +02:00
state . Items = items ;
return true ;
2023-06-04 19:00:46 +02:00
}
2023-06-10 19:06:30 +02:00
public static async void Initialize ( )
2023-06-04 19:00:46 +02:00
{
if ( _isInitialized ) return ;
2023-08-27 19:54:36 +02:00
_log . Verbose ( "Checking database..." ) ;
2023-06-04 19:00:46 +02:00
await using SqliteConnection dbc = new SqliteConnection ( ConnectionString ) ;
dbc . Open ( ) ;
2023-08-27 19:54:36 +02:00
_log . Information ( "Checking table: {Table}" , "category" ) ;
2023-06-17 14:04:26 +02:00
await dbc . ExecuteAsync ( "CREATE TABLE IF NOT EXISTS category (id STRING PRIMARY KEY, name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, last_updated INT)" ) ;
2023-06-04 19:00:46 +02:00
2023-08-27 19:54:36 +02:00
_log . Information ( "Checking table: {Table}" , "syndication" ) ;
2023-06-17 14:04:26 +02:00
await dbc . ExecuteAsync ( "CREATE TABLE IF NOT EXISTS syndication (encoded_url STRING PRIMARY KEY, title STRING, category_id STRING, syndication_type STRING, version STRING, description STRING, language STRING, copyright STRING, last_updated INT, publication_date INT, syn_updated_date INT, categories STRING, image_url STRING)" ) ;
2023-06-04 19:00:46 +02:00
2023-08-27 19:54:36 +02:00
_log . Information ( "Checking table: {Table}" , "syndication_item" ) ;
2023-06-17 14:04:26 +02:00
await dbc . ExecuteAsync ( "CREATE TABLE IF NOT EXISTS syndication_item (link STRING PRIMARY KEY, encoded_syndication_url STRING, read STRING, type STRING, title STRING, description STRING, last_updated INT, item_updated_date INT, publishing_date INT, authors STRING, categories STRING, content STRING, comments_url STRING)" ) ;
2023-08-27 19:54:36 +02:00
_log . Information ( "Checking database done!" ) ;
2023-06-04 19:00:46 +02:00
_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-17 14:04: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-17 14:04:26 +02:00
return await dbc . ExecuteScalarAsync < int > ( "SELECT COUNT(*) FROM syndication WHERE encoded_url IN(@Urls)" ) ;
} * /
2023-06-04 19:00:46 +02:00
}
}