mirror of
https://github.com/hmaxnl/SharpRSS.git
synced 2024-11-09 23:44:20 +01:00
Base mostly done, starting with UI polishing and features.
This commit is contained in:
parent
18e4135130
commit
91b28de875
|
@ -77,7 +77,7 @@ namespace SharpRss
|
||||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||||
dbc.Open();
|
dbc.Open();
|
||||||
int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed
|
int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed
|
||||||
(url,
|
(encoded_url,
|
||||||
title,
|
title,
|
||||||
category_id,
|
category_id,
|
||||||
feed_type,
|
feed_type,
|
||||||
|
@ -90,7 +90,7 @@ namespace SharpRss
|
||||||
categories,
|
categories,
|
||||||
image_url)
|
image_url)
|
||||||
VALUES (
|
VALUES (
|
||||||
@Url,
|
@EncodedUrl,
|
||||||
@Title,
|
@Title,
|
||||||
@CategoryId,
|
@CategoryId,
|
||||||
@FeedType,
|
@FeedType,
|
||||||
|
@ -104,7 +104,7 @@ namespace SharpRss
|
||||||
@ImageUrl)",
|
@ImageUrl)",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Url = feed.OriginalUrl ?? string.Empty,
|
EncodedUrl = feed.EncodedUrl,
|
||||||
Title = feed.Title ?? string.Empty,
|
Title = feed.Title ?? string.Empty,
|
||||||
CategoryId = feed.CategoryId ?? string.Empty,
|
CategoryId = feed.CategoryId ?? string.Empty,
|
||||||
FeedType = feed.FeedType ?? string.Empty,
|
FeedType = feed.FeedType ?? string.Empty,
|
||||||
|
@ -118,7 +118,7 @@ namespace SharpRss
|
||||||
ImageUrl = feed.ImageUrl ?? string.Empty
|
ImageUrl = feed.ImageUrl ?? string.Empty
|
||||||
});
|
});
|
||||||
if (affected == 0)
|
if (affected == 0)
|
||||||
Log.Warning("Failed to add feed: {FeedUrl}", feed.OriginalUrl);
|
Log.Warning("Failed to add feed: {FeedUrl}", feed.EncodedUrl);
|
||||||
}
|
}
|
||||||
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null)
|
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null)
|
||||||
{
|
{
|
||||||
|
@ -130,7 +130,7 @@ namespace SharpRss
|
||||||
{
|
{
|
||||||
FeedModel feedModel = new FeedModel()
|
FeedModel feedModel = new FeedModel()
|
||||||
{
|
{
|
||||||
OriginalUrl = reader["url"].ToString(),
|
EncodedUrl = reader["encoded_url"].ToString(),
|
||||||
Title = reader["title"].ToString(),
|
Title = reader["title"].ToString(),
|
||||||
CategoryId = reader["category_id"].ToString(),
|
CategoryId = reader["category_id"].ToString(),
|
||||||
FeedType = reader["feed_type"].ToString(),
|
FeedType = reader["feed_type"].ToString(),
|
||||||
|
@ -143,10 +143,20 @@ namespace SharpRss
|
||||||
Categories = reader["categories"].ToString().Split(','),
|
Categories = reader["categories"].ToString().Split(','),
|
||||||
ImageUrl = reader["image_url"].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);
|
feeds.Add(feedModel);
|
||||||
}
|
}
|
||||||
return feeds;
|
return feeds;
|
||||||
}
|
}
|
||||||
|
public static async Task<bool> DeleteFeedAsync(FeedModel feed, bool deleteItems = false)
|
||||||
|
{
|
||||||
|
if (feed == 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 });
|
||||||
|
if (affected > 0 && deleteItems)
|
||||||
|
await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl });
|
||||||
|
return affected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items)
|
public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items)
|
||||||
{
|
{
|
||||||
|
@ -156,13 +166,13 @@ namespace SharpRss
|
||||||
await using SqliteTransaction dbTransaction = dbc.BeginTransaction();
|
await using SqliteTransaction dbTransaction = dbc.BeginTransaction();
|
||||||
foreach (FeedItemModel item in items)
|
foreach (FeedItemModel item in items)
|
||||||
{
|
{
|
||||||
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)
|
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, @FeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)",
|
VALUES (@Id, @EncodedFeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)",
|
||||||
transaction: dbTransaction,
|
transaction: dbTransaction,
|
||||||
param: new
|
param: new
|
||||||
{
|
{
|
||||||
Id = item.Id ?? string.Empty,
|
Id = item.Id ?? string.Empty,
|
||||||
FeedUrl = item.FeedUrl ?? string.Empty,
|
EncodedFeedUrl = item.EncodedFeedUrl ?? string.Empty,
|
||||||
Read = item.Read.ToString(),
|
Read = item.Read.ToString(),
|
||||||
Title = item.Title ?? string.Empty,
|
Title = item.Title ?? string.Empty,
|
||||||
Description = item.Description ?? string.Empty,
|
Description = item.Description ?? string.Empty,
|
||||||
|
@ -177,26 +187,19 @@ namespace SharpRss
|
||||||
}
|
}
|
||||||
dbTransaction.Commit();
|
dbTransaction.Commit();
|
||||||
}
|
}
|
||||||
|
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? encodedFeedUrls)
|
||||||
private static string FormatParametersFromArray(string[] dbParams)
|
|
||||||
{
|
|
||||||
string[] formatted = dbParams.Select(s => $"'{s}'").ToArray();
|
|
||||||
return string.Join(", ", formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? feedUrls)
|
|
||||||
{
|
{
|
||||||
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||||
dbc.Open();
|
dbc.Open();
|
||||||
HashSet<FeedItemModel> items = new HashSet<FeedItemModel>();
|
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_url IN({FormatParametersFromArray(feedUrls)})");
|
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.
|
//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())
|
while (await reader.ReadAsync())
|
||||||
{
|
{
|
||||||
FeedItemModel feedItemModel = new FeedItemModel()
|
FeedItemModel feedItemModel = new FeedItemModel()
|
||||||
{
|
{
|
||||||
Id = reader["id"].ToString(),
|
Id = reader["id"].ToString(),
|
||||||
FeedUrl = reader["feed_url"].ToString(),
|
EncodedFeedUrl = reader["encoded_feed_url"].ToString(),
|
||||||
Read = bool.Parse(reader["read"].ToString()),
|
Read = bool.Parse(reader["read"].ToString()),
|
||||||
Title = reader["title"].ToString(),
|
Title = reader["title"].ToString(),
|
||||||
Description = reader["description"].ToString(),
|
Description = reader["description"].ToString(),
|
||||||
|
@ -212,7 +215,6 @@ namespace SharpRss
|
||||||
Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count);
|
Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async void Initialize()
|
public static async void Initialize()
|
||||||
{
|
{
|
||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
|
@ -223,12 +225,23 @@ namespace SharpRss
|
||||||
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS category (id STRING PRIMARY KEY, name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING)");
|
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");
|
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)");
|
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (encoded_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");
|
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)");
|
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, encoded_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!");
|
Log.Verbose("Checking database done!");
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatParametersFromArray(string[] dbParams)
|
||||||
|
{
|
||||||
|
string[] formatted = dbParams.Select(s => $"'{s}'").ToArray();
|
||||||
|
return string.Join(", ", formatted);
|
||||||
|
}
|
||||||
|
public static async Task<int> GetFeedCountAsync(string[] encodedFeedUrls)
|
||||||
|
{
|
||||||
|
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
|
||||||
|
return await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -147,7 +147,7 @@ namespace SharpRss
|
||||||
{
|
{
|
||||||
Parameters =
|
Parameters =
|
||||||
{
|
{
|
||||||
new SqliteParameter("url", feedModel.OriginalUrl ?? string.Empty),
|
new SqliteParameter("url", feedModel.EncodedUrl ?? string.Empty),
|
||||||
new SqliteParameter("title", feedModel.Title ?? string.Empty),
|
new SqliteParameter("title", feedModel.Title ?? string.Empty),
|
||||||
new SqliteParameter("groupId", feedModel.CategoryId ?? string.Empty),
|
new SqliteParameter("groupId", feedModel.CategoryId ?? string.Empty),
|
||||||
new SqliteParameter("feedType", feedModel.FeedType ?? string.Empty),
|
new SqliteParameter("feedType", feedModel.FeedType ?? string.Empty),
|
||||||
|
@ -242,7 +242,7 @@ namespace SharpRss
|
||||||
{
|
{
|
||||||
Parameters =
|
Parameters =
|
||||||
{
|
{
|
||||||
new SqliteParameter("Url", feedModel.OriginalUrl)
|
new SqliteParameter("Url", feedModel.EncodedUrl)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
int affected = await cmd.ExecuteNonQueryAsync();
|
int affected = await cmd.ExecuteNonQueryAsync();
|
||||||
|
@ -268,7 +268,7 @@ namespace SharpRss
|
||||||
FeedItemModel feedItemModel = new FeedItemModel()
|
FeedItemModel feedItemModel = new FeedItemModel()
|
||||||
{
|
{
|
||||||
Id = reader["id"].ToString(),
|
Id = reader["id"].ToString(),
|
||||||
FeedUrl = reader["feed_url"].ToString(),
|
EncodedFeedUrl = reader["feed_url"].ToString(),
|
||||||
Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0,
|
Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0,
|
||||||
Title = reader["title"].ToString(),
|
Title = reader["title"].ToString(),
|
||||||
Description = reader["description"].ToString(),
|
Description = reader["description"].ToString(),
|
||||||
|
@ -301,7 +301,7 @@ namespace SharpRss
|
||||||
{
|
{
|
||||||
cmd.Parameters.Clear();
|
cmd.Parameters.Clear();
|
||||||
cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? string.Empty));
|
cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? string.Empty));
|
||||||
cmd.Parameters.Add(new SqliteParameter("feedUrl", item.FeedUrl ?? 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("read", item.Read ? 1 : 0));
|
||||||
cmd.Parameters.Add(new SqliteParameter("type", item.Type ?? string.Empty));
|
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("title", item.Title ?? string.Empty));
|
||||||
|
@ -364,7 +364,7 @@ namespace SharpRss
|
||||||
{
|
{
|
||||||
FeedModel fetchedFeed = new FeedModel()
|
FeedModel fetchedFeed = new FeedModel()
|
||||||
{
|
{
|
||||||
OriginalUrl = reader["url"].ToString(),
|
EncodedUrl = reader["url"].ToString(),
|
||||||
Title = reader["title"].ToString(),
|
Title = reader["title"].ToString(),
|
||||||
CategoryId = reader["group_id"].ToString(),
|
CategoryId = reader["group_id"].ToString(),
|
||||||
FeedType = reader["feed_type"].ToString(),
|
FeedType = reader["feed_type"].ToString(),
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace SharpRss.Models
|
||||||
public class FeedItemModel
|
public class FeedItemModel
|
||||||
{
|
{
|
||||||
public string? Id { get; set; } = string.Empty;
|
public string? Id { get; set; } = string.Empty;
|
||||||
public string FeedUrl { get; set; } = string.Empty;
|
public string EncodedFeedUrl { get; set; } = string.Empty;
|
||||||
public bool Read { get; set; }
|
public bool Read { get; set; }
|
||||||
public string? Type { get; set; } = string.Empty;
|
public string? Type { get; set; } = string.Empty;
|
||||||
public string? Title { get; set; } = string.Empty;
|
public string? Title { get; set; } = string.Empty;
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace SharpRss.Models
|
||||||
{
|
{
|
||||||
public class FeedModel
|
public class FeedModel
|
||||||
{
|
{
|
||||||
public string OriginalUrl { get; set; } = string.Empty;
|
public string EncodedUrl { get; set; } = string.Empty;
|
||||||
public string? Title { get; set; } = string.Empty;
|
public string? Title { get; set; } = string.Empty;
|
||||||
public string? CategoryId { get; set; } = string.Empty;
|
public string? CategoryId { get; set; } = string.Empty;
|
||||||
public string? FeedType { get; set; } = string.Empty;
|
public string? FeedType { get; set; } = string.Empty;
|
||||||
|
@ -17,13 +17,14 @@ namespace SharpRss.Models
|
||||||
public DateTimeOffset? PublicationDate { get; set; }
|
public DateTimeOffset? PublicationDate { get; set; }
|
||||||
public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now;
|
public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now;
|
||||||
public string[]? Categories { get; set; }
|
public string[]? Categories { get; set; }
|
||||||
|
public int ItemCount { get; set; }
|
||||||
private string _imageUrl = string.Empty;
|
private string _imageUrl = string.Empty;
|
||||||
public string ImageUrl
|
public string ImageUrl
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_imageUrl.IsNullEmptyWhiteSpace())
|
if (_imageUrl.IsNullEmptyWhiteSpace())
|
||||||
_imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(OriginalUrl).Host);
|
_imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedUrl)).Host);
|
||||||
return _imageUrl;
|
return _imageUrl;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
|
|
|
@ -9,36 +9,62 @@ using SharpRss.Models;
|
||||||
namespace SharpRss.Services
|
namespace SharpRss.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Managing RSS feeds and groups.
|
/// Managing RSS feeds and categories.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RssService
|
public class RssService
|
||||||
{
|
{
|
||||||
public RssService()
|
public RssService()
|
||||||
{
|
{
|
||||||
SetupTestGroupsAndFeedsAsync();
|
SetupTestCategoriesAndFeedsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashSet<object>> GetCategoriesFeedsAsync()
|
public async Task<HashSet<object>> GetCategoriesAndFeedsAsync()
|
||||||
{
|
{
|
||||||
HashSet<object> items = new HashSet<object>();
|
HashSet<object> items = new HashSet<object>();
|
||||||
items.UnionWith(await GetCategoriesAsync());
|
items.UnionWith(await GetCategoriesAsync());
|
||||||
items.UnionWith(await GetUngroupedFeedsAsync());
|
items.UnionWith(await GetUngroupedFeedsAsync());
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
public async Task<bool> CreateGroupAsync(CategoryModel group) => await DbAccess_Old.SetCategoryAsync(group);
|
|
||||||
public async Task<HashSet<CategoryModel>> GetCategoriesAsync() => await DbAccess.GetCategoriesAsync();
|
|
||||||
|
|
||||||
//TODO: Rework this!
|
public async Task<CategoryModel?> CreateCategoryAsync(CategoryModel category) =>
|
||||||
// Subscribe to a feed.
|
await DbAccess.SetCategoryAsync(category);
|
||||||
|
public async Task<HashSet<CategoryModel>> GetCategoriesAsync() => await DbAccess.GetCategoriesAsync();
|
||||||
public async Task<bool> AddSubscriptionAsync(string url, CategoryModel? category = null)
|
public async Task<bool> AddSubscriptionAsync(string url, CategoryModel? category = null)
|
||||||
{
|
{
|
||||||
var syndication = SyndicationManager.CreateSyndication(url);
|
var syndication = SyndicationManager.CreateSyndication(url);
|
||||||
|
if (!syndication.Fetched)
|
||||||
|
{
|
||||||
|
Log.Debug("Failed to fetch syndication feed!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (category != null)
|
if (category != null)
|
||||||
syndication.Category = category;
|
syndication.Category = category;
|
||||||
if (!syndication.Fetched) return false;
|
try
|
||||||
await DbAccess.SetSyndicationAsync(syndication);
|
{
|
||||||
|
await DbAccess.SetSyndicationAsync(syndication);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e,"Error adding feed: {FeedUrl} to database!", url);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public async Task UpdateFeeds()
|
||||||
|
{
|
||||||
|
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? groupId = null)
|
||||||
|
{
|
||||||
|
var items = await DbAccess.GetFeedItemsAsync(feedIds);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static FeedModel FromResource(ISyndicationResource resource)
|
private static FeedModel FromResource(ISyndicationResource resource)
|
||||||
{
|
{
|
||||||
FeedModel model = new FeedModel();
|
FeedModel model = new FeedModel();
|
||||||
|
@ -50,7 +76,7 @@ namespace SharpRss.Services
|
||||||
model.Title = rssFeed.Channel.Title;
|
model.Title = rssFeed.Channel.Title;
|
||||||
model.Description = rssFeed.Channel.Description;
|
model.Description = rssFeed.Channel.Description;
|
||||||
model.Copyright = rssFeed.Channel.Copyright;
|
model.Copyright = rssFeed.Channel.Copyright;
|
||||||
model.OriginalUrl = rssFeed.Channel.SelfLink.ToString();
|
model.EncodedUrl = rssFeed.Channel.SelfLink.ToString();
|
||||||
model.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
|
model.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
|
||||||
model.Language = rssFeed.Channel.Language?.ToString();
|
model.Language = rssFeed.Channel.Language?.ToString();
|
||||||
break;
|
break;
|
||||||
|
@ -63,23 +89,6 @@ namespace SharpRss.Services
|
||||||
}
|
}
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateFeeds()
|
|
||||||
{
|
|
||||||
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? groupId = null)
|
|
||||||
{
|
|
||||||
var items = await DbAccess.GetFeedItemsAsync(feedIds);
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GenericSyndicationFeed? CreateFeed(string url)
|
private GenericSyndicationFeed? CreateFeed(string url)
|
||||||
{
|
{
|
||||||
Uri feedUri = new Uri(url);
|
Uri feedUri = new Uri(url);
|
||||||
|
@ -92,44 +101,41 @@ namespace SharpRss.Services
|
||||||
Log.Verbose("Fetching feed: {FeedUrl}", feedUri.ToString());
|
Log.Verbose("Fetching feed: {FeedUrl}", feedUri.ToString());
|
||||||
return GenericSyndicationFeed.Create(new Uri(url));
|
return GenericSyndicationFeed.Create(new Uri(url));
|
||||||
}
|
}
|
||||||
private async void SetupTestGroupsAndFeedsAsync()
|
private async void SetupTestCategoriesAndFeedsAsync()
|
||||||
{
|
{
|
||||||
CategoryModel NewsCat = new CategoryModel() { Name = "News" };
|
/*CategoryModel? newsCategory = await CreateCategoryAsync(new CategoryModel() { Name = "News" });
|
||||||
await AddSubscriptionAsync("https://www.nu.nl/rss/Algemeen", NewsCat);
|
if (newsCategory != null)
|
||||||
await AddSubscriptionAsync("https://www.nu.nl/rss/Economie", NewsCat);
|
|
||||||
await AddSubscriptionAsync("http://fedoramagazine.org/feed/");
|
|
||||||
await AddSubscriptionAsync("https://itsfoss.com/feed");
|
|
||||||
//TODO: Make multiple adding of feed to a transaction, now throws an exception.
|
|
||||||
/*var groupRes = await CreateGroupAsync(new GroupModel() { Name = "Test" });
|
|
||||||
groupRes = await CreateGroupAsync(new GroupModel() { Name = "News" });
|
|
||||||
groupRes = await CreateGroupAsync(new GroupModel() { Name = "Tech" });
|
|
||||||
groupRes = await CreateGroupAsync(new GroupModel() { Name = "Science" });*/
|
|
||||||
/*Log.Verbose("Fetching feeds...");
|
|
||||||
var groups = await GetGroupsAsync();
|
|
||||||
GroupModel testGroup = groups.Single(x => x.Name == "Test");
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var res = await AddFeed("http://fedoramagazine.org/feed/", testGroup);
|
await AddSubscriptionAsync("https://www.nu.nl/rss/Algemeen", newsCategory);
|
||||||
res = await AddFeed("https://www.nasa.gov/rss/dyn/breaking_news.rss", testGroup);
|
await AddSubscriptionAsync("https://www.nasa.gov/rss/dyn/breaking_news.rss", newsCategory);
|
||||||
res = await AddFeed("https://journals.plos.org/plosone/feed/atom", testGroup);
|
await AddSubscriptionAsync("http://news.google.com/?output=atom", newsCategory);
|
||||||
res = await AddFeed("https://itsfoss.com/feed", testGroup);
|
await AddSubscriptionAsync("https://www.ad.nl/home/rss.xml", newsCategory);
|
||||||
res = await AddFeed("https://advisories.ncsc.nl/rss/advisories", testGroup);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "Error fetching feeds!");
|
|
||||||
throw;
|
|
||||||
}*/
|
|
||||||
/*var groups = await GetGroupsAsync();
|
|
||||||
CategoryModel testGroup = groups.Single(x => x.Name == "News");*/
|
|
||||||
|
|
||||||
/*await AddFeedsAsync(new[]
|
CategoryModel? techCategory = await CreateCategoryAsync(new CategoryModel() { Name = "Tech" });
|
||||||
|
if (techCategory != null)
|
||||||
{
|
{
|
||||||
"https://www.nu.nl/rss/Algemeen",
|
await AddSubscriptionAsync("https://itsfoss.com/feed", techCategory);
|
||||||
"https://www.nu.nl/rss/Economie",
|
await AddSubscriptionAsync("http://fedoramagazine.org/feed/", techCategory);
|
||||||
"https://www.nu.nl/rss/Sport",
|
await AddSubscriptionAsync("https://arstechnica.com/feed/", techCategory);
|
||||||
"http://news.google.com/?output=atom"
|
await AddSubscriptionAsync("https://feeds.arstechnica.com/arstechnica/gadgets", techCategory);
|
||||||
}, testGroup);*/
|
}
|
||||||
|
|
||||||
|
CategoryModel? youtubeCategory = await CreateCategoryAsync(new CategoryModel() { Name = "YouTube" });
|
||||||
|
if (youtubeCategory != null)
|
||||||
|
{
|
||||||
|
await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UCXuqSBlHAE6Xw-yeJA0Tunw", youtubeCategory);
|
||||||
|
await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UC1Et9K-hHf-P_LzQkE_Q3Jw", youtubeCategory);
|
||||||
|
await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UCsXVk37bltHxD1rDPwtNM8Q", youtubeCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
await AddSubscriptionAsync("http://www.digitaleoverheid.nl/feed/");
|
||||||
|
await AddSubscriptionAsync("http://www.digitaleoverheid.nl/agenda/feed/");
|
||||||
|
await AddSubscriptionAsync("https://feeds.rijksoverheid.nl/nieuws.rss");
|
||||||
|
await AddSubscriptionAsync("https://nl.wikipedia.org/w/index.php?title=Speciaal:RecenteWijzigingen&feed=atom");
|
||||||
|
await AddSubscriptionAsync("https://feeds.aivd.nl/nieuws.rss");
|
||||||
|
await AddSubscriptionAsync("https://blogs.microsoft.com/feed");
|
||||||
|
await AddSubscriptionAsync("https://www.europarl.europa.eu/rss/doc/top-stories/nl.xml");*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Argotic.Common;
|
using Argotic.Common;
|
||||||
using Argotic.Extensions.Core;
|
using Argotic.Extensions.Core;
|
||||||
using Argotic.Syndication;
|
using Argotic.Syndication;
|
||||||
|
@ -33,7 +34,7 @@ namespace SharpRss
|
||||||
{
|
{
|
||||||
Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl);
|
Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl);
|
||||||
}
|
}
|
||||||
return ConstructSyndicationContainer(syndicationFeed);
|
return ConstructSyndicationContainer(syndicationFeed, feedUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed)
|
public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed)
|
||||||
|
@ -46,7 +47,7 @@ namespace SharpRss
|
||||||
return memStream;
|
return memStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed)
|
private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string feedUrl)
|
||||||
{
|
{
|
||||||
SyndicationContainer container = new SyndicationContainer();
|
SyndicationContainer container = new SyndicationContainer();
|
||||||
if (syndicationFeed == null)
|
if (syndicationFeed == null)
|
||||||
|
@ -62,14 +63,14 @@ namespace SharpRss
|
||||||
case SyndicationContentFormat.Rss:
|
case SyndicationContentFormat.Rss:
|
||||||
RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource;
|
RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource;
|
||||||
if (rssFeed.Channel == null) break;
|
if (rssFeed.Channel == null) break;
|
||||||
container.FeedModel.OriginalUrl = rssFeed.Channel.SelfLink?.ToString() ?? string.Empty;
|
container.FeedModel.EncodedUrl = EncodeUrl(feedUrl);
|
||||||
container.FeedModel.Title = rssFeed.Channel.Title ?? string.Empty;
|
container.FeedModel.Title = rssFeed.Channel.Title ?? string.Empty;
|
||||||
container.FeedModel.FeedType = rssFeed.Format.ToString() ?? string.Empty;
|
container.FeedModel.FeedType = rssFeed.Format.ToString() ?? string.Empty;
|
||||||
container.FeedModel.FeedVersion = rssFeed.Version?.ToString() ?? string.Empty;
|
container.FeedModel.FeedVersion = rssFeed.Version?.ToString() ?? string.Empty;
|
||||||
container.FeedModel.Description = rssFeed.Channel.Description ?? string.Empty;
|
container.FeedModel.Description = rssFeed.Channel.Description ?? string.Empty;
|
||||||
container.FeedModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty;
|
container.FeedModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty;
|
||||||
container.FeedModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty;
|
container.FeedModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty;
|
||||||
container.FeedModel.PublicationDate = rssFeed.Channel.LastBuildDate is not { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.LastBuildDate) : DateTimeOffset.MinValue;
|
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.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty<string>();
|
||||||
container.FeedModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
|
container.FeedModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
|
||||||
foreach (var rssItem in rssFeed.Channel.Items)
|
foreach (var rssItem in rssFeed.Channel.Items)
|
||||||
|
@ -77,7 +78,7 @@ namespace SharpRss
|
||||||
FeedItemModel itemModel = new FeedItemModel()
|
FeedItemModel itemModel = new FeedItemModel()
|
||||||
{
|
{
|
||||||
Id = rssItem.Link?.ToString() ?? string.Empty,
|
Id = rssItem.Link?.ToString() ?? string.Empty,
|
||||||
FeedUrl = container.FeedModel.OriginalUrl ?? string.Empty,
|
EncodedFeedUrl = container.FeedModel.EncodedUrl ?? string.Empty,
|
||||||
Type = container.FeedModel.FeedType ?? string.Empty,
|
Type = container.FeedModel.FeedType ?? string.Empty,
|
||||||
Title = rssItem.Title ?? string.Empty,
|
Title = rssItem.Title ?? string.Empty,
|
||||||
Description = rssItem.Description ?? string.Empty,
|
Description = rssItem.Description ?? string.Empty,
|
||||||
|
@ -93,14 +94,14 @@ namespace SharpRss
|
||||||
break;
|
break;
|
||||||
case SyndicationContentFormat.Atom:
|
case SyndicationContentFormat.Atom:
|
||||||
AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource;
|
AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource;
|
||||||
container.FeedModel.OriginalUrl = atomFeed.Id?.Uri.ToString() ?? string.Empty;
|
container.FeedModel.EncodedUrl = EncodeUrl(feedUrl);
|
||||||
container.FeedModel.Title = atomFeed.Title?.Content ?? string.Empty;
|
container.FeedModel.Title = atomFeed.Title?.Content ?? string.Empty;
|
||||||
container.FeedModel.FeedType = atomFeed.Format.ToString() ?? string.Empty;
|
container.FeedModel.FeedType = atomFeed.Format.ToString() ?? string.Empty;
|
||||||
container.FeedModel.FeedVersion = atomFeed.Version?.ToString() ?? string.Empty;
|
container.FeedModel.FeedVersion = atomFeed.Version?.ToString() ?? string.Empty;
|
||||||
container.FeedModel.Description = atomFeed.Subtitle?.Content ?? string.Empty;
|
container.FeedModel.Description = atomFeed.Subtitle?.Content ?? string.Empty;
|
||||||
container.FeedModel.Language = atomFeed.Language?.ToString() ?? string.Empty;
|
container.FeedModel.Language = atomFeed.Language?.ToString() ?? string.Empty;
|
||||||
container.FeedModel.Copyright = atomFeed.Rights?.Content ?? string.Empty;
|
container.FeedModel.Copyright = atomFeed.Rights?.Content ?? string.Empty;
|
||||||
container.FeedModel.PublicationDate = atomFeed.UpdatedOn != null ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue;
|
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.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty<string>();
|
||||||
container.FeedModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty;
|
container.FeedModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty;
|
||||||
foreach (var entry in atomFeed.Entries)
|
foreach (var entry in atomFeed.Entries)
|
||||||
|
@ -108,7 +109,7 @@ namespace SharpRss
|
||||||
FeedItemModel itemModel = new FeedItemModel()
|
FeedItemModel itemModel = new FeedItemModel()
|
||||||
{
|
{
|
||||||
Id = entry.Id?.Uri.ToString() ?? string.Empty,
|
Id = entry.Id?.Uri.ToString() ?? string.Empty,
|
||||||
FeedUrl = container.FeedModel?.OriginalUrl ?? string.Empty,
|
EncodedFeedUrl = container.FeedModel?.EncodedUrl ?? string.Empty,
|
||||||
Type = container.FeedModel?.FeedType ?? string.Empty,
|
Type = container.FeedModel?.FeedType ?? string.Empty,
|
||||||
Title = entry.Title?.Content ?? string.Empty,
|
Title = entry.Title?.Content ?? string.Empty,
|
||||||
Description = entry.Summary?.Content ?? string.Empty,
|
Description = entry.Summary?.Content ?? string.Empty,
|
||||||
|
@ -129,7 +130,12 @@ namespace SharpRss
|
||||||
container.Fetched = true;
|
container.Fetched = true;
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
public static string EncodeUrl(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url));
|
||||||
|
public static string DecodeUrl(string base64)
|
||||||
|
{
|
||||||
|
byte[] bytes = Convert.FromBase64String(base64);
|
||||||
|
return Encoding.Default.GetString(bytes);
|
||||||
|
}
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
DbAccess.Initialize();
|
DbAccess.Initialize();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using SharpRss;
|
||||||
using SharpRss.Models;
|
using SharpRss.Models;
|
||||||
using ToolQit;
|
using ToolQit;
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ namespace WebSharpRSS.Models
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Link == null || _faviconUrl != null) return _faviconUrl;
|
if (Link == null || _faviconUrl != null) return _faviconUrl;
|
||||||
_faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(Link).Host);
|
_faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedFeedUrl)).Host);
|
||||||
return _faviconUrl;
|
return _faviconUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
using SharpRss;
|
||||||
using SharpRss.Models;
|
using SharpRss.Models;
|
||||||
using ToolQit;
|
using ToolQit;
|
||||||
|
|
||||||
|
@ -22,9 +23,10 @@ namespace WebSharpRSS.Models
|
||||||
{
|
{
|
||||||
FeedModel = feedModel;
|
FeedModel = feedModel;
|
||||||
Title = feedModel.Title ?? string.Empty;
|
Title = feedModel.Title ?? string.Empty;
|
||||||
if (FeedModel.OriginalUrl == null) return;
|
if (FeedModel.EncodedUrl == null) return;
|
||||||
FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(FeedModel.OriginalUrl).Host);
|
FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(FeedModel.EncodedUrl)).Host);
|
||||||
}
|
TotalItems = FeedModel.ItemCount;
|
||||||
|
}
|
||||||
public readonly CategoryModel? CategoryModel;
|
public readonly CategoryModel? CategoryModel;
|
||||||
public readonly FeedModel? FeedModel;
|
public readonly FeedModel? FeedModel;
|
||||||
|
|
||||||
|
@ -36,5 +38,6 @@ namespace WebSharpRSS.Models
|
||||||
public bool HasChildren { get; set; }
|
public bool HasChildren { get; set; }
|
||||||
public bool Loading { get; set; }
|
public bool Loading { get; set; }
|
||||||
public bool IsExpanded { get; set; }
|
public bool IsExpanded { get; set; }
|
||||||
|
public int TotalItems { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -57,7 +57,7 @@
|
||||||
else if (Cid != null)
|
else if (Cid != null)
|
||||||
{
|
{
|
||||||
var feeds = await _rssService.GetFeedsAsync(Cid == string.Empty ? null : Cid);
|
var feeds = await _rssService.GetFeedsAsync(Cid == string.Empty ? null : Cid);
|
||||||
var feedIds = feeds.Select(x => x.OriginalUrl);
|
var feedIds = feeds.Select(x => x.EncodedUrl);
|
||||||
var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedIds.ToArray());
|
var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedIds.ToArray());
|
||||||
items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
|
items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
6
WebSharpRSS/Settings.json
Normal file
6
WebSharpRSS/Settings.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"Paths": {
|
||||||
|
"FaviconResolveUrl": "http://www.google.com/s2/favicons?domain={0}",
|
||||||
|
"LogPath": "/home/max/GitHub/SharpRSS/WebSharpRSS/logs/log_.json"
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,7 @@
|
||||||
if (_selectedItem == null) return;
|
if (_selectedItem == null) return;
|
||||||
if (_selectedItem.FeedModel != null)
|
if (_selectedItem.FeedModel != null)
|
||||||
{
|
{
|
||||||
_navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.OriginalUrl}");
|
_navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.EncodedUrl}");
|
||||||
}
|
}
|
||||||
else if (_selectedItem.CategoryModel != null)
|
else if (_selectedItem.CategoryModel != null)
|
||||||
{
|
{
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
{
|
{
|
||||||
Log.Verbose("Loading guide data...");
|
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 }));
|
_guideItems.Add(new TreeItemData(new CategoryModel() { Name = "All", Icon = Icons.Material.Filled.Home, HexColor = Colors.Blue.Accent1, Id = string.Empty }));
|
||||||
HashSet<object> items = await _rssService.GetCategoriesFeedsAsync();
|
HashSet<object> items = await _rssService.GetCategoriesAndFeedsAsync();
|
||||||
_guideItems.UnionWith(ModelToTreeItem(items));
|
_guideItems.UnionWith(ModelToTreeItem(items));
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user