diff --git a/Application/FeedErrorWindow.xaml b/Application/FeedErrorWindow.xaml
index cf00383..bfa924a 100644
--- a/Application/FeedErrorWindow.xaml
+++ b/Application/FeedErrorWindow.xaml
@@ -88,17 +88,10 @@
-
-
diff --git a/Application/FeedErrorWindow.xaml.cs b/Application/FeedErrorWindow.xaml.cs
index 0d412e3..ac28729 100644
--- a/Application/FeedErrorWindow.xaml.cs
+++ b/Application/FeedErrorWindow.xaml.cs
@@ -1,30 +1,29 @@
-using ChrisKaczor.InstalledBrowsers;
-using FeedCenter.Data;
-using FeedCenter.Options;
-using FeedCenter.Properties;
-using System.ComponentModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
+using ChrisKaczor.InstalledBrowsers;
+using FeedCenter.Data;
+using FeedCenter.Options;
+using FeedCenter.Properties;
namespace FeedCenter
{
public partial class FeedErrorWindow
{
+ private CollectionViewSource _collectionViewSource;
+
public FeedErrorWindow()
{
InitializeComponent();
}
- private FeedCenterEntities _database;
- private CollectionViewSource _collectionViewSource;
-
- public bool? Display(Window owner)
+ public void Display(Window owner)
{
- _database = Database.Entities;
-
// Create a view and sort it by name
- _collectionViewSource = new CollectionViewSource { Source = _database.Feeds };
+ _collectionViewSource = new CollectionViewSource { Source = Database.Entities.Feeds };
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
@@ -36,10 +35,10 @@ namespace FeedCenter
Owner = owner;
// Show the dialog and result the result
- return ShowDialog();
+ ShowDialog();
}
- private void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
+ private static void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{
var feed = (Feed) e.Item;
@@ -70,9 +69,12 @@ namespace FeedCenter
private void DeleteSelectedFeed()
{
+ if (MessageBox.Show(this, Properties.Resources.ConfirmDeleteFeed, Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
+ return;
+
var feed = (Feed) FeedDataGrid.SelectedItem;
- _database.Feeds.Remove(feed);
+ Database.Entities.SaveChanges(() => Database.Entities.Feeds.Remove(feed));
SetFeedButtonStates();
}
@@ -98,24 +100,23 @@ namespace FeedCenter
InstalledBrowser.OpenLink(Settings.Default.Browser, feed.Source);
}
- private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
- {
- // Save the actual settings
- _database.SaveChanges(() => { });
-
- DialogResult = true;
-
- Close();
- }
-
- private void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e)
+ private async void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e)
{
IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait;
- var feed = (Feed) FeedDataGrid.SelectedItem;
+ var feedId = ((Feed) FeedDataGrid.SelectedItem).Id;
- _database.SaveChanges(() => feed.Read(true));
+ await Task.Run(() =>
+ {
+ var entities = new FeedCenterEntities();
+
+ var feed = entities.Feeds.First(f => f.Id == feedId);
+
+ entities.SaveChanges(() => feed.Read(true));
+ });
+
+ Database.Entities.Refresh();
var selectedIndex = FeedDataGrid.SelectedIndex;
diff --git a/Application/FeedParsers/FeedParseError.cs b/Application/FeedParsers/FeedParseError.cs
new file mode 100644
index 0000000..786d826
--- /dev/null
+++ b/Application/FeedParsers/FeedParseError.cs
@@ -0,0 +1,8 @@
+namespace FeedCenter.FeedParsers
+{
+ internal enum FeedParseError
+ {
+ Unknown = 0,
+ InvalidXml = 1
+ }
+}
\ No newline at end of file
diff --git a/Application/FeedParsers/FeedParseException.cs b/Application/FeedParsers/FeedParseException.cs
new file mode 100644
index 0000000..dd2849b
--- /dev/null
+++ b/Application/FeedParsers/FeedParseException.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace FeedCenter.FeedParsers
+{
+ internal class FeedParseException : ApplicationException
+ {
+ public FeedParseException(FeedParseError feedParseError)
+ {
+ ParseError = feedParseError;
+ }
+
+ public FeedParseError ParseError { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Application/FeedParsers/FeedParserBase.cs b/Application/FeedParsers/FeedParserBase.cs
index 9c0be67..b801532 100644
--- a/Application/FeedParsers/FeedParserBase.cs
+++ b/Application/FeedParsers/FeedParserBase.cs
@@ -142,11 +142,17 @@ namespace FeedCenter.FeedParsers
// No clue!
return FeedType.Unknown;
}
+ catch (XmlException xmlException)
+ {
+ Log.Logger.Error(xmlException, "Exception: {0}", feedText);
+
+ throw new FeedParseException(FeedParseError.InvalidXml);
+ }
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception: {0}", feedText);
- return FeedType.Unknown;
+ throw new FeedParseException(FeedParseError.InvalidXml);
}
}
diff --git a/Application/Feeds/Feed.cs b/Application/Feeds/Feed.cs
index 6410e9a..8d4cc7d 100644
--- a/Application/Feeds/Feed.cs
+++ b/Application/Feeds/Feed.cs
@@ -1,12 +1,4 @@
-using ChrisKaczor.ApplicationUpdate;
-using FeedCenter.Data;
-using FeedCenter.FeedParsers;
-using FeedCenter.Properties;
-using FeedCenter.Xml;
-using JetBrains.Annotations;
-using Realms;
-using Serilog;
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -14,9 +6,17 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
-using Resources = FeedCenter.Properties.Resources;
+using ChrisKaczor.ApplicationUpdate;
+using FeedCenter.Data;
+using FeedCenter.FeedParsers;
+using FeedCenter.Properties;
+using FeedCenter.Xml;
+using JetBrains.Annotations;
+using Realms;
+using Serilog;
namespace FeedCenter
{
@@ -49,30 +49,30 @@ namespace FeedCenter
NotFound,
Timeout,
ConnectionFailed,
- ServerError
+ ServerError,
+ Moved
}
#endregion
public partial class Feed : RealmObject
{
+ private static HttpClient _httpClient;
+ public bool Authenticate { get; set; }
+
+ public Guid CategoryId { get; set; }
+
+ public int CheckInterval { get; set; } = 60;
+ public string Description { get; set; }
+ public bool Enabled { get; set; } = true;
+
[PrimaryKey]
- [MapTo("ID")]
public Guid Id { get; set; }
- public string Name { get; set; }
- public string Title { get; set; }
- public string Source { get; set; }
- public string Link { get; set; }
- public string Description { get; set; }
- public DateTimeOffset LastChecked { get; set; }
- public int CheckInterval { get; set; } = 60;
- public bool Enabled { get; set; } = true;
- public bool Authenticate { get; set; }
- public string Username { get; set; }
- public string Password { get; set; }
+ [UsedImplicitly]
+ public IList Items { get; }
- private string LastReadResultRaw { get; set; }
+ public DateTimeOffset LastChecked { get; set; }
public FeedReadResult LastReadResult
{
@@ -80,22 +80,6 @@ namespace FeedCenter
set => LastReadResultRaw = value.ToString();
}
- public DateTimeOffset LastUpdated { get; set; }
-
- [MapTo("CategoryID")]
- public Guid CategoryId { get; set; }
-
- private string MultipleOpenActionRaw { get; set; }
-
- public MultipleOpenAction MultipleOpenAction
- {
- get => Enum.TryParse(MultipleOpenActionRaw, out MultipleOpenAction result) ? result : MultipleOpenAction.IndividualPages;
- set => MultipleOpenActionRaw = value.ToString();
- }
-
- [UsedImplicitly]
- public IList Items { get; }
-
// ReSharper disable once UnusedMember.Global
public string LastReadResultDescription
{
@@ -115,7 +99,24 @@ namespace FeedCenter
}
}
- private static HttpClient _httpClient;
+ private string LastReadResultRaw { get; set; }
+
+ public DateTimeOffset LastUpdated { get; set; }
+ public string Link { get; set; }
+
+ public MultipleOpenAction MultipleOpenAction
+ {
+ get => Enum.TryParse(MultipleOpenActionRaw, out MultipleOpenAction result) ? result : MultipleOpenAction.IndividualPages;
+ set => MultipleOpenActionRaw = value.ToString();
+ }
+
+ private string MultipleOpenActionRaw { get; set; }
+
+ public string Name { get; set; }
+ public string Password { get; set; }
+ public string Source { get; set; }
+ public string Title { get; set; }
+ public string Username { get; set; }
public static Feed Create()
{
@@ -175,11 +176,14 @@ namespace FeedCenter
// Create and configure the HTTP client if needed
if (_httpClient == null)
{
- _httpClient = new HttpClient(new HttpClientHandler
+ var clientHandler = new HttpClientHandler
{
// Set that we'll accept compressed data
- AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
- });
+ AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
+ AllowAutoRedirect = true
+ };
+
+ _httpClient = new HttpClient(clientHandler);
// Set a user agent string
var userAgent = string.IsNullOrWhiteSpace(Settings.Default.DefaultUserAgent) ? "FeedCenter/" + UpdateCheck.LocalVersion : Settings.Default.DefaultUserAgent;
@@ -222,33 +226,52 @@ namespace FeedCenter
return Tuple.Create(FeedReadResult.ConnectionFailed, string.Empty);
}
+ catch (AggregateException aggregateException)
+ {
+ Log.Logger.Error(aggregateException, "Exception");
+
+ var innerException = aggregateException.InnerException;
+
+ if (innerException is not HttpRequestException httpRequestException)
+ return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
+
+ switch (httpRequestException.StatusCode)
+ {
+ case HttpStatusCode.InternalServerError:
+ return Tuple.Create(FeedReadResult.ServerError, string.Empty);
+
+ case HttpStatusCode.NotModified:
+ return Tuple.Create(FeedReadResult.NotModified, string.Empty);
+
+ case HttpStatusCode.NotFound:
+ return Tuple.Create(FeedReadResult.NotFound, string.Empty);
+
+ case HttpStatusCode.Unauthorized:
+ case HttpStatusCode.Forbidden:
+ return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
+
+ case HttpStatusCode.Moved:
+ return Tuple.Create(FeedReadResult.Moved, string.Empty);
+ }
+
+ if (httpRequestException.InnerException is not SocketException socketException)
+ return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
+
+ switch (socketException.SocketErrorCode)
+ {
+ case SocketError.NoData:
+ return Tuple.Create(FeedReadResult.NoResponse, string.Empty);
+
+ case SocketError.HostNotFound:
+ return Tuple.Create(FeedReadResult.NotFound, string.Empty);
+ }
+
+ return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
+ }
catch (WebException webException)
{
var result = FeedReadResult.UnknownError;
- if (webException.Response is HttpWebResponse errorResponse)
- {
- switch (errorResponse.StatusCode)
- {
- case HttpStatusCode.InternalServerError:
-
- return Tuple.Create(FeedReadResult.ServerError, string.Empty);
-
- case HttpStatusCode.NotModified:
-
- return Tuple.Create(FeedReadResult.NotModified, string.Empty);
-
- case HttpStatusCode.NotFound:
-
- return Tuple.Create(FeedReadResult.NotFound, string.Empty);
-
- case HttpStatusCode.Unauthorized:
- case HttpStatusCode.Forbidden:
-
- return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
- }
- }
-
switch (webException.Status)
{
case WebExceptionStatus.ConnectFailure:
@@ -337,6 +360,12 @@ namespace FeedCenter
return FeedReadResult.Success;
}
+ catch (FeedParseException feedParseException)
+ {
+ Log.Logger.Error(feedParseException, "Exception");
+
+ return FeedReadResult.InvalidXml;
+ }
catch (InvalidFeedFormatException exception)
{
Log.Logger.Error(exception, "Exception");
diff --git a/Application/Feeds/FeedItem.cs b/Application/Feeds/FeedItem.cs
index 50cc404..d2270b4 100644
--- a/Application/Feeds/FeedItem.cs
+++ b/Application/Feeds/FeedItem.cs
@@ -1,30 +1,30 @@
-using FeedCenter.Options;
-using Realms;
-using System;
+using System;
using System.Text.RegularExpressions;
+using FeedCenter.Options;
+using Realms;
namespace FeedCenter
{
public partial class FeedItem : RealmObject
{
- [PrimaryKey]
- [MapTo("ID")]
- public Guid Id { get; set; }
-
- [MapTo("FeedID")]
- public Guid FeedId { get; set; }
-
- public string Title { get; set; }
- public string Link { get; set; }
- public string Description { get; set; }
public bool BeenRead { get; set; }
- public DateTimeOffset LastFound { get; set; }
- public bool New { get; set; }
- public string Guid { get; set; }
- public int Sequence { get; set; }
+ public string Description { get; set; }
public Feed Feed { get; set; }
+ public Guid FeedId { get; set; }
+ public string Guid { get; set; }
+
+ [PrimaryKey]
+ public Guid Id { get; set; }
+
+ public DateTimeOffset LastFound { get; set; }
+ public string Link { get; set; }
+ public bool New { get; set; }
+ public int Sequence { get; set; }
+
+ public string Title { get; set; }
+
public static FeedItem Create()
{
return new FeedItem { Id = System.Guid.NewGuid() };
@@ -59,6 +59,8 @@ namespace FeedCenter
throw new ArgumentOutOfRangeException();
}
+ title ??= string.Empty;
+
// Condense multiple spaces to one space
title = MultipleSpaceRegex().Replace(title, " ");
diff --git a/Application/MainWindow/MainWindow.xaml.cs b/Application/MainWindow/MainWindow.xaml.cs
index fdd6676..c789f9f 100644
--- a/Application/MainWindow/MainWindow.xaml.cs
+++ b/Application/MainWindow/MainWindow.xaml.cs
@@ -146,7 +146,7 @@ namespace FeedCenter
private void ResetDatabase()
{
// Get the ID of the current feed
- var currentId = _currentFeed?.Id ?? Guid.Empty;
+ var currentId = _currentFeed?.IsValid ?? false ? _currentFeed.Id : Guid.Empty;
// Create a new database object
_database.Refresh();
diff --git a/Application/MainWindow/Toolbar.cs b/Application/MainWindow/Toolbar.cs
index b4e55b0..7218049 100644
--- a/Application/MainWindow/Toolbar.cs
+++ b/Application/MainWindow/Toolbar.cs
@@ -1,12 +1,12 @@
-using ChrisKaczor.InstalledBrowsers;
-using FeedCenter.Options;
-using FeedCenter.Properties;
-using System.IO;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Web.UI;
using System.Windows;
using System.Windows.Controls;
+using ChrisKaczor.InstalledBrowsers;
+using FeedCenter.Options;
+using FeedCenter.Properties;
namespace FeedCenter
{
@@ -59,18 +59,16 @@ namespace FeedCenter
// Create the options form
var optionsWindow = new OptionsWindow { Owner = this };
- // Show the options form and get the result
- var result = optionsWindow.ShowDialog();
+ // Show the options window
+ optionsWindow.ShowDialog();
- // If okay was selected
- if (result.HasValue && result.Value)
- {
- // Refresh the database to current settings
- ResetDatabase();
+ // Refresh the database to current settings
+ ResetDatabase();
- // Re-initialize the feed display
- DisplayFeed();
- }
+ // Re-initialize the feed display
+ DisplayFeed();
+
+ UpdateErrorLink();
}
private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
@@ -84,19 +82,15 @@ namespace FeedCenter
var feedErrorWindow = new FeedErrorWindow();
// Display the window
- var result = feedErrorWindow.Display(this);
+ feedErrorWindow.Display(this);
- // If okay was selected
- if (result.GetValueOrDefault())
- {
- // Refresh the database to current settings
- ResetDatabase();
+ // Refresh the database to current settings
+ ResetDatabase();
- // Re-initialize the feed display
- DisplayFeed();
+ // Re-initialize the feed display
+ DisplayFeed();
- UpdateErrorLink();
- }
+ UpdateErrorLink();
}
private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
diff --git a/Application/Options/FeedWindow.xaml b/Application/Options/FeedWindow.xaml
index b2390e2..bd8016f 100644
--- a/Application/Options/FeedWindow.xaml
+++ b/Application/Options/FeedWindow.xaml
@@ -6,7 +6,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance Type=feedCenter:Feed}"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:validation="clr-namespace:ChrisKaczor.Wpf.Validation;assembly=ChrisKaczor.Wpf.Validation"
mc:Ignorable="d"
Title="FeedWindow"
Height="300"
@@ -38,15 +37,8 @@
-
-
-
-
-
-
-
-
+ Margin="6"
+ Text="{Binding Path=Source, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />