SharpRSS/SharpRss/Services/DatabaseService.cs
2023-05-22 13:26:27 +02:00

267 lines
12 KiB
C#

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
{
internal class DatabaseService : IDisposable
{
internal DatabaseService()
{
_sqlConn = new SqliteConnection(_connectionString);
InitializeDb();
}
private readonly SqliteConnection _sqlConn;
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";
// Group
public async Task<HashSet<GroupModel?>> GetGroupsAsync(string? groupName = null)
{
_sqlConn.Open();
using SqliteCommand cmd = new SqliteCommand(groupName != null ? $"SELECT * FROM {_groupTable} WHERE name=@name;" : $"SELECT * FROM {_groupTable}", _sqlConn)
{
Parameters =
{
new SqliteParameter("name", groupName)
}
};
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
HashSet<GroupModel?> groups = new HashSet<GroupModel?>();
while (reader.Read())
{
groups.Add(new GroupModel()
{
Name = reader["name"].ToString(),
HexColor = reader["hex_color"].ToString(),
Icon = reader["icon"].ToString(),
Id = reader["id"].ToString()
});
}
_sqlConn.Close();
return groups;
}
/// <summary>
/// Creates a group if not exists else will update the group.
/// </summary>
/// <param name="groupModel"></param>
/// <returns></returns>
public async Task<bool> SetGroupAsync(GroupModel groupModel)
{
bool result = false;
_sqlConn.Open();
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)
{
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;
_sqlConn.Close();
return result;
}
public async Task<bool> RemoveGroupAsync(GroupModel groupModel)
{
bool result = false;
_sqlConn.Open();
// 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)
{
Parameters =
{
new SqliteParameter("id", groupModel.Id)
}
};
int affected = await cmd.ExecuteNonQueryAsync();
if (affected != 0)
result = true;
_sqlConn.Close();
return result;
}
// Feed
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? feedName = null)
{
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
_sqlConn.Open();
using SqliteCommand cmd = new SqliteCommand(feedName != null ? $"SELECT * FROM {_feedTable} WHERE name=@name" : $"SELECT * FROM {_feedTable}", _sqlConn)
{
Parameters =
{
new SqliteParameter("name", feedName)
}
};
int affected = await cmd.ExecuteNonQueryAsync();
Log.Verbose("{FeedAmount} feeds found!", affected);
_sqlConn.Close();
return feeds;
}
public async Task<bool> SetFeedAsync(FeedModel feedModel)
{
bool result = false;
_sqlConn.Open();
using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_feedTable} (id, url, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url)" +
$"VALUES (IFNULL((SELECT id FROM {_feedTable} WHERE url=@url), @id), @url, @groupId, @feedType, @description, @language, @copyright, @dateAdded, @lastUpdated, @imageUrl)", _sqlConn)
{
Parameters =
{
new SqliteParameter("id", feedModel.Id),
new SqliteParameter("url", feedModel.Url),
new SqliteParameter("groupId", feedModel.GroupId),
new SqliteParameter("feedType", feedModel.FeedType),
new SqliteParameter("description", feedModel.Description),
new SqliteParameter("language", feedModel.Language),
new SqliteParameter("copyright", feedModel.Copyright),
new SqliteParameter("dateAdded", feedModel.DateAdded.ToUnixTimeMilliseconds()),
new SqliteParameter("lastUpdated", feedModel.LastUpdated.ToUnixTimeMilliseconds()),
new SqliteParameter("imageUrl", feedModel.ImageUrl)
}
};
int affected = await cmd.ExecuteNonQueryAsync();
if (affected != 0)
result = true;
_sqlConn.Close();
return result;
}
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;
}
// Feed item
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();
using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {_feedItemTable} (id, feed_id, read, type, title, description, link, last_updated, publishing_date, author, categories, content)" +
$"VALUES (IFNULL((SELECT id FROM {_feedItemTable} WHERE url=@url), @id), @feedId, @read, @type, @title, @description, @link, @lastUpdated, @publishingDate, @author, @categories, @content)", _sqlConn);
foreach (FeedItemModel item in items)
{
cmd.Parameters.Clear();
cmd.Parameters.Add(new SqliteParameter("id", item.Id));
cmd.Parameters.Add(new SqliteParameter("feedid", item.FeedId));
cmd.Parameters.Add(new SqliteParameter("read", item.Read ? 1 : 0));
cmd.Parameters.Add(new SqliteParameter("type", item.Type));
cmd.Parameters.Add(new SqliteParameter("title", item.Title));
cmd.Parameters.Add(new SqliteParameter("description", item.Description));
cmd.Parameters.Add(new SqliteParameter("link", item.Link));
cmd.Parameters.Add(new SqliteParameter("lastUpdated", item.LastUpdated.ToUnixTimeMilliseconds()));
cmd.Parameters.Add(new SqliteParameter("publishingDate", item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0));
cmd.Parameters.Add(new SqliteParameter("author", item.Author));
cmd.Parameters.Add(new SqliteParameter("categories", string.Join(',', item.Categories)));
cmd.Parameters.Add(new SqliteParameter("content", item.Content));
int affected = await cmd.ExecuteNonQueryAsync();
if (affected == 0)
Log.Verbose($"Could not set feed item: {item.Link}");
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;
}
private async void InitializeDb()
{
Log.Verbose("Checking database...");
HashSet<string> failed = new HashSet<string>();
_sqlConn.Open();
Log.Verbose("Checking table: {Table}", _groupTable);
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)");
if (queryResponse.Any()) failed.Add("category_data");
Log.Verbose("Checking table: {Table}", _feedTable);
queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedTable} (id STRING PRIMARY KEY, url STRING NOT NULL, group_id STRING DEFAULT NULL, feed_type STRING, description STRING, language STRING, copyright STRING, date_added INT, last_updated INT, image_url STRING)");
if (queryResponse.Any()) failed.Add("feed_data");
Log.Verbose("Checking table: {Table}", _feedItemTable);
queryResponse = await _sqlConn.QueryAsync($"CREATE TABLE IF NOT EXISTS {_feedItemTable} (id STRING PRIMARY KEY, feed_id STRING DEFAULT NULL, read INT, type STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, author STRING, categories STRING, content STRING)");
if (queryResponse.Any()) failed.Add("feed_item_data");
_sqlConn.Close();
if (failed.Any())
{
var joined = string.Join(',', failed);
Log.Error("Failed to initialize table(s): {TableNames}", joined);
}
else
Log.Verbose("Checking database done!");
}
public void Dispose()
{
_sqlConn.Dispose();
}
}
}