mirror of
https://github.com/hmaxnl/SharpRSS.git
synced 2025-01-18 21:04:21 +01:00
Reworking backend...
This commit is contained in:
parent
722f6f132a
commit
ea91abe629
|
@ -12,15 +12,15 @@ namespace SharpRss.Core
|
|||
FetchFeeds();
|
||||
}
|
||||
private readonly SyndicationService _syndicationService = new SyndicationService();
|
||||
private Dictionary<string, FeedModel> _cachedFeeds = new Dictionary<string, FeedModel>();
|
||||
private Dictionary<string, SyndicationModel> _cachedFeeds = new Dictionary<string, SyndicationModel>();
|
||||
|
||||
private async void FetchFeeds()
|
||||
{
|
||||
HashSet<FeedModel> fetchedFeeds = await _syndicationService.GetFeedsAsync();
|
||||
HashSet<SyndicationModel> fetchedFeeds = await _syndicationService.GetFeedsAsync();
|
||||
_cachedFeeds = fetchedFeeds.ToDictionary(x => x.EncodedUrl);
|
||||
}
|
||||
|
||||
public FeedModel? CacheFeed(string encodedUrl) => _cachedFeeds.TryGetValue(encodedUrl, out FeedModel model) ? model : null;
|
||||
public SyndicationModel? CacheFeed(string encodedUrl) => _cachedFeeds.TryGetValue(encodedUrl, out SyndicationModel model) ? model : null;
|
||||
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@ using Microsoft.Data.Sqlite;
|
|||
using Serilog;
|
||||
using SharpRss.Core;
|
||||
using SharpRss.Models;
|
||||
using ToolQit.Containers;
|
||||
|
||||
namespace SharpRss
|
||||
{
|
||||
|
@ -24,12 +23,12 @@ namespace SharpRss
|
|||
{
|
||||
CategoryModel? catModel = await SetCategoryAsync(synContainer.Category);
|
||||
if (catModel != null)
|
||||
synContainer.FeedModel.CategoryId = catModel.Id;
|
||||
synContainer.SyndicationModel.CategoryId = catModel.Id;
|
||||
}
|
||||
if (synContainer.FeedModel != null)
|
||||
await SetFeedAsync(synContainer.FeedModel);
|
||||
if (synContainer.FeedItems != null && synContainer.FeedItems.Any())
|
||||
await SetFeedItemsAsync(synContainer.FeedItems);
|
||||
if (synContainer.SyndicationModel != null)
|
||||
await SetSyndicationAsync(synContainer.SyndicationModel);
|
||||
if (synContainer.SyndicationItems != null && synContainer.SyndicationItems.Any())
|
||||
await SetSyndicationItemsAsync(synContainer.SyndicationItems);
|
||||
}
|
||||
|
||||
public static async Task<HashSet<CategoryModel>> GetCategoriesAsync()
|
||||
|
@ -47,7 +46,7 @@ namespace SharpRss
|
|||
HexColor = reader["hex_color"].ToString(),
|
||||
Icon = reader["icon"].ToString()
|
||||
};
|
||||
categoryModel.FeedCount = await dbc.ExecuteScalarAsync<int>($"SELECT COUNT(*) FROM feed WHERE category_id=@CatId", new { CatId = categoryModel.Id });
|
||||
categoryModel.SyndicationCount = await dbc.ExecuteScalarAsync<int>($"SELECT COUNT(*) FROM feed WHERE category_id=@CatId", new { CatId = categoryModel.Id });
|
||||
categories.Add(categoryModel);
|
||||
}
|
||||
return categories;
|
||||
|
@ -74,7 +73,7 @@ namespace SharpRss
|
|||
return affected > 0;
|
||||
}
|
||||
|
||||
public static async Task SetFeedAsync(FeedModel feed)
|
||||
public static async Task SetSyndicationAsync(SyndicationModel syndication)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
|
@ -106,67 +105,67 @@ namespace SharpRss
|
|||
@ImageUrl)",
|
||||
new
|
||||
{
|
||||
EncodedUrl = feed.EncodedUrl,
|
||||
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 != null && feed.Categories.Any() ? string.Join(',', feed.Categories) : string.Empty,
|
||||
ImageUrl = feed.ImageUrl ?? string.Empty
|
||||
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
|
||||
});
|
||||
if (affected == 0)
|
||||
Log.Warning("Failed to add feed: {FeedUrl}", feed.EncodedUrl);
|
||||
Log.Warning("Failed to add feed: {FeedUrl}", syndication.EncodedUrl);
|
||||
}
|
||||
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null)
|
||||
public static async Task<HashSet<SyndicationModel>> GetSyndicationsAsync(string[]? categoryIds = null)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
|
||||
HashSet<SyndicationModel> feeds = new HashSet<SyndicationModel>();
|
||||
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()
|
||||
SyndicationModel syndicationModel = new SyndicationModel()
|
||||
{
|
||||
EncodedUrl = reader["encoded_url"].ToString(),
|
||||
Title = reader["title"].ToString(),
|
||||
CategoryId = reader["category_id"].ToString(),
|
||||
FeedType = reader["feed_type"].ToString(),
|
||||
FeedVersion = reader["feed_version"].ToString(),
|
||||
SyndicationType = reader["feed_type"].ToString(),
|
||||
SyndicationVersion = 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(','),
|
||||
SynUpdatedDate = DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(reader["last_updated"].ToString())),
|
||||
Categories = SyndicationManager.StringToStringArray(reader["categories"].ToString()),
|
||||
ImageUrl = reader["image_url"].ToString()
|
||||
};
|
||||
feedModel.ItemCount = await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = feedModel.EncodedUrl });
|
||||
feeds.Add(feedModel);
|
||||
syndicationModel.ItemCount = await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = syndicationModel.EncodedUrl });
|
||||
feeds.Add(syndicationModel);
|
||||
}
|
||||
return feeds;
|
||||
}
|
||||
public static async Task<bool> DeleteFeedAsync(FeedModel feed, bool deleteItems = false)
|
||||
public static async Task<bool> DeleteSyndicationAsync(SyndicationModel syndication, bool deleteItems = false)
|
||||
{
|
||||
if (feed == null) return false;
|
||||
if (syndication == null) return false;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
int affected = await dbc.ExecuteAsync("DELETE FROM feed WHERE encoded_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl });
|
||||
int affected = await dbc.ExecuteAsync("DELETE FROM feed WHERE encoded_url=@EncodedUrl", new { EncodedUrl = syndication.EncodedUrl });
|
||||
if (affected > 0 && deleteItems)
|
||||
await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl });
|
||||
await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = syndication.EncodedUrl });
|
||||
return affected > 0;
|
||||
}
|
||||
|
||||
public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items)
|
||||
public static async Task SetSyndicationItemsAsync(HashSet<SyndicationItemModel> items)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
int totalAffected = 0;
|
||||
await using SqliteTransaction dbTransaction = dbc.BeginTransaction();
|
||||
foreach (FeedItemModel item in items)
|
||||
foreach (SyndicationItemModel item in items)
|
||||
{
|
||||
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)",
|
||||
|
@ -174,15 +173,15 @@ namespace SharpRss
|
|||
param: new
|
||||
{
|
||||
Id = item.Id ?? string.Empty,
|
||||
EncodedFeedUrl = item.EncodedFeedUrl ?? string.Empty,
|
||||
EncodedFeedUrl = item.EncodedSyndicationUrl ?? 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,
|
||||
LastUpdated = item.ItemUpdatedDate?.ToUnixTimeMilliseconds() ?? 0,
|
||||
PublishingDate = item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0,
|
||||
Authors = item.Authors != null && item.Authors.Any() ? string.Join(',', item.Authors) : string.Empty,
|
||||
Categories = item.Categories != null && item.Categories.Any() ? string.Join(',', item.Categories) : string.Empty,
|
||||
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,
|
||||
Content = item.Content ?? string.Empty
|
||||
});
|
||||
totalAffected += affected;
|
||||
|
@ -190,33 +189,32 @@ namespace SharpRss
|
|||
dbTransaction.Commit();
|
||||
}
|
||||
|
||||
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? encodedFeedUrls)
|
||||
public static async Task<HashSet<SyndicationItemModel>> GetSyndicationItemsAsync(string[]? encodedFeedUrls)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
HashSet<FeedItemModel> items = new HashSet<FeedItemModel>();
|
||||
HashSet<SyndicationItemModel> items = new HashSet<SyndicationItemModel>();
|
||||
FeedCache fCache = new FeedCache();
|
||||
await using DbDataReader reader = await dbc.ExecuteReaderAsync(encodedFeedUrls == null ? "SELECT * FROM feed_item" : $"SELECT * FROM feed_item WHERE encoded_feed_url IN({FormatParametersFromArray(encodedFeedUrls)})");
|
||||
//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.
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
FeedItemModel feedItemModel = new FeedItemModel()
|
||||
SyndicationItemModel syndicationItemModel = new SyndicationItemModel()
|
||||
{
|
||||
Id = reader["id"].ToString(),
|
||||
EncodedFeedUrl = reader["encoded_feed_url"].ToString(),
|
||||
EncodedSyndicationUrl = reader["encoded_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())),
|
||||
ItemUpdatedDate = 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(','),
|
||||
Authors = SyndicationManager.StringToStringArray(reader["authors"].ToString()),
|
||||
Categories = SyndicationManager.StringToStringArray(reader["categories"].ToString()),
|
||||
Content = reader["content"].ToString()
|
||||
};
|
||||
feedItemModel.ParentFeed = fCache.CacheFeed(feedItemModel.EncodedFeedUrl);
|
||||
// Get feed
|
||||
items.Add(feedItemModel);
|
||||
syndicationItemModel.SyndicationParent = fCache.CacheFeed(syndicationItemModel.EncodedSyndicationUrl) ?? new SyndicationModel();
|
||||
items.Add(syndicationItemModel);
|
||||
}
|
||||
Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count);
|
||||
return items;
|
||||
|
@ -244,9 +242,10 @@ namespace SharpRss
|
|||
string[] formatted = dbParams.Select(s => $"'{s}'").ToArray();
|
||||
return string.Join(", ", formatted);
|
||||
}
|
||||
public static async Task<int> GetFeedCountAsync(string[] encodedFeedUrls)
|
||||
public static async Task<int> GetSyndicationCountAsync(string[] encodedSyndicationUrls)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
return await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,417 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Serilog;
|
||||
using SharpRss.Models;
|
||||
|
||||
namespace SharpRss
|
||||
{
|
||||
public static class DbAccess_Old
|
||||
{
|
||||
//TODO: Rename group => category.
|
||||
//TODO: Reworking feed => model/db implementation.
|
||||
private static bool _isInitialized;
|
||||
private static readonly string ConnectionString = $"Data Source={Path.Combine(Environment.CurrentDirectory, "sharp_rss.sqlite")};";
|
||||
private const string GroupTable = "group_data";
|
||||
private const string FeedTable = "feed_data";
|
||||
private const string FeedItemTable = "feed_item_data";
|
||||
|
||||
// Groups
|
||||
public static async Task<HashSet<CategoryModel>> GetCategoriesAsync(string? categoryId = null)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand(categoryId != null ? $"SELECT * FROM {GroupTable} WHERE id=@gId;" : $"SELECT * FROM {GroupTable}", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("gId", categoryId)
|
||||
}
|
||||
};
|
||||
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
HashSet<CategoryModel> categories = new HashSet<CategoryModel>();
|
||||
await using SqliteCommand cmdFeedCount = new SqliteCommand($"SELECT COUNT(*) FROM {FeedTable} WHERE group_id=@groupId", dbc);
|
||||
while (reader.Read())
|
||||
{
|
||||
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;
|
||||
|
||||
categories.Add(new CategoryModel()
|
||||
{
|
||||
Name = reader["name"].ToString(),
|
||||
FeedCount = count,
|
||||
HexColor = reader["hex_color"].ToString(),
|
||||
Icon = reader["icon"].ToString(),
|
||||
Id = reader["id"].ToString()
|
||||
});
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
public static async Task<bool> SetCategoryAsync(CategoryModel categoryModel)
|
||||
{
|
||||
bool result = false;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await 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)", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("id", categoryModel.Id),
|
||||
new SqliteParameter("hexColor", categoryModel.HexColor),
|
||||
new SqliteParameter("icon", categoryModel.Icon),
|
||||
new SqliteParameter("name", categoryModel.Name)
|
||||
}
|
||||
};
|
||||
int affected = await cmd.ExecuteNonQueryAsync();
|
||||
if (affected != 0)
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
public static async Task<bool> RemoveCategoryAsync(CategoryModel categoryModel)
|
||||
{
|
||||
bool result = false;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
// Remove the group and remove the feeds that were part of the group.
|
||||
await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {GroupTable} WHERE id=@id; UPDATE {FeedTable} SET group_id=NULL WHERE group_id=@id", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("id", categoryModel.Id)
|
||||
}
|
||||
};
|
||||
int affected = await cmd.ExecuteNonQueryAsync();
|
||||
if (affected != 0)
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
// Feeds
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="groupId">Empty = ungrouped feeds | null = all feeds | id = grouped feeds</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string? groupId = null)
|
||||
{
|
||||
HashSet<FeedModel> feeds = new HashSet<FeedModel>();
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand(groupId != null ? $"SELECT * FROM {FeedTable} WHERE group_id=@groupId" : $"SELECT * FROM {FeedTable}", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("groupId", groupId ?? string.Empty)
|
||||
}
|
||||
};
|
||||
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
while (!reader.IsClosed && reader.Read())
|
||||
feeds.Add(await ReaderToFeedModel(reader));
|
||||
return feeds;
|
||||
}
|
||||
public static async Task<FeedModel?> GetFeedAsync(string url)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
FeedModel? feed = null;
|
||||
//TODO: Use dapper to simplify this query.
|
||||
await using SqliteCommand cmd = new SqliteCommand($"SELECT * FROM {FeedTable} WHERE url=@Url", dbc)
|
||||
{
|
||||
Parameters = { new SqliteParameter("Url", url) }
|
||||
};
|
||||
try
|
||||
{
|
||||
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
if (reader.Read())
|
||||
feed = await ReaderToFeedModel(reader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while fetching feed from db.");
|
||||
}
|
||||
return feed;
|
||||
}
|
||||
public static async Task<FeedModel?> SetFeedAsync(FeedModel feedModel)
|
||||
{
|
||||
FeedModel? feed = null;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {FeedTable} (url, title, group_id, feed_type, description, language, copyright, date_added, last_updated, image_url, original_document) VALUES (@url, @title, @groupId, @feedType, @description, @language, @copyright, @dateAdded, @lastUpdated, @imageUrl, @originalDoc); SELECT * FROM {FeedTable} WHERE url=@url", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("url", feedModel.EncodedUrl ?? string.Empty),
|
||||
new SqliteParameter("title", feedModel.Title ?? string.Empty),
|
||||
new SqliteParameter("groupId", feedModel.CategoryId ?? 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),
|
||||
new SqliteParameter("dateAdded", feedModel.PublicationDate?.ToUnixTimeMilliseconds() ?? 0),
|
||||
new SqliteParameter("lastUpdated", feedModel.LastUpdated?.ToUnixTimeMilliseconds() ?? 0),
|
||||
new SqliteParameter("imageUrl", feedModel.ImageUrl ?? string.Empty)
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
feed = await ReaderToFeedModel(reader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Database error, adding feed model to database failed!");
|
||||
return feed;
|
||||
}
|
||||
return feed;
|
||||
/*await using SqliteTransaction transaction = dbc.BeginTransaction();
|
||||
try
|
||||
{
|
||||
foreach (var feedModel in feedModels)
|
||||
{
|
||||
//await 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, IFNULL((SELECT date_added FROM {_feedTable} WHERE id=@id), @dateAdded), @lastUpdated, @imageUrl, @originalDoc); SELECT * FROM {_feedTable} WHERE url=@url", dbc)
|
||||
await 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, IFNULL((SELECT date_added FROM {FeedTable} WHERE id=@id), @dateAdded), @lastUpdated, @imageUrl, @originalDoc)", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
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),
|
||||
new SqliteParameter("dateAdded", feedModel.DateAdded?.ToUnixTimeMilliseconds()),
|
||||
new SqliteParameter("lastUpdated", feedModel.LastUpdated?.ToUnixTimeMilliseconds()),
|
||||
new SqliteParameter("imageUrl", feedModel.ImageUrl ?? string.Empty),
|
||||
new SqliteParameter("originalDoc", feedModel.OriginalDocument ?? string.Empty)
|
||||
},
|
||||
Transaction = transaction
|
||||
};
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
Log.Error(ex, "Error on inserting feeds to db.");
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
|
||||
public static async Task FetchFeedItemsAsync(string[]? feedUrls = null)
|
||||
{
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
HashSet<FeedModel> dbFeeds = new HashSet<FeedModel>();
|
||||
string query = $"SELECT * FROM {FeedTable}";
|
||||
if (feedUrls != null)
|
||||
{
|
||||
List<string>? feedUrlsFormatted = feedUrls.Select(s => $"'{s}'").ToList();
|
||||
query = $"SELECT * FROM {FeedTable} WHERE url IN(({string.Join(", ", feedUrlsFormatted)}))";
|
||||
}
|
||||
await using SqliteCommand fetchCmd = new SqliteCommand(query, dbc);
|
||||
await using SqliteDataReader reader = await fetchCmd.ExecuteReaderAsync();
|
||||
while (reader.Read())
|
||||
{
|
||||
dbFeeds.Add(await ReaderToFeedModel(reader));
|
||||
}
|
||||
HashSet<FeedItemModel> feedItems = new HashSet<FeedItemModel>();
|
||||
foreach (var dbFeed in dbFeeds)
|
||||
{
|
||||
/*GenericSyndicationFeed syndication = new GenericSyndicationFeed();
|
||||
syndication.Load(dbFeed.OriginalDocument);*/
|
||||
//TODO: Get items and add to db
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> RemoveFeedAsync(FeedModel feedModel)
|
||||
{
|
||||
bool result = false;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {FeedTable} WHERE url=@Url; UPDATE {FeedItemTable} SET feed_id=NULL WHERE feed_id=@Url", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("Url", feedModel.EncodedUrl)
|
||||
}
|
||||
};
|
||||
int affected = await cmd.ExecuteNonQueryAsync();
|
||||
if (affected != 0)
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
// Feed items
|
||||
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? feedIds = null)
|
||||
{
|
||||
List<string>? formattedIds = feedIds?.Select(s => $"'{s}'").ToList();
|
||||
HashSet<FeedItemModel> feedItems = new HashSet<FeedItemModel>();
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand(
|
||||
formattedIds != null
|
||||
? $"SELECT * FROM {FeedItemTable} WHERE feed_id IN ({string.Join(", ", formattedIds)})"
|
||||
: $"SELECT * FROM {FeedItemTable}", dbc);
|
||||
|
||||
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
while (reader.Read())
|
||||
{
|
||||
FeedItemModel feedItemModel = new FeedItemModel()
|
||||
{
|
||||
Id = reader["id"].ToString(),
|
||||
EncodedFeedUrl = reader["feed_url"].ToString(),
|
||||
Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0,
|
||||
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()
|
||||
};
|
||||
feedItems.Add(feedItemModel);
|
||||
}
|
||||
return feedItems;
|
||||
}
|
||||
|
||||
public static async Task<int> SetFeedItemsAsync(HashSet<FeedItemModel> items)
|
||||
{
|
||||
int result = 0;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteTransaction transaction = dbc.BeginTransaction();
|
||||
await using SqliteCommand cmd = new SqliteCommand($"INSERT OR REPLACE INTO {FeedItemTable} (id, feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content)" +
|
||||
$"VALUES (IFNULL((SELECT id FROM {FeedItemTable} WHERE link=@link), @id), @feedUrl, @read, @title, @description, @link, @lastUpdated, @publishingDate, @authors, @categories, @content)", dbc)
|
||||
{
|
||||
Transaction = transaction
|
||||
};
|
||||
foreach (FeedItemModel item in items)
|
||||
{
|
||||
cmd.Parameters.Clear();
|
||||
cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? string.Empty));
|
||||
cmd.Parameters.Add(new SqliteParameter("feedUrl", item.EncodedFeedUrl ?? string.Empty));
|
||||
cmd.Parameters.Add(new SqliteParameter("read", item.Read ? 1 : 0));
|
||||
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));
|
||||
cmd.Parameters.Add(new SqliteParameter("lastUpdated", item.LastUpdated?.ToUnixTimeMilliseconds()));
|
||||
cmd.Parameters.Add(new SqliteParameter("publishingDate", item.PublishingDate?.ToUnixTimeMilliseconds() ?? 0));
|
||||
cmd.Parameters.Add(new SqliteParameter("authors", item.Authors != null ? string.Join(',', item.Authors) : 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));
|
||||
if (dbc.State != ConnectionState.Open)
|
||||
dbc.Open();
|
||||
int affected = await cmd.ExecuteNonQueryAsync();
|
||||
if (affected == 0)
|
||||
Log.Verbose("Could not set feed item: {FeedLink}", item.Link);
|
||||
else
|
||||
result += affected;
|
||||
}
|
||||
transaction.Commit();
|
||||
return result;
|
||||
}
|
||||
public static async Task<bool> RemoveFeedItemAsync(FeedItemModel itemModel)
|
||||
{
|
||||
bool result = false;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand($"DELETE FROM {FeedItemTable} WHERE id=@id", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter("id", itemModel.Id)
|
||||
}
|
||||
};
|
||||
int affected = await cmd.ExecuteNonQueryAsync();
|
||||
if (affected != 0)
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
public static async Task<CategoryModel?> GetGroupFromFeedItemAsync(FeedItemModel feedItem)
|
||||
{
|
||||
CategoryModel? result = null;
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
await using SqliteCommand cmd = new SqliteCommand($"SELECT * FROM {GroupTable} WHERE id=(SELECT group_id FROM {FeedTable} WHERE id=@fId)", dbc)
|
||||
{
|
||||
Parameters =
|
||||
{
|
||||
new SqliteParameter ("fId", feedItem.Id)
|
||||
}
|
||||
};
|
||||
await using SqliteDataReader reader = await cmd.ExecuteReaderAsync();
|
||||
HashSet<CategoryModel>? groups = null;
|
||||
/*if (reader.Read())
|
||||
groups = await GetCategoriesAsync(reader["group_id"].ToString());*/
|
||||
if (groups != null && groups.Any())
|
||||
result = groups.FirstOrDefault();
|
||||
return result;
|
||||
}
|
||||
private static async Task<FeedModel> ReaderToFeedModel(SqliteDataReader reader)
|
||||
{
|
||||
FeedModel fetchedFeed = new FeedModel()
|
||||
{
|
||||
EncodedUrl = reader["url"].ToString(),
|
||||
Title = reader["title"].ToString(),
|
||||
CategoryId = reader["group_id"].ToString(),
|
||||
FeedType = reader["feed_type"].ToString(),
|
||||
Description = reader["description"].ToString(),
|
||||
Language = reader["language"].ToString(),
|
||||
Copyright = reader["copyright"].ToString(),
|
||||
PublicationDate = 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),
|
||||
ImageUrl = reader["image_url"].ToString()
|
||||
};
|
||||
//TODO: Set group on insert
|
||||
/*var groupFetch = await GetGroupsAsync(fetchedFeed.GroupId);
|
||||
if (groupFetch.Any())
|
||||
fetchedFeed.Group = groupFetch.First();
|
||||
else
|
||||
Log.Warning("Could not get group from feed: {FeedId}", fetchedFeed.Id);*/
|
||||
return fetchedFeed;
|
||||
}
|
||||
|
||||
//===
|
||||
public static async void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
Log.Verbose("Checking database...");
|
||||
HashSet<string> failed = new HashSet<string>();
|
||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||
dbc.Open();
|
||||
Log.Verbose("Checking table: {Table}", "category");
|
||||
var queryResponse = await dbc.QueryAsync("CREATE TABLE IF NOT EXISTS category (name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING, id STRING PRIMARY KEY)");
|
||||
if (queryResponse.Any()) failed.Add("category");
|
||||
|
||||
Log.Verbose("Checking table: {Table}", "feed");
|
||||
queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS feed (url STRING PRIMARY KEY, 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)");
|
||||
if (queryResponse.Any()) failed.Add("feed");
|
||||
|
||||
Log.Verbose("Checking table: {Table}", "feed_item");
|
||||
queryResponse = await dbc.QueryAsync($"CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, feed_url STRING, read INT, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)");
|
||||
if (queryResponse.Any()) failed.Add("feed_item");
|
||||
|
||||
if (failed.Any())
|
||||
{
|
||||
var joined = string.Join(',', failed);
|
||||
Log.Error("Failed to initialize table(s): {TableNames}", joined);
|
||||
}
|
||||
else
|
||||
Log.Verbose("Checking database done!");
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ namespace SharpRss.Models
|
|||
{
|
||||
Id = Guid.NewGuid().ToString();
|
||||
}
|
||||
/*private string _id = string.Empty;*/
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
private string _hexColor = string.Empty;
|
||||
|
@ -24,7 +24,8 @@ namespace SharpRss.Models
|
|||
}
|
||||
set => _hexColor = value;
|
||||
}
|
||||
public int FeedCount { get; set; }
|
||||
public string Icon { get; set; } = string.Empty;
|
||||
public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now;
|
||||
public int SyndicationCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,23 @@
|
|||
|
||||
namespace SharpRss.Models
|
||||
{
|
||||
public class FeedItemModel
|
||||
public class SyndicationItemModel
|
||||
{
|
||||
public FeedModel? ParentFeed { get; set; }
|
||||
public SyndicationModel SyndicationParent { get; set; } = new SyndicationModel();
|
||||
// Db props
|
||||
public string? Id { get; set; } = string.Empty;
|
||||
public string EncodedFeedUrl { get; set; } = string.Empty;
|
||||
public string EncodedSyndicationUrl { get; set; } = string.Empty;
|
||||
public bool Read { get; set; }
|
||||
public string? Type { get; set; } = string.Empty;
|
||||
public string? Title { get; set; } = string.Empty;
|
||||
public string? Description { get; set; } = string.Empty;
|
||||
public string? Link { get; set; } = string.Empty;
|
||||
public DateTimeOffset? LastUpdated { get; set; }
|
||||
public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now;
|
||||
public DateTimeOffset? ItemUpdatedDate { get; set; }
|
||||
public DateTimeOffset? PublishingDate { get; set; }
|
||||
public string[]? Authors { get; set; }
|
||||
public string[]? Categories { get; set; }
|
||||
public string? Content { get; set; } = string.Empty;
|
||||
public string? CommentsUrl { get; set; } = string.Empty;
|
||||
public string? HexColor { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
|
@ -4,18 +4,21 @@ using ToolQit.Extensions;
|
|||
|
||||
namespace SharpRss.Models
|
||||
{
|
||||
public class FeedModel
|
||||
public class SyndicationModel
|
||||
{
|
||||
public CategoryModel? Category { get; set; }
|
||||
// DB props
|
||||
public string EncodedUrl { get; set; } = string.Empty;
|
||||
public string? Title { get; set; } = string.Empty;
|
||||
public string? CategoryId { get; set; } = string.Empty;
|
||||
public string? FeedType { get; set; } = string.Empty;
|
||||
public string? FeedVersion { get; set; } = string.Empty;
|
||||
public string? SyndicationType { get; set; } = string.Empty;
|
||||
public string? SyndicationVersion { get; set; } = string.Empty;
|
||||
public string? Description { get; set; } = string.Empty;
|
||||
public string? Language { get; set; } = string.Empty;
|
||||
public string? Copyright { get; set; } = string.Empty;
|
||||
public DateTimeOffset LastUpdated { get; set; } = DateTimeOffset.Now;
|
||||
public DateTimeOffset? PublicationDate { get; set; }
|
||||
public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now;
|
||||
public DateTimeOffset? SynUpdatedDate { get; set; }
|
||||
public string[]? Categories { get; set; }
|
||||
public int ItemCount { get; set; }
|
||||
private string _imageUrl = string.Empty;
|
||||
|
@ -24,7 +27,7 @@ namespace SharpRss.Models
|
|||
get
|
||||
{
|
||||
if (_imageUrl.IsNullEmptyWhiteSpace())
|
||||
_imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedUrl)).Host);
|
||||
_imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrlFromBase64(EncodedUrl)).Host);
|
||||
return _imageUrl;
|
||||
}
|
||||
set
|
||||
|
@ -36,7 +39,7 @@ namespace SharpRss.Models
|
|||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is FeedModel objModel)
|
||||
if (obj is SyndicationModel objModel)
|
||||
return EncodedUrl.Equals(objModel.EncodedUrl);
|
||||
return false;
|
||||
}
|
|
@ -18,11 +18,11 @@ namespace SharpRss.Services
|
|||
SetupTestCategoriesAndFeedsAsync();
|
||||
}
|
||||
|
||||
public async Task<HashSet<object>> GetCategoriesAndFeedsAsync()
|
||||
public async Task<HashSet<object>> GetCategoriesAndSyndicationsAsync()
|
||||
{
|
||||
HashSet<object> items = new HashSet<object>();
|
||||
items.UnionWith(await GetCategoriesAsync());
|
||||
items.UnionWith(await GetUngroupedFeedsAsync());
|
||||
items.UnionWith(await GetUngroupedSyndicationsAsync());
|
||||
return items;
|
||||
}
|
||||
|
||||
|
@ -54,25 +54,25 @@ namespace SharpRss.Services
|
|||
Log.Verbose("Fetching feeds...");
|
||||
var feeds = await GetFeedsAsync();
|
||||
}
|
||||
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetFeedsAsync(categoryId == null ? null : new[]{ categoryId });
|
||||
public async Task<HashSet<FeedModel>> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync(new []{""});
|
||||
public async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string feedId, string? groupId = null) => await GetFeedItemsFromFeedsAsync(new[] { feedId }, groupId);
|
||||
public async Task<HashSet<FeedItemModel>> GetFeedItemsFromFeedsAsync(string[] feedIds, string? categoryId = null)
|
||||
public async Task<HashSet<SyndicationModel>> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetSyndicationsAsync(categoryId == null ? null : new[]{ categoryId });
|
||||
public async Task<HashSet<SyndicationModel>> GetUngroupedSyndicationsAsync() => await DbAccess.GetSyndicationsAsync(new []{""});
|
||||
public async Task<HashSet<SyndicationItemModel>> GetSyndicationItemsAsync(string feedId, string? groupId = null) => await GetSyndicationItemsFromSyndicationsAsync(new[] { feedId }, groupId);
|
||||
public async Task<HashSet<SyndicationItemModel>> GetSyndicationItemsFromSyndicationsAsync(string[] feedIds, string? categoryId = null)
|
||||
{
|
||||
var items = await DbAccess.GetFeedItemsAsync(feedIds);
|
||||
var items = await DbAccess.GetSyndicationItemsAsync(feedIds);
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static FeedModel FromResource(ISyndicationResource resource)
|
||||
private static SyndicationModel FromResource(ISyndicationResource resource)
|
||||
{
|
||||
FeedModel model = new FeedModel();
|
||||
SyndicationModel model = new SyndicationModel();
|
||||
switch (resource.Format)
|
||||
{
|
||||
case SyndicationContentFormat.Rss:
|
||||
RssFeed rssFeed = (RssFeed)resource;
|
||||
model.FeedType = rssFeed.Format.ToString();
|
||||
model.SyndicationType = rssFeed.Format.ToString();
|
||||
model.Title = rssFeed.Channel.Title;
|
||||
model.Description = rssFeed.Channel.Description;
|
||||
model.Copyright = rssFeed.Channel.Copyright;
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<PackageReference Include="Argotic.Common" Version="3000.0.5" />
|
||||
<PackageReference Include="Argotic.Core" Version="3000.0.5" />
|
||||
<PackageReference Include="Argotic.Web" Version="2008.0.2" />
|
||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||
<PackageReference Include="Dapper" Version="2.0.138" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.7" />
|
||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -15,8 +15,8 @@ namespace SharpRss
|
|||
{
|
||||
public GenericSyndicationFeed SyndicationFeed { get; set; }
|
||||
public CategoryModel Category { get; set; }
|
||||
public FeedModel FeedModel { get; set; }
|
||||
public HashSet<FeedItemModel> FeedItems { get; set; }
|
||||
public SyndicationModel SyndicationModel { get; set; }
|
||||
public HashSet<SyndicationItemModel> SyndicationItems { get; set; }
|
||||
public bool Fetched;
|
||||
}
|
||||
public static class SyndicationManager
|
||||
|
@ -27,27 +27,27 @@ namespace SharpRss
|
|||
Uri feedUri = new Uri(feedUrl);
|
||||
try
|
||||
{
|
||||
Log.Debug("Fetching feed: {FeedUri}", feedUri.ToString());
|
||||
Log.Debug("Fetching syndication: {SyndicationUri}", feedUri.ToString());
|
||||
syndicationFeed = GenericSyndicationFeed.Create(feedUri);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl);
|
||||
Log.Error(e,"Could not get syndication: {SyndicationUrl}", feedUrl);
|
||||
}
|
||||
return ConstructSyndicationContainer(syndicationFeed, feedUrl);
|
||||
}
|
||||
|
||||
public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed)
|
||||
public static Stream SyndicationToStream(GenericSyndicationFeed syndicationFeed)
|
||||
{
|
||||
MemoryStream memStream = new MemoryStream();
|
||||
syndicationFeed.Resource.Save(memStream);
|
||||
if (memStream.Length <= 0)
|
||||
Log.Warning("Failed to serialize {FeedType} feed: {FeedUri}", syndicationFeed.Format.ToString(), syndicationFeed.Title);
|
||||
Log.Warning("Failed to serialize {SyndicationType} feed: {SyndicationUri}", syndicationFeed.Format.ToString(), syndicationFeed.Title);
|
||||
memStream.Position = 0;
|
||||
return memStream;
|
||||
}
|
||||
|
||||
private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string feedUrl)
|
||||
private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string syndicationUrl)
|
||||
{
|
||||
SyndicationContainer container = new SyndicationContainer();
|
||||
if (syndicationFeed == null)
|
||||
|
@ -56,30 +56,30 @@ namespace SharpRss
|
|||
return container;
|
||||
}
|
||||
container.SyndicationFeed = syndicationFeed;
|
||||
container.FeedModel = new FeedModel();
|
||||
container.FeedItems = new HashSet<FeedItemModel>();
|
||||
container.SyndicationModel = new SyndicationModel();
|
||||
container.SyndicationItems = new HashSet<SyndicationItemModel>();
|
||||
switch (syndicationFeed.Resource.Format)
|
||||
{
|
||||
case SyndicationContentFormat.Rss:
|
||||
RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource;
|
||||
if (rssFeed.Channel == null) break;
|
||||
container.FeedModel.EncodedUrl = EncodeUrl(feedUrl);
|
||||
container.FeedModel.Title = rssFeed.Channel.Title ?? string.Empty;
|
||||
container.FeedModel.FeedType = rssFeed.Format.ToString() ?? string.Empty;
|
||||
container.FeedModel.FeedVersion = rssFeed.Version?.ToString() ?? string.Empty;
|
||||
container.FeedModel.Description = rssFeed.Channel.Description ?? string.Empty;
|
||||
container.FeedModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty;
|
||||
container.FeedModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty;
|
||||
container.FeedModel.PublicationDate = rssFeed.Channel.PublicationDate is { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.PublicationDate) : DateTimeOffset.MinValue;
|
||||
container.FeedModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty<string>();
|
||||
container.FeedModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.EncodedUrl = EncodeUrlToBase64(syndicationUrl);
|
||||
container.SyndicationModel.Title = rssFeed.Channel.Title ?? string.Empty;
|
||||
container.SyndicationModel.SyndicationType = rssFeed.Format.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.SyndicationVersion = rssFeed.Version?.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.Description = rssFeed.Channel.Description ?? string.Empty;
|
||||
container.SyndicationModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty;
|
||||
container.SyndicationModel.PublicationDate = rssFeed.Channel.PublicationDate is { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.PublicationDate) : DateTimeOffset.MinValue;
|
||||
container.SyndicationModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty<string>();
|
||||
container.SyndicationModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
|
||||
foreach (var rssItem in rssFeed.Channel.Items)
|
||||
{
|
||||
FeedItemModel itemModel = new FeedItemModel()
|
||||
SyndicationItemModel itemModel = new SyndicationItemModel()
|
||||
{
|
||||
Id = rssItem.Link?.ToString() ?? string.Empty,
|
||||
EncodedFeedUrl = container.FeedModel.EncodedUrl ?? string.Empty,
|
||||
Type = container.FeedModel.FeedType ?? string.Empty,
|
||||
EncodedSyndicationUrl = container.SyndicationModel.EncodedUrl ?? string.Empty,
|
||||
Type = container.SyndicationModel.SyndicationType ?? string.Empty,
|
||||
Title = rssItem.Title ?? string.Empty,
|
||||
Description = rssItem.Description ?? string.Empty,
|
||||
Link = rssItem.Link?.ToString() ?? string.Empty,
|
||||
|
@ -89,53 +89,56 @@ namespace SharpRss
|
|||
Content = rssItem.Extensions?.Where(x => x is SiteSummaryContentSyndicationExtension).Select(x => (x as SiteSummaryContentSyndicationExtension)?.Context.Encoded).FirstOrDefault() ?? string.Empty,
|
||||
CommentsUrl = rssItem.Extensions?.Where(x => x is WellFormedWebCommentsSyndicationExtension).Select(x => (x as WellFormedWebCommentsSyndicationExtension)?.Context.CommentsFeed.ToString()).FirstOrDefault() ?? string.Empty
|
||||
};
|
||||
container.FeedItems.Add(itemModel);
|
||||
container.SyndicationItems.Add(itemModel);
|
||||
}
|
||||
break;
|
||||
case SyndicationContentFormat.Atom:
|
||||
AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource;
|
||||
container.FeedModel.EncodedUrl = EncodeUrl(feedUrl);
|
||||
container.FeedModel.Title = atomFeed.Title?.Content ?? string.Empty;
|
||||
container.FeedModel.FeedType = atomFeed.Format.ToString() ?? string.Empty;
|
||||
container.FeedModel.FeedVersion = atomFeed.Version?.ToString() ?? string.Empty;
|
||||
container.FeedModel.Description = atomFeed.Subtitle?.Content ?? string.Empty;
|
||||
container.FeedModel.Language = atomFeed.Language?.ToString() ?? string.Empty;
|
||||
container.FeedModel.Copyright = atomFeed.Rights?.Content ?? string.Empty;
|
||||
container.FeedModel.PublicationDate = atomFeed.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue;
|
||||
container.FeedModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty<string>();
|
||||
container.FeedModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.EncodedUrl = EncodeUrlToBase64(syndicationUrl);
|
||||
container.SyndicationModel.Title = atomFeed.Title?.Content ?? string.Empty;
|
||||
container.SyndicationModel.SyndicationType = atomFeed.Format.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.SyndicationVersion = atomFeed.Version?.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.Description = atomFeed.Subtitle?.Content ?? string.Empty;
|
||||
container.SyndicationModel.Language = atomFeed.Language?.ToString() ?? string.Empty;
|
||||
container.SyndicationModel.Copyright = atomFeed.Rights?.Content ?? string.Empty;
|
||||
container.SyndicationModel.PublicationDate = atomFeed.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue;
|
||||
container.SyndicationModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty<string>();
|
||||
container.SyndicationModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty;
|
||||
foreach (var entry in atomFeed.Entries)
|
||||
{
|
||||
FeedItemModel itemModel = new FeedItemModel()
|
||||
SyndicationItemModel itemModel = new SyndicationItemModel()
|
||||
{
|
||||
Id = entry.Id?.Uri.ToString() ?? string.Empty,
|
||||
EncodedFeedUrl = container.FeedModel?.EncodedUrl ?? string.Empty,
|
||||
Type = container.FeedModel?.FeedType ?? string.Empty,
|
||||
EncodedSyndicationUrl = container.SyndicationModel?.EncodedUrl ?? string.Empty,
|
||||
Type = container.SyndicationModel?.SyndicationType ?? string.Empty,
|
||||
Title = entry.Title?.Content ?? string.Empty,
|
||||
Description = entry.Summary?.Content ?? string.Empty,
|
||||
Link = entry.Id?.Uri.ToString() ?? string.Empty,
|
||||
LastUpdated = entry.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.UpdatedOn) : DateTimeOffset.Now,
|
||||
ItemUpdatedDate = entry.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.UpdatedOn) : DateTimeOffset.Now,
|
||||
PublishingDate = entry.PublishedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.PublishedOn) : entry.UpdatedOn,
|
||||
Authors = entry.Authors?.Select(auth => auth.Name).ToArray() ?? Array.Empty<string>(),
|
||||
Categories = entry.Categories?.Select(cat => cat.Label).ToArray() ?? Array.Empty<string>(),
|
||||
Content = entry.Content?.Content ?? string.Empty
|
||||
};
|
||||
container.FeedItems.Add(itemModel);
|
||||
container.SyndicationItems.Add(itemModel);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.Warning("Feed implementation missing!");
|
||||
Log.Warning("Syndication implementation missing!");
|
||||
break;
|
||||
}
|
||||
container.Fetched = true;
|
||||
return container;
|
||||
}
|
||||
public static string EncodeUrl(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url));
|
||||
public static string DecodeUrl(string base64)
|
||||
public static string EncodeUrlToBase64(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url));
|
||||
public static string DecodeUrlFromBase64(string base64)
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(base64);
|
||||
return Encoding.Default.GetString(bytes);
|
||||
}
|
||||
public static string FormatStringArrayToString(string[] sArr) => string.Join('|', sArr);
|
||||
public static string[] StringToStringArray(string stringFormat) => stringFormat.Split('|');
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
DbAccess.Initialize();
|
||||
|
|
|
@ -5,20 +5,20 @@ using ToolQit;
|
|||
|
||||
namespace WebSharpRSS.Models
|
||||
{
|
||||
public class FeedItemData : FeedItemModel
|
||||
public class SyndicationItemData : SyndicationItemModel
|
||||
{
|
||||
public FeedItemData()
|
||||
public SyndicationItemData()
|
||||
{
|
||||
|
||||
}
|
||||
public static FeedItemData? FromModel(FeedItemModel model) => Utilities.ConvertFrom<FeedItemData, FeedItemModel>(model);
|
||||
public static SyndicationItemData? FromModel(SyndicationItemModel model) => Utilities.ConvertFrom<SyndicationItemData, SyndicationItemModel>(model);
|
||||
private string? _faviconUrl;
|
||||
public string? FaviconUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Link == null || _faviconUrl != null) return _faviconUrl;
|
||||
_faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedFeedUrl)).Host);
|
||||
_faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrlFromBase64(EncodedSyndicationUrl)).Host);
|
||||
return _faviconUrl;
|
||||
}
|
||||
}
|
|
@ -16,19 +16,19 @@ namespace WebSharpRSS.Models
|
|||
CategoryModel = categoryModel;
|
||||
Title = categoryModel.Name;
|
||||
Icon = categoryModel.Icon == string.Empty ? Icons.Material.Filled.RssFeed : categoryModel.Icon;
|
||||
HasChildren = CategoryModel.FeedCount > 0;
|
||||
HasChildren = CategoryModel.SyndicationCount > 0;
|
||||
}
|
||||
|
||||
public TreeItemData(FeedModel feedModel)
|
||||
public TreeItemData(SyndicationModel syndicationModel)
|
||||
{
|
||||
FeedModel = feedModel;
|
||||
Title = feedModel.Title ?? string.Empty;
|
||||
if (FeedModel.EncodedUrl == null) return;
|
||||
FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(FeedModel.EncodedUrl)).Host);
|
||||
TotalItems = FeedModel.ItemCount;
|
||||
SyndicationModel = syndicationModel;
|
||||
Title = syndicationModel.Title ?? string.Empty;
|
||||
if (SyndicationModel.EncodedUrl == null) return;
|
||||
FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrlFromBase64(SyndicationModel.EncodedUrl)).Host);
|
||||
TotalItems = SyndicationModel.ItemCount;
|
||||
}
|
||||
public readonly CategoryModel? CategoryModel;
|
||||
public readonly FeedModel? FeedModel;
|
||||
public readonly SyndicationModel? SyndicationModel;
|
||||
|
||||
public HashSet<TreeItemData>? Children { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
|
|
@ -44,22 +44,22 @@
|
|||
}
|
||||
}
|
||||
private string? _cid;
|
||||
HashSet<FeedItemData> items = new HashSet<FeedItemData>();
|
||||
HashSet<SyndicationItemData> items = new HashSet<SyndicationItemData>();
|
||||
bool _isLoading = true;
|
||||
private async void LoadItems()
|
||||
{
|
||||
_isLoading = true;
|
||||
if (Fid != null)
|
||||
{
|
||||
var fItems = await _syndicationService.GetFeedItemsAsync(Fid);
|
||||
items = fItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
|
||||
var fItems = await _syndicationService.GetSyndicationItemsAsync(Fid);
|
||||
items = fItems.Select(x => SyndicationItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
|
||||
}
|
||||
else if (Cid != null)
|
||||
{
|
||||
var feeds = await _syndicationService.GetFeedsAsync(Cid == string.Empty ? null : Cid);
|
||||
var feedIds = feeds.Select(x => x.EncodedUrl);
|
||||
var feedItems = await _syndicationService.GetFeedItemsFromFeedsAsync(feedIds.ToArray());
|
||||
items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
|
||||
var feedItems = await _syndicationService.GetSyndicationItemsFromSyndicationsAsync(feedIds.ToArray());
|
||||
items = feedItems.Select(x => SyndicationItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
|
||||
}
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
|
|
|
@ -46,15 +46,15 @@
|
|||
|
||||
@code {
|
||||
[Parameter]
|
||||
public HashSet<FeedItemData>? Items { get; set; }
|
||||
public HashSet<SyndicationItemData>? Items { get; set; }
|
||||
DialogOptions _dialogOptions = new DialogOptions() { FullWidth = true, MaxWidth = MaxWidth.ExtraLarge, NoHeader = true, CloseButton = true, CloseOnEscapeKey = true };
|
||||
|
||||
private void Callback(FeedItemData feedItem)
|
||||
private void Callback(SyndicationItemData syndicationItem)
|
||||
{
|
||||
var parameters = new DialogParameters();
|
||||
parameters.Add("Data", feedItem);
|
||||
parameters.Add("Data", syndicationItem);
|
||||
_dialogService.Show<ReadDialog>("", parameters, _dialogOptions);
|
||||
Log.Verbose("Item: {ItemId} clicked", feedItem.Id);
|
||||
Log.Verbose("Item: {ItemId} clicked", syndicationItem.Id);
|
||||
}
|
||||
|
||||
}
|
|
@ -25,5 +25,5 @@ else
|
|||
|
||||
@code {
|
||||
[Parameter]
|
||||
public FeedItemData? FeedItem { get; set; }
|
||||
public SyndicationItemData? FeedItem { get; set; }
|
||||
}
|
|
@ -8,6 +8,6 @@
|
|||
|
||||
@code {
|
||||
[Parameter]
|
||||
public FeedItemData? Data { get; set; }
|
||||
public SyndicationItemData? Data { get; set; }
|
||||
|
||||
}
|
|
@ -49,9 +49,9 @@
|
|||
private void ItemClicked()
|
||||
{
|
||||
if (_selectedItem == null) return;
|
||||
if (_selectedItem.FeedModel != null)
|
||||
if (_selectedItem.SyndicationModel != null)
|
||||
{
|
||||
_navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.EncodedUrl}");
|
||||
_navigation.NavigateTo($"/list?fid={_selectedItem.SyndicationModel.EncodedUrl}");
|
||||
}
|
||||
else if (_selectedItem.CategoryModel != null)
|
||||
{
|
||||
|
@ -71,11 +71,11 @@
|
|||
{
|
||||
Log.Verbose("Loading guide data...");
|
||||
_guideItems.Add(new TreeItemData(new CategoryModel() { Name = "All", Icon = Icons.Material.Filled.Home, HexColor = Colors.Blue.Accent1, Id = string.Empty }));
|
||||
HashSet<object> items = await _syndicationService.GetCategoriesAndFeedsAsync();
|
||||
HashSet<object> items = await _syndicationService.GetCategoriesAndSyndicationsAsync();
|
||||
_guideItems.UnionWith(ModelToTreeItem(items));
|
||||
|
||||
StateHasChanged();
|
||||
Log.Verbose("Guide initialized!");
|
||||
}
|
||||
private HashSet<TreeItemData> ModelToTreeItem<T>(HashSet<T> model) => model.Select(x => x is CategoryModel model ? new TreeItemData(model) : x is FeedModel feedModel ? new TreeItemData(feedModel) : throw new ArgumentException("Item arg is invalid!")).ToHashSet();
|
||||
private HashSet<TreeItemData> ModelToTreeItem<T>(HashSet<T> model) => model.Select(x => x is CategoryModel model ? new TreeItemData(model) : x is SyndicationModel feedModel ? new TreeItemData(feedModel) : throw new ArgumentException("Item arg is invalid!")).ToHashSet();
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MudBlazor" Version="6.2.2" />
|
||||
<PackageReference Include="MudBlazor" Version="6.4.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user