mirror of
https://github.com/ckaczor/FeedCenter.git
synced 2026-01-14 01:25:38 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9717e2d6af | |||
| 7ace91684c | |||
| c186de0394 | |||
| 41d0b055dd | |||
| 2d32d740a4 | |||
| 5e7fa4a2e0 | |||
| 66ea567eaa | |||
| 6bae35a255 | |||
| cdd22c6632 | |||
| b68f794e7d | |||
| 4291d301cc |
@@ -1,10 +1,12 @@
|
||||
using Realms;
|
||||
using FeedCenter.Feeds;
|
||||
using Realms;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
public class Account : RealmObject, INotifyDataErrorInfo
|
||||
{
|
||||
@@ -24,6 +26,15 @@ public class Account : RealmObject, INotifyDataErrorInfo
|
||||
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
|
||||
}
|
||||
|
||||
private IAccountReader _accountReader;
|
||||
|
||||
private IAccountReader GetAccountReader()
|
||||
{
|
||||
_accountReader ??= AccountReaderFactory.CreateAccountReader(this);
|
||||
|
||||
return _accountReader;
|
||||
}
|
||||
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
@@ -33,21 +44,9 @@ public class Account : RealmObject, INotifyDataErrorInfo
|
||||
set => TypeRaw = value.ToString();
|
||||
}
|
||||
|
||||
public bool SupportsFeedEdit => Type switch
|
||||
{
|
||||
AccountType.Fever => false,
|
||||
AccountType.GoogleReader => false,
|
||||
AccountType.Local => true,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
public bool SupportsFeedEdit => GetAccountReader().SupportsFeedEdit;
|
||||
|
||||
public bool SupportsFeedDelete => Type switch
|
||||
{
|
||||
AccountType.Fever => false,
|
||||
AccountType.GoogleReader => false,
|
||||
AccountType.Local => true,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
public bool SupportsFeedDelete => GetAccountReader().SupportsFeedDelete;
|
||||
|
||||
private string TypeRaw { get; set; }
|
||||
|
||||
@@ -158,21 +157,14 @@ public class Account : RealmObject, INotifyDataErrorInfo
|
||||
return new Account { Name = DefaultName, Type = AccountType.Local };
|
||||
}
|
||||
|
||||
public int GetProgressSteps(FeedCenterEntities entities)
|
||||
public async Task<int> GetProgressSteps(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),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
var progressSteps = await GetAccountReader().GetProgressSteps(accountReadInput);
|
||||
|
||||
return progressSteps;
|
||||
}
|
||||
|
||||
public AccountReadResult Read(AccountReadInput accountReadInput)
|
||||
public async Task<AccountReadResult> Read(AccountReadInput accountReadInput)
|
||||
{
|
||||
// If not enabled then do nothing
|
||||
if (!Enabled)
|
||||
@@ -189,14 +181,7 @@ public class Account : RealmObject, INotifyDataErrorInfo
|
||||
return AccountReadResult.NotDue;
|
||||
}
|
||||
|
||||
var accountReadResult = Type switch
|
||||
{
|
||||
// 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()
|
||||
};
|
||||
var accountReadResult = await GetAccountReader().Read(accountReadInput);
|
||||
|
||||
return accountReadResult;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
public class AccountReadInput(FeedCenterEntities entities, Guid? feedId, bool forceRead, Action incrementProgress)
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
public enum AccountReadResult
|
||||
{
|
||||
16
Application/Accounts/AccountReaderFactory.cs
Normal file
16
Application/Accounts/AccountReaderFactory.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
internal static class AccountReaderFactory
|
||||
{
|
||||
internal static IAccountReader CreateAccountReader(Account account) =>
|
||||
account.Type switch
|
||||
{
|
||||
AccountType.Miniflux => new MinifluxReader(account),
|
||||
AccountType.Local => new LocalReader(account),
|
||||
AccountType.Fever => new FeverReader(account),
|
||||
AccountType.GoogleReader => new GoogleReaderReader(account),
|
||||
_ => throw new NotSupportedException($"Account type '{account.Type}' is not supported."),
|
||||
};
|
||||
}
|
||||
9
Application/Accounts/AccountType.cs
Normal file
9
Application/Accounts/AccountType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
public enum AccountType
|
||||
{
|
||||
Local,
|
||||
Fever,
|
||||
GoogleReader,
|
||||
Miniflux
|
||||
}
|
||||
@@ -1,20 +1,30 @@
|
||||
using System;
|
||||
using ChrisKaczor.FeverClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ChrisKaczor.FeverClient;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
internal class FeverReader : IAccountReader
|
||||
using FeverFeedItem = ChrisKaczor.FeverClient.Models.FeedItem;
|
||||
|
||||
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,19 +34,21 @@ 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 GetAllFeverFeedItems(feverClient);
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var existingFeedsByRemoteId = accountReadInput.Entities.Feeds.Where(f => f.Account.Id == account.Id) .ToDictionary(f => f.RemoteId);
|
||||
|
||||
var transaction = accountReadInput.Entities.BeginTransaction();
|
||||
|
||||
foreach (var feverFeed in feverFeeds)
|
||||
{
|
||||
var feed = accountReadInput.Entities.Feeds.FirstOrDefault(f => f.RemoteId == feverFeed.Id.ToString() && f.Account.Id == account.Id);
|
||||
var feed = existingFeedsByRemoteId.GetValueOrDefault(feverFeed.Id.ToString(), null);
|
||||
|
||||
if (feed == null)
|
||||
{
|
||||
@@ -66,15 +78,15 @@ internal class FeverReader : IAccountReader
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var feverFeedItems = allFeverFeedItems
|
||||
.Where(f => f.FeedId == feverFeed.Id)
|
||||
.OrderByDescending(fi => fi.CreatedOnTime).ToList();
|
||||
var feverFeedItems = allFeverFeedItems.GetValueOrDefault(feverFeed.Id, []);
|
||||
|
||||
var existingFeedItemsByRemoteId = feed.Items.ToDictionary(fi => fi.RemoteId);
|
||||
|
||||
var sequence = 1;
|
||||
|
||||
foreach (var feverFeedItem in feverFeedItems)
|
||||
{
|
||||
var feedItem = feed.Items.FirstOrDefault(f => f.RemoteId == feverFeedItem.Id.ToString());
|
||||
var feedItem = existingFeedItemsByRemoteId.GetValueOrDefault(feverFeedItem.Id.ToString(), null);
|
||||
|
||||
if (feedItem == null)
|
||||
{
|
||||
@@ -120,14 +132,28 @@ 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)
|
||||
private static async Task<Dictionary<int, List<FeverFeedItem>>> GetAllFeverFeedItems(FeverClient feverClient)
|
||||
{
|
||||
var allFeverFeedItems = new List<FeverFeedItem>();
|
||||
|
||||
await foreach (var page in feverClient.GetAllFeedItems())
|
||||
{
|
||||
allFeverFeedItems.AddRange(page);
|
||||
}
|
||||
|
||||
var grouped = allFeverFeedItems.OrderByDescending(fi => fi.CreatedOnTime).GroupBy(fi => fi.FeedId);
|
||||
|
||||
return grouped.ToDictionary(g => g.Key, g => g.ToList());
|
||||
}
|
||||
|
||||
public async Task MarkFeedItemRead(string feedItemId)
|
||||
{
|
||||
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
|
||||
|
||||
@@ -136,6 +162,10 @@ internal class FeverReader : IAccountReader
|
||||
await feverClient.MarkFeedItemAsRead(int.Parse(feedItemId));
|
||||
}
|
||||
|
||||
public bool SupportsFeedDelete => false;
|
||||
|
||||
public bool SupportsFeedEdit => false;
|
||||
|
||||
private static string GetApiKey(Account account)
|
||||
{
|
||||
var input = $"{account.Username}:{account.Password}";
|
||||
@@ -1,19 +1,16 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
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,22 +116,28 @@ 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;
|
||||
}
|
||||
|
||||
public bool SupportsFeedDelete => false;
|
||||
|
||||
public bool SupportsFeedEdit => false;
|
||||
|
||||
//private static string GetApiKey(Account account)
|
||||
//{
|
||||
// var input = $"{account.Username}:{account.Password}";
|
||||
12
Application/Accounts/IAccountReader.cs
Normal file
12
Application/Accounts/IAccountReader.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
public interface IAccountReader
|
||||
{
|
||||
public Task<int> GetProgressSteps(AccountReadInput accountReadInput);
|
||||
public Task<AccountReadResult> Read(AccountReadInput accountReadInput);
|
||||
public Task MarkFeedItemRead(string feedItemId);
|
||||
public bool SupportsFeedDelete { get; }
|
||||
public bool SupportsFeedEdit { get; }
|
||||
}
|
||||
@@ -1,19 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
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 +39,15 @@ 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();
|
||||
}
|
||||
|
||||
public bool SupportsFeedDelete => true;
|
||||
|
||||
public bool SupportsFeedEdit => true;
|
||||
}
|
||||
151
Application/Accounts/MinifluxReader.cs
Normal file
151
Application/Accounts/MinifluxReader.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ChrisKaczor.MinifluxClient;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.Accounts;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public bool SupportsFeedDelete => true;
|
||||
|
||||
public bool SupportsFeedEdit => true;
|
||||
}
|
||||
@@ -35,8 +35,8 @@ public partial class App
|
||||
var app = new App();
|
||||
app.InitializeComponent();
|
||||
|
||||
// Create an single instance handle to see if we are already running
|
||||
var isolationHandle = SingleInstance.GetSingleInstanceHandleAsync(Name).Result;
|
||||
// Create a single instance handle to see if we are already running
|
||||
var isolationHandle = SingleInstance.GetSingleInstanceHandleAsync(Name).GetAwaiter().GetResult();
|
||||
|
||||
// If there is another copy then pass it the command line and exit
|
||||
if (isolationHandle == null)
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.Data;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ using FeedCenter.Options;
|
||||
using Realms;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using FeedCenter.Accounts;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
@@ -15,10 +17,17 @@ public class FeedCenterEntities
|
||||
SchemaVersion = 2,
|
||||
MigrationCallback = (migration, oldSchemaVersion) =>
|
||||
{
|
||||
if (oldSchemaVersion == 1)
|
||||
migration.NewRealm.Add(Account.CreateDefault());
|
||||
Account localAccount;
|
||||
|
||||
var localAccount = migration.NewRealm.All<Account>().First(a => a.Type == AccountType.Local);
|
||||
if (oldSchemaVersion == 1)
|
||||
{
|
||||
localAccount = Account.CreateDefault();
|
||||
migration.NewRealm.Add(localAccount);
|
||||
}
|
||||
else
|
||||
{
|
||||
localAccount = migration.NewRealm.All<Account>().First(a => a.Type == AccountType.Local);
|
||||
}
|
||||
|
||||
var newVersionCategories = migration.NewRealm.All<Category>();
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-windows8.0</TargetFramework>
|
||||
<TargetFramework>net10.0-windows8.0</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<UseWindowsForms>false</UseWindowsForms>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
<RestoreEnablePackagePruning>false</RestoreEnablePackagePruning>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<EnableDefaultApplicationDefinition>false</EnableDefaultApplicationDefinition>
|
||||
@@ -25,9 +26,10 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ChrisKaczor.ApplicationUpdate" Version="1.0.7" />
|
||||
<PackageReference Include="ChrisKaczor.FeverClient" Version="1.0.1" />
|
||||
<PackageReference Include="ChrisKaczor.FeverClient" Version="1.0.3" />
|
||||
<PackageReference Include="ChrisKaczor.GenericSettingsProvider" Version="1.0.4" />
|
||||
<PackageReference Include="ChrisKaczor.InstalledBrowsers" Version="1.0.4" />
|
||||
<PackageReference Include="ChrisKaczor.MinifluxClient" Version="1.0.3" />
|
||||
<PackageReference Include="ChrisKaczor.Wpf.Application.SingleInstance" Version="1.0.5" />
|
||||
<PackageReference Include="ChrisKaczor.Wpf.Application.StartWithWindows" Version="1.0.5" />
|
||||
<PackageReference Include="ChrisKaczor.Wpf.Controls.HtmlTextBlock" Version="1.0.6" />
|
||||
@@ -38,23 +40,24 @@
|
||||
<PackageReference Include="ChrisKaczor.Wpf.Windows.SnappingWindow" Version="1.0.4" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="DebounceThrottle" Version="3.0.1" />
|
||||
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.3" />
|
||||
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.3.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageReference Include="HtmlTextWriter" Version="3.0.2" />
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.11" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Compact" Version="4.0.8876.1" GeneratePathProperty="true">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="10.0.0" />
|
||||
<PackageReference Include="NameBasedGrid" Version="1.0.0">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Application.ico" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=Feeds/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=feeds/@EntryIndexedValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mainwindow/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -4,4 +4,5 @@
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kaczor/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kaczor/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Miniflux/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:linkControl="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
|
||||
xmlns:controlBox="clr-namespace:ChrisKaczor.Wpf.Windows;assembly=ChrisKaczor.Wpf.Windows.ControlBox" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:feedCenter="clr-namespace:FeedCenter"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
mc:Ignorable="d"
|
||||
x:Class="FeedCenter.FeedErrorWindow"
|
||||
Title="{x:Static my:Resources.FeedErrorWindow}"
|
||||
@@ -46,7 +47,7 @@
|
||||
HeadersVisibility="Column"
|
||||
Background="{x:Null}"
|
||||
CanUserSortColumns="True"
|
||||
d:DataContext="{d:DesignInstance Type=feedCenter:Feed}" SelectionChanged="FeedDataGrid_SelectionChanged">
|
||||
d:DataContext="{d:DesignInstance Type=feeds:Feed}" SelectionChanged="FeedDataGrid_SelectionChanged">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{x:Static my:Resources.FeedNameColumnHeader}"
|
||||
Binding="{Binding Name}"
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using ChrisKaczor.InstalledBrowsers;
|
||||
using FeedCenter.Feeds;
|
||||
using FeedCenter.Options;
|
||||
using FeedCenter.Properties;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Xml;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.FeedParsers;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.FeedParsers;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using FeedCenter.Xml;
|
||||
using Serilog;
|
||||
using System.Xml;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.FeedParsers;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Xml;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.FeedParsers;
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public enum AccountType
|
||||
{
|
||||
Local,
|
||||
Fever,
|
||||
GoogleReader
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using JetBrains.Annotations;
|
||||
using Realms;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using FeedCenter.Accounts;
|
||||
using JetBrains.Annotations;
|
||||
using Realms;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Feeds;
|
||||
|
||||
public class Category : RealmObject, INotifyDataErrorInfo
|
||||
{
|
||||
|
||||
@@ -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>>
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using ChrisKaczor.ApplicationUpdate;
|
||||
using FeedCenter.Accounts;
|
||||
using FeedCenter.FeedParsers;
|
||||
using FeedCenter.Properties;
|
||||
using FeedCenter.Xml;
|
||||
@@ -19,7 +20,7 @@ using JetBrains.Annotations;
|
||||
using Realms;
|
||||
using Serilog;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Feeds;
|
||||
|
||||
public partial class Feed : RealmObject, INotifyDataErrorInfo
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using FeedCenter.Options;
|
||||
using Realms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using FeedCenter.Accounts;
|
||||
using FeedCenter.Options;
|
||||
using Realms;
|
||||
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Feeds;
|
||||
|
||||
public partial class FeedItem : RealmObject
|
||||
{
|
||||
@@ -86,19 +86,7 @@ public partial class FeedItem : RealmObject
|
||||
if (feed == null || feed.Account.Type == AccountType.Local)
|
||||
return;
|
||||
|
||||
switch (feed.Account.Type)
|
||||
{
|
||||
case AccountType.Fever:
|
||||
// Delegate to the right reader based on the account type
|
||||
await FeverReader.MarkFeedItemRead(feed.Account, RemoteId);
|
||||
|
||||
break;
|
||||
case AccountType.Local:
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
break;
|
||||
}
|
||||
await AccountReaderFactory.CreateAccountReader(feed.Account).MarkFeedItemRead(RemoteId);
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\n")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Feeds;
|
||||
|
||||
public enum FeedReadResult
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Feeds;
|
||||
|
||||
public enum FeedType
|
||||
{
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public interface IAccountReader
|
||||
{
|
||||
public int GetProgressSteps(FeedCenterEntities entities);
|
||||
public AccountReadResult Read(Account account, AccountReadInput accountReadInput);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace FeedCenter;
|
||||
namespace FeedCenter.Feeds;
|
||||
|
||||
public enum MultipleOpenAction
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using FeedCenter.Properties;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
@@ -38,7 +37,7 @@ public partial class MainWindow
|
||||
return;
|
||||
|
||||
// Get the feed item
|
||||
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
|
||||
var feedItem = (Feeds.FeedItem) ((ListBoxItem) sender).DataContext;
|
||||
|
||||
// Remove the item from the list
|
||||
LinkTextList.Items.Remove(feedItem);
|
||||
@@ -50,7 +49,7 @@ public partial class MainWindow
|
||||
private async void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// Get the feed item
|
||||
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
|
||||
var feedItem = (Feeds.FeedItem) ((ListBoxItem) sender).DataContext;
|
||||
|
||||
// Try to open the item link
|
||||
if (!InstalledBrowser.OpenLink(Settings.Default.Browser, feedItem.Link))
|
||||
@@ -104,7 +103,7 @@ public partial class MainWindow
|
||||
var menuItem = (MenuItem) sender;
|
||||
|
||||
// Get the feed from the menu item tab
|
||||
var feed = (Feed) menuItem.Tag;
|
||||
var feed = (Feeds.Feed) menuItem.Tag;
|
||||
|
||||
// Loop over all feeds and look for the index of the new one
|
||||
var feedIndex = 0;
|
||||
|
||||
@@ -1,38 +1,20 @@
|
||||
using ChrisKaczor.ApplicationUpdate;
|
||||
using FeedCenter.Feeds;
|
||||
using FeedCenter.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using FeedCenter.Accounts;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
public partial class MainWindow
|
||||
{
|
||||
private BackgroundWorker _feedReadWorker;
|
||||
|
||||
private class FeedReadWorkerInput
|
||||
{
|
||||
public bool ForceRead { get; }
|
||||
public Guid? FeedId { get; }
|
||||
|
||||
public FeedReadWorkerInput()
|
||||
{
|
||||
}
|
||||
|
||||
public FeedReadWorkerInput(bool forceRead)
|
||||
{
|
||||
ForceRead = forceRead;
|
||||
}
|
||||
|
||||
public FeedReadWorkerInput(bool forceRead, Guid? feedId)
|
||||
{
|
||||
ForceRead = forceRead;
|
||||
FeedId = feedId;
|
||||
}
|
||||
}
|
||||
private bool _reading;
|
||||
|
||||
private void SetProgressMode(bool showProgress, int maximum)
|
||||
{
|
||||
@@ -49,55 +31,72 @@ public partial class MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCurrentFeed(bool forceRead = false)
|
||||
private async void ReadCurrentFeed(bool forceRead = false)
|
||||
{
|
||||
// Don't read if we're already working
|
||||
if (_feedReadWorker.IsBusy)
|
||||
return;
|
||||
try
|
||||
{
|
||||
// Don't read if we're already working
|
||||
if (_reading)
|
||||
return;
|
||||
|
||||
// Don't read if there is nothing to read
|
||||
if (!_database.Feeds.Any())
|
||||
return;
|
||||
_reading = true;
|
||||
|
||||
// Switch to progress mode
|
||||
SetProgressMode(true, 1);
|
||||
// Don't read if there is nothing to read
|
||||
if (!_database.Feeds.Any())
|
||||
return;
|
||||
|
||||
// Create the input class
|
||||
var workerInput = new FeedReadWorkerInput(forceRead, _currentFeed.Id);
|
||||
var accountReadInput = new AccountReadInput(_database, _currentFeed.Id, forceRead, () => { });
|
||||
|
||||
// Start the worker
|
||||
_feedReadWorker.RunWorkerAsync(workerInput);
|
||||
// Switch to progress mode
|
||||
SetProgressMode(true, await _currentFeed.Account.GetProgressSteps(accountReadInput));
|
||||
|
||||
// Start reading
|
||||
await HandleFeedReadWorkerStart(forceRead, _currentFeed.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadFeeds(bool forceRead = false)
|
||||
private async Task ReadFeeds(bool forceRead = false)
|
||||
{
|
||||
// Don't read if we're already working
|
||||
if (_feedReadWorker.IsBusy)
|
||||
if (_reading)
|
||||
return;
|
||||
|
||||
_reading = true;
|
||||
|
||||
// Don't read if there is nothing to read
|
||||
if (!_database.Accounts.Any())
|
||||
return;
|
||||
|
||||
var progressSteps = _database.Accounts.Sum(a => a.GetProgressSteps(_database));
|
||||
var accountReadInput = new AccountReadInput(_database, null, forceRead, () => { });
|
||||
|
||||
// Calculate total progress steps
|
||||
var progressSteps = 0;
|
||||
|
||||
foreach (var account in _database.Accounts)
|
||||
{
|
||||
progressSteps += await account.GetProgressSteps(accountReadInput);
|
||||
}
|
||||
|
||||
// Switch to progress mode
|
||||
SetProgressMode(true, progressSteps);
|
||||
|
||||
// Create the input class
|
||||
var workerInput = new FeedReadWorkerInput(forceRead);
|
||||
|
||||
// Start the worker
|
||||
_feedReadWorker.RunWorkerAsync(workerInput);
|
||||
// Start reading
|
||||
await HandleFeedReadWorkerStart(forceRead, null);
|
||||
}
|
||||
|
||||
private void HandleFeedReadWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
private void IncrementProgress()
|
||||
{
|
||||
Debug.Assert(FeedReadProgress.Value + 1 <= FeedReadProgress.Maximum);
|
||||
|
||||
// Set progress
|
||||
FeedReadProgress.Value = e.ProgressPercentage;
|
||||
FeedReadProgress.Value++;
|
||||
}
|
||||
|
||||
private void HandleFeedReadWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
|
||||
private void CompleteRead()
|
||||
{
|
||||
// Refresh the database to current settings
|
||||
ResetDatabase();
|
||||
@@ -119,6 +118,8 @@ public partial class MainWindow
|
||||
NewVersionLink.Visibility = Visibility.Visible;
|
||||
|
||||
UpdateErrorLink();
|
||||
|
||||
_reading = false;
|
||||
}
|
||||
|
||||
private void UpdateErrorLink()
|
||||
@@ -134,98 +135,61 @@ public partial class MainWindow
|
||||
: string.Format(Properties.Resources.FeedErrorsLink, feedErrorCount);
|
||||
}
|
||||
|
||||
private static void HandleFeedReadWorkerStart(object sender, DoWorkEventArgs e)
|
||||
private async Task HandleFeedReadWorkerStart(bool forceRead, Guid? feedId)
|
||||
{
|
||||
// Create a new database instance for just this thread
|
||||
var database = new FeedCenterEntities();
|
||||
|
||||
// Get the worker
|
||||
var worker = (BackgroundWorker)sender;
|
||||
|
||||
// Get the input information
|
||||
var workerInput = (FeedReadWorkerInput)e.Argument ?? new FeedReadWorkerInput();
|
||||
|
||||
// Setup for progress
|
||||
var currentProgress = 0;
|
||||
|
||||
var accountsToRead = new List<Account>();
|
||||
|
||||
// If we have a single feed then get the account for that feed
|
||||
if (workerInput.FeedId != null)
|
||||
try
|
||||
{
|
||||
var feed = database.Feeds.FirstOrDefault(f => f.Id == workerInput.FeedId);
|
||||
// Create a new database instance for just this thread
|
||||
var database = new FeedCenterEntities();
|
||||
|
||||
if (feed != null)
|
||||
accountsToRead.Add(feed.Account);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise get all accounts
|
||||
accountsToRead.AddRange(database.Accounts);
|
||||
}
|
||||
var accountsToRead = new List<Account>();
|
||||
|
||||
var incrementProgress = () =>
|
||||
{
|
||||
// Increment progress
|
||||
currentProgress += 1;
|
||||
// If we have a single feed then get the account for that feed
|
||||
if (feedId != null)
|
||||
{
|
||||
var feed = database.Feeds.FirstOrDefault(f => f.Id == feedId);
|
||||
|
||||
if (feed != null)
|
||||
accountsToRead.Add(feed.Account);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise get all accounts
|
||||
accountsToRead.AddRange(database.Accounts);
|
||||
}
|
||||
|
||||
// Loop over each account and read it
|
||||
foreach (var account in accountsToRead)
|
||||
{
|
||||
var accountReadInput = new AccountReadInput(database, feedId, forceRead, IncrementProgress);
|
||||
|
||||
await account.Read(accountReadInput);
|
||||
}
|
||||
|
||||
// Report progress
|
||||
worker.ReportProgress(currentProgress);
|
||||
};
|
||||
IncrementProgress();
|
||||
|
||||
// Loop over each account and read it
|
||||
foreach (var account in accountsToRead)
|
||||
{
|
||||
var accountReadInput = new AccountReadInput(database, workerInput.FeedId, workerInput.ForceRead, incrementProgress);
|
||||
// See if we're due for a version check
|
||||
if (UpdateCheck.LocalVersion.Major > 0 && DateTime.Now - Settings.Default.LastVersionCheck >= Settings.Default.VersionCheckInterval)
|
||||
{
|
||||
// Get the update information
|
||||
await UpdateCheck.CheckForUpdate(Settings.Default.IncludePrerelease);
|
||||
|
||||
account.Read(accountReadInput);
|
||||
// Update the last check time
|
||||
Settings.Default.LastVersionCheck = DateTime.Now;
|
||||
}
|
||||
|
||||
// Report progress
|
||||
IncrementProgress();
|
||||
|
||||
// Sleep for a little bit so the user can see the update
|
||||
Thread.Sleep(Settings.Default.ProgressSleepInterval * 3);
|
||||
|
||||
CompleteRead();
|
||||
}
|
||||
|
||||
//// Create the list of feeds to read
|
||||
//var feedsToRead = new List<Feed>();
|
||||
|
||||
//// If we have a single feed then add it to the list - otherwise add them all
|
||||
//if (workerInput.FeedId != null)
|
||||
// feedsToRead.Add(database.Feeds.First(feed => feed.Id == workerInput.FeedId));
|
||||
//else
|
||||
// feedsToRead.AddRange(database.Feeds);
|
||||
|
||||
//// Loop over each feed and read it
|
||||
//foreach (var feed in feedsToRead)
|
||||
//{
|
||||
// // Read the feed
|
||||
// database.SaveChanges(() => feed.Read(workerInput.ForceRead));
|
||||
|
||||
// // Increment progress
|
||||
// currentProgress += 1;
|
||||
|
||||
// // Report progress
|
||||
// worker.ReportProgress(currentProgress);
|
||||
//}
|
||||
|
||||
// Increment progress
|
||||
currentProgress += 1;
|
||||
|
||||
// Report progress
|
||||
worker.ReportProgress(currentProgress);
|
||||
|
||||
// See if we're due for a version check
|
||||
if (DateTime.Now - Settings.Default.LastVersionCheck >= Settings.Default.VersionCheckInterval)
|
||||
catch (Exception exception)
|
||||
{
|
||||
// Get the update information
|
||||
UpdateCheck.CheckForUpdate(Settings.Default.IncludePrerelease).Wait();
|
||||
|
||||
// Update the last check time
|
||||
Settings.Default.LastVersionCheck = DateTime.Now;
|
||||
HandleException(exception);
|
||||
}
|
||||
|
||||
// Increment progress
|
||||
currentProgress += 1;
|
||||
|
||||
// Report progress
|
||||
worker.ReportProgress(currentProgress);
|
||||
|
||||
// Sleep for a little bit so the user can see the update
|
||||
Thread.Sleep(Settings.Default.ProgressSleepInterval * 3);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using ChrisKaczor.ApplicationUpdate;
|
||||
using ChrisKaczor.Wpf.Application;
|
||||
using FeedCenter.Feeds;
|
||||
using FeedCenter.Properties;
|
||||
using Serilog;
|
||||
using System;
|
||||
@@ -29,7 +30,6 @@ public partial class MainWindow : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_mainTimer?.Dispose();
|
||||
_feedReadWorker?.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
@@ -54,9 +54,6 @@ public partial class MainWindow : IDisposable
|
||||
// Set up the update handler
|
||||
InitializeUpdate();
|
||||
|
||||
// Show the notification icon
|
||||
NotificationIcon.Initialize(this);
|
||||
|
||||
// Load window settings
|
||||
LoadWindowSettings();
|
||||
|
||||
@@ -66,12 +63,6 @@ public partial class MainWindow : IDisposable
|
||||
: Brushes.Black;
|
||||
HeaderLabel.Foreground = LinkTextList.Foreground;
|
||||
|
||||
// Create the background worker that does the actual reading
|
||||
_feedReadWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
|
||||
_feedReadWorker.DoWork += HandleFeedReadWorkerStart;
|
||||
_feedReadWorker.ProgressChanged += HandleFeedReadWorkerProgressChanged;
|
||||
_feedReadWorker.RunWorkerCompleted += HandleFeedReadWorkerCompleted;
|
||||
|
||||
// Set up the database
|
||||
_database = new FeedCenterEntities();
|
||||
|
||||
@@ -88,13 +79,26 @@ public partial class MainWindow : IDisposable
|
||||
// Initialize the feed display
|
||||
InitializeDisplay();
|
||||
|
||||
// Check for update
|
||||
if (Settings.Default.CheckVersionAtStartup)
|
||||
await UpdateCheck.CheckForUpdate(Settings.Default.IncludePrerelease);
|
||||
if (UpdateCheck.LocalVersion.Major > 0)
|
||||
{
|
||||
// Check for update
|
||||
if (Settings.Default.CheckVersionAtStartup)
|
||||
await UpdateCheck.CheckForUpdate(Settings.Default.IncludePrerelease);
|
||||
|
||||
// Show the link if updates are available
|
||||
if (UpdateCheck.UpdateAvailable)
|
||||
NewVersionLink.Visibility = Visibility.Visible;
|
||||
// Show the link if updates are available
|
||||
if (UpdateCheck.UpdateAvailable)
|
||||
NewVersionLink.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Show the notification icon
|
||||
NotificationIcon.Initialize(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Logger.Error(e, "");
|
||||
}
|
||||
|
||||
Log.Logger.Information("MainForm creation finished");
|
||||
}
|
||||
|
||||
@@ -38,29 +38,36 @@ public partial class MainWindow
|
||||
_mainTimer?.Stop();
|
||||
}
|
||||
|
||||
private void HandleMainTimerElapsed(object sender, EventArgs e)
|
||||
private async void HandleMainTimerElapsed(object sender, EventArgs e)
|
||||
{
|
||||
_dispatcher.Invoke(() =>
|
||||
try
|
||||
{
|
||||
// If the background worker is busy then don't do anything
|
||||
if (_feedReadWorker.IsBusy)
|
||||
return;
|
||||
await _dispatcher.Invoke(async () =>
|
||||
{
|
||||
// If the background worker is busy then don't do anything
|
||||
if (_reading)
|
||||
return;
|
||||
|
||||
// Stop the timer for now
|
||||
StopTimer();
|
||||
// Stop the timer for now
|
||||
StopTimer();
|
||||
|
||||
// Move to the next feed if the scroll interval has expired and the mouse isn't hovering
|
||||
if (LinkTextList.IsMouseOver)
|
||||
_lastFeedDisplay = DateTime.Now;
|
||||
else if (DateTime.Now - _lastFeedDisplay >= Settings.Default.FeedScrollInterval)
|
||||
NextFeed();
|
||||
// Move to the next feed if the scroll interval has expired and the mouse isn't hovering
|
||||
if (LinkTextList.IsMouseOver)
|
||||
_lastFeedDisplay = DateTime.Now;
|
||||
else if (DateTime.Now - _lastFeedDisplay >= Settings.Default.FeedScrollInterval)
|
||||
NextFeed();
|
||||
|
||||
// Check to see if we should try to read the feeds
|
||||
if (DateTime.Now - _lastFeedRead >= Settings.Default.FeedCheckInterval)
|
||||
ReadFeeds();
|
||||
// Check to see if we should try to read the feeds
|
||||
if (DateTime.Now - _lastFeedRead >= Settings.Default.FeedCheckInterval)
|
||||
await ReadFeeds();
|
||||
|
||||
// Get the timer going again
|
||||
StartTimer();
|
||||
});
|
||||
// Get the timer going again
|
||||
StartTimer();
|
||||
});
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using System.Web.UI;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using FeedCenter.Feeds;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
@@ -75,7 +76,7 @@ public partial class MainWindow
|
||||
UpdateErrorLink();
|
||||
}
|
||||
|
||||
private static void HandleException(Exception exception)
|
||||
internal static void HandleException(Exception exception)
|
||||
{
|
||||
Log.Logger.Write(LogEventLevel.Debug, exception, "");
|
||||
}
|
||||
@@ -109,19 +110,33 @@ public partial class MainWindow
|
||||
UpdateErrorLink();
|
||||
}
|
||||
|
||||
private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
|
||||
private async void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuItem = (MenuItem) e.Source;
|
||||
try
|
||||
{
|
||||
var menuItem = (MenuItem) e.Source;
|
||||
|
||||
if (Equals(menuItem, MenuRefresh))
|
||||
ReadCurrentFeed(true);
|
||||
else if (Equals(menuItem, MenuRefreshAll))
|
||||
ReadFeeds(true);
|
||||
if (Equals(menuItem, MenuRefresh))
|
||||
ReadCurrentFeed(true);
|
||||
else if (Equals(menuItem, MenuRefreshAll))
|
||||
await ReadFeeds(true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
private async void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ReadFeeds(true);
|
||||
try
|
||||
{
|
||||
await ReadFeeds(true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private async void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -76,13 +76,6 @@ public partial class MainWindow
|
||||
{
|
||||
base.OnClosing(e);
|
||||
|
||||
// Ditch the worker
|
||||
if (_feedReadWorker != null)
|
||||
{
|
||||
_feedReadWorker.CancelAsync();
|
||||
_feedReadWorker.Dispose();
|
||||
}
|
||||
|
||||
// Get rid of the timer
|
||||
TerminateTimer();
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ internal static class NotificationIcon
|
||||
_notificationIcon = new TaskbarIcon { Icon = Resources.Application };
|
||||
_notificationIcon.TrayMouseDoubleClick += HandleNotificationIconDoubleClick;
|
||||
|
||||
// Setup the menu
|
||||
// Set up the menu
|
||||
var contextMenu = new ContextMenu();
|
||||
contextMenu.Opened += HandleContextMenuOpened;
|
||||
|
||||
@@ -76,7 +76,7 @@ internal static class NotificationIcon
|
||||
public static void Dispose()
|
||||
{
|
||||
// Get rid of the icon
|
||||
_notificationIcon.Dispose();
|
||||
_notificationIcon?.Dispose();
|
||||
_notificationIcon = null;
|
||||
|
||||
_mainWindow = null;
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using FeedCenter.Accounts;
|
||||
|
||||
namespace FeedCenter.Options
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public class AccountTypeItem
|
||||
{
|
||||
public class AccountTypeItem
|
||||
{
|
||||
public AccountType AccountType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public AccountType AccountType { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public static List<AccountTypeItem> AccountTypes =>
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = Properties.Resources.AccountTypeFever,
|
||||
AccountType = AccountType.Fever
|
||||
},
|
||||
//new()
|
||||
//{
|
||||
// Name = Properties.Resources.AccountTypeGoogleReader,
|
||||
// AccountType = AccountType.GoogleReader
|
||||
//}
|
||||
];
|
||||
}
|
||||
public static List<AccountTypeItem> AccountTypes =>
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = Properties.Resources.AccountTypeFever,
|
||||
AccountType = AccountType.Fever
|
||||
},
|
||||
//new()
|
||||
//{
|
||||
// Name = Properties.Resources.AccountTypeGoogleReader,
|
||||
// AccountType = AccountType.GoogleReader
|
||||
//}
|
||||
new()
|
||||
{
|
||||
Name = Properties.Resources.AccountTypeMiniflux,
|
||||
AccountType = AccountType.Miniflux
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using FeedCenter.Accounts;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
xmlns:properties="clr-namespace:FeedCenter.Properties"
|
||||
xmlns:feedCenter="clr-namespace:FeedCenter"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
d:DataContext="{d:DesignInstance Type=feedCenter:Account}"
|
||||
d:DataContext="{d:DesignInstance Type=accounts:Account}"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:options="clr-namespace:FeedCenter.Options"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
xmlns:accounts="clr-namespace:FeedCenter.Accounts"
|
||||
mc:Ignorable="d"
|
||||
Title="AccountWindow"
|
||||
Height="350"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using ChrisKaczor.Wpf.Validation;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using FeedCenter.Accounts;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
@@ -33,46 +33,46 @@ public partial class AccountWindow
|
||||
return ShowDialog();
|
||||
}
|
||||
|
||||
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
|
||||
private async void HandleOkayButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var transaction = _entities.BeginTransaction();
|
||||
|
||||
if (!this.IsValid(OptionsTabControl))
|
||||
try
|
||||
{
|
||||
transaction.Rollback();
|
||||
return;
|
||||
}
|
||||
var transaction = _entities.BeginTransaction();
|
||||
|
||||
if (_isNew)
|
||||
{
|
||||
_entities.Accounts.Add(_account);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
var accountId = _account.Id;
|
||||
|
||||
AccountReadProgressBar.Value = 0;
|
||||
AccountReadProgressBar.Maximum = _account.GetProgressSteps(_entities) + 1;
|
||||
|
||||
AccountReadProgress.Visibility = Visibility.Visible;
|
||||
ButtonPanel.Visibility = Visibility.Collapsed;
|
||||
|
||||
var dispatcher = Dispatcher.CurrentDispatcher;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var entities = new FeedCenterEntities();
|
||||
var account = entities.Accounts.First(a => a.Id == accountId);
|
||||
var accountReadInput = new AccountReadInput(entities, null, true, () => dispatcher.Invoke(() => AccountReadProgressBar.Value++));
|
||||
account.Read(accountReadInput);
|
||||
|
||||
dispatcher.Invoke(() =>
|
||||
if (!this.IsValid(OptionsTabControl))
|
||||
{
|
||||
DialogResult = true;
|
||||
transaction.Rollback();
|
||||
return;
|
||||
}
|
||||
|
||||
Close();
|
||||
});
|
||||
});
|
||||
if (_isNew)
|
||||
{
|
||||
_entities.Accounts.Add(_account);
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
var accountId = _account.Id;
|
||||
|
||||
var accountReadInput = new AccountReadInput(_entities, null, true, () => AccountReadProgressBar.Value++);
|
||||
|
||||
AccountReadProgressBar.Value = 0;
|
||||
AccountReadProgressBar.Maximum = await _account.GetProgressSteps(accountReadInput);
|
||||
|
||||
AccountReadProgress.Visibility = Visibility.Visible;
|
||||
ButtonPanel.Visibility = Visibility.Collapsed;
|
||||
|
||||
//var entities = new FeedCenterEntities();
|
||||
var account = _entities.Accounts.First(a => a.Id == accountId);
|
||||
await account.Read(accountReadInput);
|
||||
|
||||
DialogResult = true;
|
||||
|
||||
Close();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MainWindow.HandleException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
xmlns:properties="clr-namespace:FeedCenter.Properties"
|
||||
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
|
||||
xmlns:feedCenter="clr-namespace:FeedCenter"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
xmlns:accounts="clr-namespace:FeedCenter.Accounts"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="311"
|
||||
d:DesignWidth="425">
|
||||
@@ -46,7 +48,7 @@
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
AllowDrop="True"
|
||||
Background="{x:Null}"
|
||||
d:DataContext="{d:DesignInstance feedCenter:Account }">
|
||||
d:DataContext="{d:DesignInstance accounts:Account }">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name}"
|
||||
Header="{x:Static properties:Resources.AccountNameColumnHeader}"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using FeedCenter.Accounts;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
@@ -52,7 +52,7 @@ public partial class AccountsOptionsPanel
|
||||
|
||||
private void AddAccount()
|
||||
{
|
||||
var account = new Account(AccountType.Fever);
|
||||
var account = new Account(AccountType.Miniflux);
|
||||
|
||||
var accountWindow = new AccountWindow(_entities);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:options="clr-namespace:FeedCenter.Options"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/FeedCenter;component/Resources/Application.ico"
|
||||
FocusManager.FocusedElement="{Binding ElementName=FeedLinkFilterText}">
|
||||
@@ -99,9 +100,9 @@
|
||||
mah:TextBoxHelper.Watermark="{x:Static my:Resources.openLabel}"
|
||||
IsEnabled="{Binding ElementName=OpenCheckBox, Path=IsChecked}">
|
||||
<ComboBoxItem Content="{x:Static my:Resources.openAllMultipleToolbarButton}"
|
||||
Tag="{x:Static feedCenter:MultipleOpenAction.IndividualPages}" />
|
||||
Tag="{x:Static feeds:MultipleOpenAction.IndividualPages}" />
|
||||
<ComboBoxItem Content="{x:Static my:Resources.openAllSingleToolbarButton}"
|
||||
Tag="{x:Static feedCenter:MultipleOpenAction.SinglePage}" />
|
||||
Tag="{x:Static feeds:MultipleOpenAction.SinglePage}" />
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
<StackPanel Grid.Column="0"
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Windows;assembly=ChrisKaczor.Wpf.Windows.ControlBox"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:options="clr-namespace:FeedCenter.Options"
|
||||
d:DataContext="{d:DesignInstance Type=feedCenter:Category}"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
d:DataContext="{d:DesignInstance Type=feeds:Category}"
|
||||
Title="CategoryWindow"
|
||||
Width="300"
|
||||
ResizeMode="NoResize"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ChrisKaczor.Wpf.Validation;
|
||||
using System.Windows;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
namespace FeedCenter.Options
|
||||
{
|
||||
public class CheckedFeedListItem : CheckedListItem<Feed>
|
||||
{
|
||||
}
|
||||
}
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public class CheckedFeedListItem : CheckedListItem<Feeds.Feed>;
|
||||
@@ -4,10 +4,11 @@
|
||||
xmlns:properties="clr-namespace:FeedCenter.Properties"
|
||||
xmlns:feedCenter="clr-namespace:FeedCenter"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
d:DataContext="{d:DesignInstance Type=feedCenter:Feed}"
|
||||
d:DataContext="{d:DesignInstance Type=feeds:Feed}"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:options="clr-namespace:FeedCenter.Options"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
mc:Ignorable="d"
|
||||
Title="FeedWindow"
|
||||
Height="350"
|
||||
@@ -90,9 +91,9 @@
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.openLabel}">
|
||||
<ComboBoxItem Content="{x:Static properties:Resources.openAllSingleToolbarButton}"
|
||||
Tag="{x:Static feedCenter:MultipleOpenAction.SinglePage}" />
|
||||
Tag="{x:Static feeds:MultipleOpenAction.SinglePage}" />
|
||||
<ComboBoxItem Content="{x:Static properties:Resources.openAllMultipleToolbarButton}"
|
||||
Tag="{x:Static feedCenter:MultipleOpenAction.IndividualPages}" />
|
||||
Tag="{x:Static feeds:MultipleOpenAction.IndividualPages}" />
|
||||
</ComboBox>
|
||||
<ComboBox Name="UserAgentComboBox"
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
|
||||
@@ -14,7 +14,7 @@ public partial class FeedWindow
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public bool? Display(Feed feed, Window owner)
|
||||
public bool? Display(Feeds.Feed feed, Window owner)
|
||||
{
|
||||
CategoryComboBox.ItemsSource = _entities.Categories;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:properties="clr-namespace:FeedCenter.Properties"
|
||||
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
|
||||
xmlns:feedCenter="clr-namespace:FeedCenter"
|
||||
xmlns:feeds="clr-namespace:FeedCenter.Feeds"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="311"
|
||||
d:DesignWidth="425">
|
||||
@@ -45,7 +46,7 @@
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
AllowDrop="True"
|
||||
Background="{x:Null}"
|
||||
d:DataContext="{d:DesignInstance feedCenter:Category }">
|
||||
d:DataContext="{d:DesignInstance feeds:Category }">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name}"
|
||||
Header="{x:Static properties:Resources.CategoryNameColumnHeader}"
|
||||
@@ -80,7 +81,7 @@
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
|
||||
Background="{x:Null}"
|
||||
SelectionChanged="HandleFeedDataGridSelectionChanged"
|
||||
d:DataContext="{d:DesignInstance feedCenter:Feed }">
|
||||
d:DataContext="{d:DesignInstance feeds:Feed }">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name}"
|
||||
Header="{x:Static properties:Resources.FeedNameColumnHeader}"
|
||||
|
||||
@@ -7,6 +7,8 @@ using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Xml;
|
||||
using FeedCenter.Accounts;
|
||||
using FeedCenter.Feeds;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public class UserAgentItem
|
||||
{
|
||||
new UserAgentItem
|
||||
{
|
||||
Caption = Properties.Resources.ApplicationUserAgentCaption,
|
||||
Caption = Resources.ApplicationUserAgentCaption,
|
||||
UserAgent = string.Empty
|
||||
},
|
||||
new UserAgentItem
|
||||
|
||||
@@ -25,5 +25,5 @@ using System.Windows;
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
|
||||
|
||||
[assembly: AssemblyVersion("1.1.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.1.0.0")]
|
||||
[assembly: AssemblyVersion("0.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.0.0.0")]
|
||||
@@ -7,7 +7,7 @@
|
||||
<PublishDir>bin\Release\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net9.0-windows8.0</TargetFramework>
|
||||
<TargetFramework>net10.0-windows8.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
|
||||
11
Application/Properties/Resources.Designer.cs
generated
11
Application/Properties/Resources.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace FeedCenter.Properties {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
@@ -141,6 +141,15 @@ namespace FeedCenter.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Miniflux.
|
||||
/// </summary>
|
||||
public static string AccountTypeMiniflux {
|
||||
get {
|
||||
return ResourceManager.GetString("AccountTypeMiniflux", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to URL.
|
||||
/// </summary>
|
||||
|
||||
@@ -614,5 +614,8 @@ All feeds currently in category "{0}" will be moved to the default category.</va
|
||||
</data>
|
||||
<data name="includePrereleaseCheckBox" xml:space="preserve">
|
||||
<value>Include _prerelease</value>
|
||||
</data>
|
||||
</data>
|
||||
<data name="AccountTypeMiniflux" xml:space="preserve">
|
||||
<value>Miniflux</value>
|
||||
</data>
|
||||
</root>
|
||||
13
appveyor.yml
13
appveyor.yml
@@ -10,7 +10,15 @@ assembly_info:
|
||||
file: 'Properties\AssemblyInfo.cs'
|
||||
assembly_version: "{version}"
|
||||
assembly_file_version: "{version}"
|
||||
environment:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
install:
|
||||
- ps: |
|
||||
Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -UseBasicParsing -OutFile "$env:temp\dotnet-install.ps1"
|
||||
& $env:temp\dotnet-install.ps1 -Architecture x64 -Version '10.0.100' -InstallDir "$env:ProgramFiles\dotnet"
|
||||
before_build:
|
||||
- ps: dotnet --version
|
||||
- ps: nuget restore .\Installer\
|
||||
- ps: dotnet publish .\Application\ /p:PublishProfile=Properties\PublishProfiles\x64.pubxml
|
||||
build:
|
||||
@@ -26,7 +34,6 @@ for:
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: GitHub
|
||||
prerelease: false
|
||||
|
||||
- branches:
|
||||
only:
|
||||
@@ -34,6 +41,4 @@ for:
|
||||
version: 1.2.0.{build}
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: GitHub
|
||||
prerelease: true
|
||||
tag: $(version)-prerelease
|
||||
name: GitHub-Prerelease
|
||||
|
||||
Reference in New Issue
Block a user