SharpRSS/SharpRss/DbAccess.cs
2023-06-09 15:13:53 +02:00

260 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Security.Policy;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Data.Sqlite;
using Serilog;
using SharpRss.Models;
namespace SharpRss
{
internal static class DbAccess
{
static DbAccess()
{
Initialize();
}
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)
{
if (synContainer.Category != null)
await SetCategoryAsync(synContainer.Category);
if (synContainer.FeedModel != null)
await SetFeedAsync(synContainer.FeedModel);
if (synContainer.FeedItems != null && synContainer.FeedItems.Any())
await SetFeedItemsAsync(synContainer.FeedItems);
}
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(),
FeedCount = 0, // Not implemented.
Icon = reader["icon"].ToString()
};
categories.Add(categoryModel);
}
return categories;
}
public static async Task<bool> SetCategoryAsync(CategoryModel category)
{
if (category == null) return false;
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 });
return affected > 0;
}
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();
await using SqliteCommand cmd = new SqliteCommand(@"INSERT OR REPLACE INTO feed
(url,
title,
category_id,
feed_type,
feed_version,
description,
language,
copyright,
publication_date,
last_updated,
categories,
image_url)
VALUES (
@Url,
@Title,
@CategoryId,
@FeedType,
@FeedVersion,
@Description,
@Language,
@Copyright,
@PublicationDate,
@LastUpdated,
@Categories,
@ImageUrl)", dbc)
{
Parameters =
{
new SqliteParameter("Url", feed.OriginalUrl ?? string.Empty),
new SqliteParameter("Title", feed.Title ?? string.Empty),
new SqliteParameter("CategoryId", feed.CategoryId ?? string.Empty),
new SqliteParameter("FeedType", feed.FeedType ?? string.Empty),
new SqliteParameter("FeedVersion", feed.FeedVersion ?? string.Empty),
new SqliteParameter("Description", feed.Description ?? string.Empty),
new SqliteParameter("Language", feed.Language ?? string.Empty),
new SqliteParameter("Copyright", feed.Copyright ?? string.Empty),
new SqliteParameter("PublicationDate", feed.PublicationDate?.ToUnixTimeMilliseconds() ?? 0),
new SqliteParameter("LastUpdated", feed.LastUpdated?.ToUnixTimeMilliseconds() ?? 0),
new SqliteParameter("Categories", feed.Categories != null && feed.Categories.Any() ? string.Join(',', feed.Categories) : string.Empty),
new SqliteParameter("ImageUrl", feed.ImageUrl ?? string.Empty)
}
};
int exec = await cmd.ExecuteNonQueryAsync();
/*int affected = await dbc.ExecuteAsync("INSERT OR REPLACE INTO feed (url, title, category_id, feed_type, feed_version, description, language, copyright, publication_date, last_updated, categories, image_url) VALUES (@Url, @Title, @CategoryId, @FeedType, @FeedVersion, @Description, @language, @Copyright, @PublicationDate, @LastUpdated, @Categories, @ImageUrl)",
new
{
Url = feed.OriginalUrl ?? string.Empty,
Title = feed.Title ?? string.Empty,
CategoryId = feed.CategoryId ?? string.Empty,
FeedType = feed.FeedType ?? string.Empty,
FeedVersion = feed.FeedVersion ?? string.Empty,
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,
Categories = feed.Categories.Any() ? string.Join(',', feed.Categories) : string.Empty,
ImageUrl = feed.ImageUrl ?? string.Empty
});
if (affected == 0)
Log.Warning("Failed to add feed: {FeedUrl}", feed.OriginalUrl);*/
}
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>();
await using DbDataReader reader = await dbc.ExecuteReaderAsync(categoryIds == null ? "SELECT * FROM feed" : "SELECT * FROM feed WHERE category_id IN(@CatIds)", new { CatIds = categoryIds });
while (await reader.ReadAsync())
{
FeedModel feedModel = new FeedModel()
{
OriginalUrl = reader["url"].ToString(),
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()
};
feeds.Add(feedModel);
}
return feeds;
}
public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
int totalAffected = 0;
await using SqliteTransaction dbTransaction = dbc.BeginTransaction();
foreach (FeedItemModel item in items)
{
await using SqliteCommand cmd = new SqliteCommand(@"INSERT OR REPLACE INTO feed_item (id, feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content)
VALUES (@Id, @FeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)", dbc, dbTransaction)
{
Parameters =
{
new SqliteParameter("Id", item.Id ?? string.Empty),
new SqliteParameter("FeedUrl", item.FeedUrl ?? string.Empty),
new SqliteParameter("Read", item.Read.ToString()),
new SqliteParameter("Title", item.Title),
new SqliteParameter("Description", item.Description ?? string.Empty),
new SqliteParameter("Link", item.Link ?? string.Empty),
new SqliteParameter("LastUpdated", item.LastUpdated?.ToUnixTimeMilliseconds() ?? 0),
new SqliteParameter("PublishingDate", item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0),
new SqliteParameter("Authors", item.Authors.Any() ? string.Join(',', item.Authors) : string.Empty),
new SqliteParameter("Categories", item.Categories.Any() ? string.Join(',', item.Categories) : string.Empty),
new SqliteParameter("Content", item.Content ?? string.Empty)
}
};
int exec = await cmd.ExecuteNonQueryAsync();
/*int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed_item (id, feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content)
VALUES (@Id, @FeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)",
transaction: dbTransaction,
param: new
{
Id = item.Id ?? string.Empty,
FeedUrl = item.FeedUrl ?? string.Empty,
Read = item.Read.ToString(),
Title = item.Title ?? string.Empty,
Description = item.Description ?? string.Empty,
Link = item.Link ?? string.Empty,
LastUpdated = item.LastUpdated?.ToUnixTimeMilliseconds() ?? 0,
PublishingDate = item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0,
Authors = item.Authors.Any() ? string.Join(',', item.Authors) : string.Empty,
Categories = item.Categories.Any() ? string.Join(',', item.Categories) : string.Empty,
Content = item.Content ?? string.Empty
});
totalAffected += affected;*/
}
dbTransaction.Commit();
}
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? feedUrls)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open();
HashSet<FeedItemModel> items = new HashSet<FeedItemModel>();
await using DbDataReader reader = await dbc.ExecuteReaderAsync(feedUrls == null ? "SELECT * FROM feed_item" : "SELECT * FROM feed_item WHERE feed_item.feed_url IN(@Urls)", new { Urls = feedUrls });
while (await reader.ReadAsync())
{
FeedItemModel feedItemModel = new FeedItemModel()
{
Id = reader["id"].ToString(),
FeedUrl = reader["feed_url"].ToString(),
Read = bool.Parse(reader["read"].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())),
Authors = reader["authors"].ToString().ToString().Split(','),
Categories = reader["categories"].ToString().Split(','),
Content = reader["content"].ToString()
};
items.Add(feedItemModel);
}
return items;
}
private static async void Initialize()
{
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");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (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)");
Log.Verbose("Checking table: {Table}", "feed_item");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, feed_url STRING, read STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)");
Log.Verbose("Checking database done!");
_isInitialized = true;
}
}
}