Start adding Miniflux support plus other cleanup

- Modernize old async code
- Update to .NET 10
- Adjust namespace
- Bypass update check when debugging
This commit is contained in:
2025-11-13 10:33:56 -05:00
parent cdd22c6632
commit 6bae35a255
56 changed files with 560 additions and 326 deletions

View File

@@ -1,10 +1,11 @@
using Realms;
using System;
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Realms;
namespace FeedCenter;
namespace FeedCenter.Feeds;
public class Account : RealmObject, INotifyDataErrorInfo
{
@@ -37,6 +38,7 @@ public class Account : RealmObject, INotifyDataErrorInfo
{
AccountType.Fever => false,
AccountType.GoogleReader => false,
AccountType.Miniflux => true,
AccountType.Local => true,
_ => throw new NotSupportedException()
};
@@ -45,6 +47,7 @@ public class Account : RealmObject, INotifyDataErrorInfo
{
AccountType.Fever => false,
AccountType.GoogleReader => false,
AccountType.Miniflux => true,
AccountType.Local => true,
_ => throw new NotSupportedException()
};
@@ -158,21 +161,22 @@ public class Account : RealmObject, INotifyDataErrorInfo
return new Account { Name = DefaultName, Type = AccountType.Local };
}
public int GetProgressSteps(FeedCenterEntities entities)
public async Task<int> GetProgressSteps(Account account, AccountReadInput accountReadInput)
{
var progressSteps = Type switch
{
// Delegate to the right reader based on the account type
AccountType.Fever => new FeverReader().GetProgressSteps(entities),
AccountType.GoogleReader => new GoogleReaderReader().GetProgressSteps(entities),
AccountType.Local => new LocalReader().GetProgressSteps(entities),
AccountType.Fever => await new FeverReader(account).GetProgressSteps(accountReadInput),
AccountType.GoogleReader => await new GoogleReaderReader(account).GetProgressSteps(accountReadInput),
AccountType.Miniflux => await new MinifluxReader(account).GetProgressSteps(accountReadInput),
AccountType.Local => await new LocalReader(account).GetProgressSteps(accountReadInput),
_ => throw new NotSupportedException()
};
return progressSteps;
}
public AccountReadResult Read(AccountReadInput accountReadInput)
public async Task<AccountReadResult> Read(AccountReadInput accountReadInput)
{
// If not enabled then do nothing
if (!Enabled)
@@ -189,14 +193,25 @@ public class Account : RealmObject, INotifyDataErrorInfo
return AccountReadResult.NotDue;
}
var accountReadResult = Type switch
AccountReadResult accountReadResult;
switch (Type)
{
// Delegate to the right reader based on the account type
AccountType.Fever => new FeverReader().Read(this, accountReadInput),
AccountType.GoogleReader => new GoogleReaderReader().Read(this, accountReadInput),
AccountType.Local => new LocalReader().Read(this, accountReadInput),
_ => throw new NotSupportedException()
};
case AccountType.Fever:
accountReadResult = await new FeverReader(this).Read(accountReadInput);
break;
case AccountType.GoogleReader:
accountReadResult = await new GoogleReaderReader(this).Read(accountReadInput);
break;
case AccountType.Miniflux:
accountReadResult = await new MinifluxReader(this).Read(accountReadInput);
break;
case AccountType.Local:
accountReadResult = await new LocalReader(this).Read(accountReadInput);
break;
default:
throw new NotSupportedException();
}
return accountReadResult;
}

View File

@@ -1,6 +1,6 @@
using System;
namespace FeedCenter;
namespace FeedCenter.Feeds;
public class AccountReadInput(FeedCenterEntities entities, Guid? feedId, bool forceRead, Action incrementProgress)
{

View File

@@ -1,4 +1,4 @@
namespace FeedCenter;
namespace FeedCenter.Feeds;
public enum AccountReadResult
{

View File

@@ -1,8 +1,9 @@
namespace FeedCenter;
namespace FeedCenter.Feeds;
public enum AccountType
{
Local,
Fever,
GoogleReader
GoogleReader,
Miniflux
}

View File

@@ -1,11 +1,11 @@
using JetBrains.Annotations;
using Realms;
using System;
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using JetBrains.Annotations;
using Realms;
namespace FeedCenter;
namespace FeedCenter.Feeds;
public class Category : RealmObject, INotifyDataErrorInfo
{

View File

@@ -3,7 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
namespace FeedCenter;
namespace FeedCenter.Feeds;
internal class DataErrorDictionary : Dictionary<string, List<string>>
{

View File

@@ -19,7 +19,7 @@ using JetBrains.Annotations;
using Realms;
using Serilog;
namespace FeedCenter;
namespace FeedCenter.Feeds;
public partial class Feed : RealmObject, INotifyDataErrorInfo
{

View File

@@ -1,12 +1,12 @@
using FeedCenter.Options;
using Realms;
using System;
using System;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FeedCenter.Options;
using Realms;
namespace FeedCenter;
namespace FeedCenter.Feeds;
public partial class FeedItem : RealmObject
{
@@ -90,7 +90,12 @@ public partial class FeedItem : RealmObject
{
case AccountType.Fever:
// Delegate to the right reader based on the account type
await FeverReader.MarkFeedItemRead(feed.Account, RemoteId);
await new FeverReader(feed.Account).MarkFeedItemRead(RemoteId);
break;
case AccountType.Miniflux:
// Delegate to the right reader based on the account type
await new MinifluxReader(feed.Account).MarkFeedItemRead(RemoteId);
break;
case AccountType.Local:

View File

@@ -1,4 +1,4 @@
namespace FeedCenter;
namespace FeedCenter.Feeds;
public enum FeedReadResult
{

View File

@@ -1,4 +1,4 @@
namespace FeedCenter;
namespace FeedCenter.Feeds;
public enum FeedType
{

View File

@@ -1,20 +1,26 @@
using System;
using ChrisKaczor.FeverClient;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ChrisKaczor.FeverClient;
namespace FeedCenter;
namespace FeedCenter.Feeds;
internal class FeverReader : IAccountReader
internal class FeverReader(Account account) : IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities)
public async Task<int> GetProgressSteps(AccountReadInput accountReadInput)
{
return 7;
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
var feverClient = new FeverClient(account.Url, apiKey);
var feeds = await feverClient.GetFeeds();
return feeds.Count() * 2 + 5;
}
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
public async Task<AccountReadResult> Read(AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
@@ -24,11 +30,11 @@ internal class FeverReader : IAccountReader
accountReadInput.IncrementProgress();
var feverFeeds = feverClient.GetFeeds().Result.ToList();
var feverFeeds = (await feverClient.GetFeeds()).ToList();
accountReadInput.IncrementProgress();
var allFeverFeedItems = feverClient.GetAllFeedItems().Result.ToList();
var allFeverFeedItems = (await feverClient.GetAllFeedItems()).ToList();
accountReadInput.IncrementProgress();
@@ -120,14 +126,14 @@ internal class FeverReader : IAccountReader
account.LastChecked = checkTime;
transaction.Commit();
await transaction.CommitAsync();
accountReadInput.IncrementProgress();
return AccountReadResult.Success;
}
public static async Task MarkFeedItemRead(Account account, string feedItemId)
public async Task MarkFeedItemRead(string feedItemId)
{
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;

View File

@@ -1,19 +1,16 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace FeedCenter;
namespace FeedCenter.Feeds;
internal class GoogleReaderReader : IAccountReader
internal class GoogleReaderReader(Account account) : IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities)
public Task<int> GetProgressSteps(AccountReadInput accountReadInput)
{
return 7;
return Task.FromResult(7);
}
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
public async Task<AccountReadResult> Read(AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
@@ -119,20 +116,22 @@ internal class GoogleReaderReader : IAccountReader
account.LastChecked = checkTime;
transaction.Commit();
await transaction.CommitAsync();
accountReadInput.IncrementProgress();
return AccountReadResult.Success;
}
public static async Task MarkFeedItemRead(Account account, string feedItemId)
public Task MarkFeedItemRead(string feedItemId)
{
//var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
//var feverClient = new FeverClient.FeverClient(account.Url, apiKey);
//await feverClient.MarkFeedItemAsRead(int.Parse(feedItemId));
return Task.CompletedTask;
}
//private static string GetApiKey(Account account)

View File

@@ -1,7 +1,10 @@
namespace FeedCenter;
using System.Threading.Tasks;
namespace FeedCenter.Feeds;
public interface IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities);
public AccountReadResult Read(Account account, AccountReadInput accountReadInput);
public Task<int> GetProgressSteps(AccountReadInput accountReadInput);
public Task<AccountReadResult> Read(AccountReadInput accountReadInput);
public Task MarkFeedItemRead(string feedItemId);
}

View File

@@ -1,19 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace FeedCenter;
namespace FeedCenter.Feeds;
public class LocalReader : IAccountReader
public class LocalReader(Account account) : IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities)
public Task<int> GetProgressSteps(AccountReadInput accountReadInput)
{
var enabledFeedCount = entities.Feeds.Count(f => f.Account.Type == AccountType.Local && f.Enabled);
var enabledFeedCount = accountReadInput.Entities.Feeds.Count(f => f.Account.Type == AccountType.Local && f.Enabled);
return enabledFeedCount;
return Task.FromResult(enabledFeedCount);
}
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
public Task<AccountReadResult> Read(AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
@@ -37,6 +38,11 @@ public class LocalReader : IAccountReader
accountReadInput.Entities.SaveChanges(() => account.LastChecked = checkTime);
return AccountReadResult.Success;
return Task.FromResult(AccountReadResult.Success);
}
public Task MarkFeedItemRead(string feedItemId)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ChrisKaczor.MinifluxClient;
namespace FeedCenter.Feeds;
internal class MinifluxReader(Account account) : IAccountReader
{
public async Task<int> GetProgressSteps(AccountReadInput accountReadInput)
{
var minifluxClient = new MinifluxClient(account.Url, account.Password);
int feedCount;
if (accountReadInput.FeedId.HasValue)
{
feedCount = 1;
}
else
{
var feeds = await minifluxClient.GetFeeds();
feedCount = feeds.Count();
}
return feedCount * 2 + 4;
}
public async Task<AccountReadResult> Read(AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
var minifluxClient = new MinifluxClient(account.Url, account.Password);
accountReadInput.IncrementProgress();
var localFeeds = accountReadInput.Entities.Feeds.ToList();
var remoteFeeds = (await minifluxClient.GetFeeds()).ToList();
if (accountReadInput.FeedId.HasValue)
{
localFeeds = localFeeds.Where(f => f.Id == accountReadInput.FeedId.Value).ToList();
remoteFeeds = remoteFeeds.Where(rf => rf.Id.ToString() == localFeeds.First().RemoteId).ToList();
}
accountReadInput.IncrementProgress();
var transaction = accountReadInput.Entities.BeginTransaction();
foreach (var remoteFeed in remoteFeeds)
{
var feed = accountReadInput.Entities.Feeds.FirstOrDefault(f => f.RemoteId == remoteFeed.Id.ToString() && f.Account.Id == account.Id);
if (feed == null)
{
feed = new Feed
{
Id = Guid.NewGuid(),
RemoteId = remoteFeed.Id.ToString(),
Title = remoteFeed.Title,
Source = remoteFeed.FeedUrl,
Link = remoteFeed.SiteUrl,
Account = account,
Name = remoteFeed.Title,
CategoryId = accountReadInput.Entities.DefaultCategory.Id,
Enabled = true,
CheckInterval = 0,
};
accountReadInput.Entities.Feeds.Add(feed);
}
feed.Name = remoteFeed.Title;
feed.Title = remoteFeed.Title;
feed.Link = remoteFeed.SiteUrl;
feed.Source = remoteFeed.FeedUrl;
feed.LastReadResult = FeedReadResult.Success;
feed.LastChecked = checkTime;
accountReadInput.IncrementProgress();
var sequence = 1;
var remoteFeedItems = (await minifluxClient.GetFeedEntries(remoteFeed.Id, SortField.PublishedAt, SortDirection.Descending)).ToList();
foreach (var remoteFeedItem in remoteFeedItems)
{
var feedItem = feed.Items.FirstOrDefault(f => f.RemoteId == remoteFeedItem.Id.ToString());
if (feedItem == null)
{
feedItem = new FeedItem
{
Id = Guid.NewGuid(),
RemoteId = remoteFeedItem.Id.ToString(),
Title = remoteFeedItem.Title,
Link = remoteFeedItem.Url,
Description = remoteFeedItem.Content,
BeenRead = remoteFeedItem.Status == "read",
FeedId = feed.Id,
Guid = Guid.NewGuid().ToString(),
Sequence = sequence++,
};
feed.Items.Add(feedItem);
}
feedItem.LastFound = checkTime;
feedItem.BeenRead = remoteFeedItem.Status == "read";
feedItem.Sequence = sequence++;
}
accountReadInput.IncrementProgress();
var feedItemsNotSeen = feed.Items.Where(fi => fi.LastFound != checkTime).ToList();
foreach (var feedItemNotSeen in feedItemsNotSeen)
{
feed.Items.Remove(feedItemNotSeen);
}
}
accountReadInput.IncrementProgress();
var feedsNotSeen = localFeeds.Where(f => f.Account.Id == account.Id && f.LastChecked != checkTime).ToList();
foreach (var feedNotSeen in feedsNotSeen)
{
accountReadInput.Entities.Feeds.Remove(feedNotSeen);
}
account.LastChecked = checkTime;
await transaction.CommitAsync();
accountReadInput.IncrementProgress();
return AccountReadResult.Success;
}
public async Task MarkFeedItemRead(string feedItemId)
{
var minifluxClient = new MinifluxClient(account.Url, account.Password);
await minifluxClient.MarkFeedEntries([long.Parse(feedItemId)], FeedEntryStatus.Read);
}
}

View File

@@ -1,4 +1,4 @@
namespace FeedCenter;
namespace FeedCenter.Feeds;
public enum MultipleOpenAction
{