2023-05-18 01:27:11 +02:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Threading.Tasks ;
using Dapper ;
using Microsoft.Data.Sqlite ;
using Serilog ;
using SharpRss.Models ;
namespace SharpRss.Services
{
2023-05-18 20:15:31 +02:00
internal class DatabaseService : IDisposable
2023-05-18 01:27:11 +02:00
{
internal DatabaseService ( )
{
_sqlConn = new SqliteConnection ( _connectionString ) ;
2023-05-22 19:09:01 +02:00
//InitializeDb();
2023-05-18 01:27:11 +02:00
}
private readonly SqliteConnection _sqlConn ;
2023-05-20 00:04:45 +02:00
private readonly string _connectionString = $"Data Source={Path.Combine(Environment.CurrentDirectory, " sharp_rss . sqlite ")};" ;
private readonly string _groupTable = "group_data" ;
private readonly string _feedTable = "feed_data" ;
private readonly string _feedItemTable = "feed_item_data" ;
2023-05-18 01:27:11 +02:00
2023-05-22 15:55:21 +02:00
// Groups
public async Task < HashSet < GroupModel > > GetGroupsAsync ( string? groupName = null )
2023-05-20 00:04:45 +02:00
{
2023-05-21 21:56:37 +02:00
_sqlConn . Open ( ) ;
2023-05-22 13:26:27 +02:00
using SqliteCommand cmd = new SqliteCommand ( groupName ! = null ? $"SELECT * FROM {_groupTable} WHERE name=@name;" : $"SELECT * FROM {_groupTable}" , _sqlConn )
2023-05-21 21:56:37 +02:00
{
Parameters =
{
new SqliteParameter ( "name" , groupName )
}
} ;
await using SqliteDataReader reader = await cmd . ExecuteReaderAsync ( ) ;
2023-05-22 15:55:21 +02:00
HashSet < GroupModel > groups = new HashSet < GroupModel > ( ) ;
2023-05-23 15:04:02 +02:00
using SqliteCommand cmdFeedCount = new SqliteCommand ( $"SELECT COUNT(*) FROM {_feedTable} WHERE group_id=@groupId" , _sqlConn ) ;
2023-05-21 21:56:37 +02:00
while ( reader . Read ( ) )
{
2023-05-23 15:04:02 +02:00
cmdFeedCount . Parameters . Clear ( ) ;
cmdFeedCount . Parameters . Add ( new SqliteParameter ( "groupId" , reader [ "id" ] . ToString ( ) ) ) ;
using SqliteDataReader countReader = await cmdFeedCount . ExecuteReaderAsync ( ) ;
int count = countReader . Read ( ) ? countReader . GetInt32 ( 0 ) : 0 ;
groups . Add ( new GroupModel ( )
2023-05-21 21:56:37 +02:00
{
Name = reader [ "name" ] . ToString ( ) ,
2023-05-23 15:04:02 +02:00
FeedCount = count ,
2023-05-21 21:56:37 +02:00
HexColor = reader [ "hex_color" ] . ToString ( ) ,
Icon = reader [ "icon" ] . ToString ( ) ,
Id = reader [ "id" ] . ToString ( )
} ) ;
}
_sqlConn . Close ( ) ;
return groups ;
2023-05-20 00:04:45 +02:00
}
2023-05-21 21:56:37 +02:00
/// <summary>
2023-05-22 15:55:21 +02:00
/// Creates a group if not exists then update the group.
2023-05-21 21:56:37 +02:00
/// </summary>
/// <param name="groupModel"></param>
/// <returns></returns>
public async Task < bool > SetGroupAsync ( GroupModel groupModel )
2023-05-18 01:27:11 +02:00
{
2023-05-21 21:56:37 +02:00
bool result = false ;
2023-05-18 01:27:11 +02:00
_sqlConn . Open ( ) ;
2023-05-22 13:26:27 +02:00
using SqliteCommand cmd = new SqliteCommand ( $"INSERT OR REPLACE INTO {_groupTable} (id, hex_color, icon, name) VALUES (IFNULL((SELECT id FROM {_groupTable} WHERE name=@name), @id), @hexColor, @icon, @name)" , _sqlConn )
2023-05-18 01:27:11 +02:00
{
2023-05-21 21:56:37 +02:00
Parameters =
{
new SqliteParameter ( "id" , groupModel . Id ) ,
new SqliteParameter ( "hexColor" , groupModel . HexColor ) ,
new SqliteParameter ( "icon" , groupModel . Icon ) ,
new SqliteParameter ( "name" , groupModel . Name )
}
} ;
int affected = await cmd . ExecuteNonQueryAsync ( ) ;
if ( affected ! = 0 )
result = true ;
2023-05-18 01:27:11 +02:00
_sqlConn . Close ( ) ;
return result ;
}
2023-05-21 21:56:37 +02:00
public async Task < bool > RemoveGroupAsync ( GroupModel groupModel )
2023-05-18 01:27:11 +02:00
{
2023-05-21 21:56:37 +02:00
bool result = false ;
2023-05-18 01:27:11 +02:00
_sqlConn . Open ( ) ;
2023-05-22 13:26:27 +02:00
// Remove the group and remove the feeds that were part of the group.
using SqliteCommand cmd = new SqliteCommand ( $"DELETE FROM {_groupTable} WHERE id=@id; UPDATE {_feedTable} SET group_id=NULL WHERE group_id=@id" , _sqlConn )
2023-05-18 01:27:11 +02:00
{
2023-05-21 21:56:37 +02:00
Parameters =
{
new SqliteParameter ( "id" , groupModel . Id )
}
} ;
int affected = await cmd . ExecuteNonQueryAsync ( ) ;
if ( affected ! = 0 )
result = true ;
2023-05-18 01:27:11 +02:00
_sqlConn . Close ( ) ;
return result ;
}
2023-05-22 15:55:21 +02:00
// Feeds
2023-05-23 15:04:02 +02:00
public async Task < HashSet < FeedModel > > GetFeedsAsync ( string? groupId = null )
2023-05-18 01:27:11 +02:00
{
2023-05-21 21:56:37 +02:00
HashSet < FeedModel > feeds = new HashSet < FeedModel > ( ) ;
2023-05-18 01:27:11 +02:00
_sqlConn . Open ( ) ;
2023-05-23 15:04:02 +02:00
using SqliteCommand cmd = new SqliteCommand ( groupId ! = null ? $"SELECT * FROM {_feedTable} WHERE group_id=@groupId" : $"SELECT * FROM {_feedTable}" , _sqlConn )
2023-05-20 00:04:45 +02:00
{
2023-05-21 21:56:37 +02:00
Parameters =
{
2023-05-23 15:04:02 +02:00
new SqliteParameter ( "groupId" , groupId = = null ? string . Empty : groupId )
2023-05-21 21:56:37 +02:00
}
} ;
2023-05-22 15:55:21 +02:00
await using SqliteDataReader reader = await cmd . ExecuteReaderAsync ( ) ;
while ( reader . Read ( ) )
{
2023-05-22 19:09:01 +02:00
feeds . Add ( new FeedModel ( reader [ "url" ] . ToString ( ) )
2023-05-22 15:55:21 +02:00
{
Id = reader [ "id" ] . ToString ( ) ,
2023-05-22 19:09:01 +02:00
Title = reader [ "group_id" ] . ToString ( ) ,
2023-05-22 15:55:21 +02:00
GroupId = reader [ "group_id" ] . ToString ( ) ,
FeedType = reader [ "feed_type" ] . ToString ( ) ,
Description = reader [ "description" ] . ToString ( ) ,
Language = reader [ "language" ] . ToString ( ) ,
Copyright = reader [ "copyright" ] . ToString ( ) ,
DateAdded = DateTimeOffset . FromUnixTimeMilliseconds ( long . TryParse ( reader [ "date_added" ] . ToString ( ) , out long parsedVal ) ? parsedVal : 0 ) ,
LastUpdated = DateTimeOffset . FromUnixTimeMilliseconds ( long . TryParse ( reader [ "last_updated" ] . ToString ( ) , out long lastUpdated ) ? lastUpdated : 0 ) ,
2023-05-22 19:09:01 +02:00
ImageUrl = reader [ "image_url" ] . ToString ( ) ,
OriginalDocument = reader [ "original_document" ] . ToString ( )
2023-05-22 15:55:21 +02:00
} ) ;
}
2023-05-18 01:27:11 +02:00
_sqlConn . Close ( ) ;
2023-05-21 21:56:37 +02:00
return feeds ;
2023-05-18 01:27:11 +02:00
}
2023-05-22 13:26:27 +02:00
public async Task < bool > SetFeedAsync ( FeedModel feedModel )
2023-05-18 01:27:11 +02:00
{
2023-05-21 21:56:37 +02:00
bool result = false ;
2023-05-18 01:27:11 +02:00
_sqlConn . Open ( ) ;
2023-05-22 19:09:01 +02:00
using SqliteCommand cmd = new SqliteCommand ( $"INSERT OR REPLACE INTO {_feedTable} (id, url, title, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url, original_document) VALUES (IFNULL((SELECT id FROM {_feedTable} WHERE url=@url), @id), @url, @title, @groupId, @feedType, @description, @language, @copyright, @dateAdded, @lastUpdated, @imageUrl, @originalDoc)" , _sqlConn )
2023-05-20 00:04:45 +02:00
{
2023-05-21 21:56:37 +02:00
Parameters =
{
2023-05-22 19:09:01 +02:00
new SqliteParameter ( "id" , feedModel . Id ? ? string . Empty ) ,
new SqliteParameter ( "url" , feedModel . Url ? ? string . Empty ) ,
new SqliteParameter ( "title" , feedModel . Title ? ? string . Empty ) ,
new SqliteParameter ( "groupId" , feedModel . GroupId ? ? string . Empty ) ,
new SqliteParameter ( "feedType" , feedModel . FeedType ? ? string . Empty ) ,
new SqliteParameter ( "description" , feedModel . Description ? ? string . Empty ) ,
new SqliteParameter ( "language" , feedModel . Language ? ? string . Empty ) ,
new SqliteParameter ( "copyright" , feedModel . Copyright ? ? string . Empty ) ,
2023-05-21 21:56:37 +02:00
new SqliteParameter ( "dateAdded" , feedModel . DateAdded . ToUnixTimeMilliseconds ( ) ) ,
new SqliteParameter ( "lastUpdated" , feedModel . LastUpdated . ToUnixTimeMilliseconds ( ) ) ,
2023-05-22 19:09:01 +02:00
new SqliteParameter ( "imageUrl" , feedModel . ImageUrl ? ? string . Empty ) ,
new SqliteParameter ( "originalDoc" , feedModel . OriginalDocument ? ? string . Empty )
2023-05-21 21:56:37 +02:00
}
} ;
int affected = await cmd . ExecuteNonQueryAsync ( ) ;
if ( affected ! = 0 )
result = true ;
2023-05-18 01:27:11 +02:00
_sqlConn . Close ( ) ;
2023-05-21 21:56:37 +02:00
return result ;
2023-05-18 01:27:11 +02:00
}
2023-05-22 13:26:27 +02:00
public async Task < bool > RemoveFeedAsync ( FeedModel feedModel )
{
bool result = false ;
_sqlConn . Open ( ) ; // After removing the feed unset the feed id from the feed items
using SqliteCommand cmd = new SqliteCommand ( $"DELETE FROM {_feedTable} WHERE id=@id; UPDATE {_feedItemTable} SET feed_id=NULL WHERE feed_id=@id" , _sqlConn )
{
Parameters =
{
new SqliteParameter ( "id" , feedModel . Id )
}
} ;
int affected = await cmd . ExecuteNonQueryAsync ( ) ;
if ( affected ! = 0 )
result = true ;
_sqlConn . Close ( ) ;
return result ;
}
2023-05-22 15:55:21 +02:00
// Feed items
2023-05-22 13:26:27 +02:00
public async Task < HashSet < FeedItemModel > > GetFeedItemsAsync ( )
{
HashSet < FeedItemModel > feeditems = new HashSet < FeedItemModel > ( ) ;
_sqlConn . Open ( ) ;
using SqliteCommand cmd = new SqliteCommand ( $"SELECT * FROM {_feedItemTable}" , _sqlConn ) ;
await using SqliteDataReader reader = await cmd . ExecuteReaderAsync ( ) ;
while ( reader . Read ( ) )
{
feeditems . Add ( new FeedItemModel ( )
{
Id = reader [ "id" ] . ToString ( ) ,
FeedId = reader [ "feed_id" ] . ToString ( ) ,
Read = int . TryParse ( reader [ "read" ] . ToString ( ) , out int parsedValue ) ? parsedValue = = 0 ? false : true : false ,
Type = reader [ "type" ] . ToString ( ) ,
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 ( ) ) ) ,
Author = reader [ "author" ] . ToString ( ) ,
Categories = reader [ "categories" ] . ToString ( ) . Split ( ',' ) ,
Content = reader [ "content" ] . ToString ( )
} ) ;
}
_sqlConn . Close ( ) ;
return feeditems ;
}
public async Task < int > SetFeedItemsAsync ( HashSet < FeedItemModel > items )
{
int result = 0 ;
_sqlConn . Open ( ) ;
2023-05-22 19:09:01 +02:00
using SqliteCommand cmd = new SqliteCommand ( $"INSERT OR REPLACE INTO {_feedItemTable} (id, feed_id, read, title, description, link, last_updated, publishing_date, author, categories, content)" +
$"VALUES (IFNULL((SELECT id FROM {_feedItemTable} WHERE link=@link), @id), @feedId, @read, @title, @description, @link, @lastUpdated, @publishingDate, @author, @categories, @content)" , _sqlConn ) ;
2023-05-22 13:26:27 +02:00
foreach ( FeedItemModel item in items )
{
cmd . Parameters . Clear ( ) ;
2023-05-22 19:09:01 +02:00
cmd . Parameters . Add ( new SqliteParameter ( "id" , item . Id ? ? string . Empty ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "feedId" , item . FeedId ? ? string . Empty ) ) ;
2023-05-22 13:26:27 +02:00
cmd . Parameters . Add ( new SqliteParameter ( "read" , item . Read ? 1 : 0 ) ) ;
2023-05-22 19:09:01 +02:00
cmd . Parameters . Add ( new SqliteParameter ( "type" , item . Type ? ? string . Empty ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "title" , item . Title ? ? string . Empty ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "description" , item . Description ? ? string . Empty ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "link" , item . Link ? ? string . Empty ) ) ;
2023-05-22 13:26:27 +02:00
cmd . Parameters . Add ( new SqliteParameter ( "lastUpdated" , item . LastUpdated . ToUnixTimeMilliseconds ( ) ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "publishingDate" , item . PublishingDate ? . ToUnixTimeMilliseconds ( ) ? ? 0 ) ) ;
2023-05-22 19:09:01 +02:00
cmd . Parameters . Add ( new SqliteParameter ( "author" , item . Author ? ? string . Empty ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "categories" , item . Categories ! = null ? string . Join ( ',' , item . Categories ) : string . Empty ) ) ;
cmd . Parameters . Add ( new SqliteParameter ( "content" , item . Content ? ? string . Empty ) ) ;
2023-05-22 13:26:27 +02:00
int affected = await cmd . ExecuteNonQueryAsync ( ) ;
if ( affected = = 0 )
2023-05-22 19:09:01 +02:00
Log . Verbose ( "Could not set feed item: {FeedLink}" , item . Link ) ;
2023-05-22 13:26:27 +02:00
else
result + = affected ;
}
_sqlConn . Close ( ) ;
return result ; // Return the amount affected rows.
}
public async Task < bool > RemoveFeedItemAsync ( FeedItemModel itemModel )
{
bool result = false ;
_sqlConn . Open ( ) ;
using SqliteCommand cmd = new SqliteCommand ( $"DELETE FROM {_feedItemTable} WHERE id=@id" , _sqlConn )
{
Parameters =
{
new SqliteParameter ( "id" , itemModel . Id )
}
} ;
int affected = await cmd . ExecuteNonQueryAsync ( ) ;
if ( affected ! = 0 )
result = true ;
_sqlConn . Close ( ) ;
return result ;
}
2023-05-18 01:27:11 +02:00
private async void InitializeDb ( )
{
Log . Verbose ( "Checking database..." ) ;
2023-05-19 14:40:57 +02:00
HashSet < string > failed = new HashSet < string > ( ) ;
2023-05-18 01:27:11 +02:00
_sqlConn . Open ( ) ;
2023-05-20 00:04:45 +02:00
Log . Verbose ( "Checking table: {Table}" , _groupTable ) ;
2023-05-21 21:56:37 +02:00
var queryResponse = await _sqlConn . QueryAsync ( $"CREATE TABLE IF NOT EXISTS {_groupTable} (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY)" ) ;
2023-05-19 14:40:57 +02:00
if ( queryResponse . Any ( ) ) failed . Add ( "category_data" ) ;
2023-05-20 00:04:45 +02:00
Log . Verbose ( "Checking table: {Table}" , _feedTable ) ;
2023-05-22 19:09:01 +02:00
queryResponse = await _sqlConn . QueryAsync ( $"CREATE TABLE IF NOT EXISTS {_feedTable} (id STRING PRIMARY KEY, url STRING NOT NULL, title STRING, group_id STRING, feed_type STRING, description STRING, language STRING, copyright STRING, date_added INT, last_updated INT, image_url STRING, original_document STRING)" ) ;
2023-05-19 14:40:57 +02:00
if ( queryResponse . Any ( ) ) failed . Add ( "feed_data" ) ;
2023-05-20 00:04:45 +02:00
Log . Verbose ( "Checking table: {Table}" , _feedItemTable ) ;
2023-05-22 19:09:01 +02:00
queryResponse = await _sqlConn . QueryAsync ( $"CREATE TABLE IF NOT EXISTS {_feedItemTable} (id STRING PRIMARY KEY, feed_id STRING, read INT, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, author STRING, categories STRING, content STRING)" ) ;
2023-05-20 00:04:45 +02:00
if ( queryResponse . Any ( ) ) failed . Add ( "feed_item_data" ) ;
2023-05-19 14:40:57 +02:00
_sqlConn . Close ( ) ;
if ( failed . Any ( ) )
2023-05-18 01:27:11 +02:00
{
2023-05-19 14:40:57 +02:00
var joined = string . Join ( ',' , failed ) ;
Log . Error ( "Failed to initialize table(s): {TableNames}" , joined ) ;
2023-05-18 01:27:11 +02:00
}
2023-05-19 14:40:57 +02:00
else
Log . Verbose ( "Checking database done!" ) ;
2023-05-18 01:27:11 +02:00
}
public void Dispose ( )
{
_sqlConn . Dispose ( ) ;
}
}
}