40 Commits

Author SHA1 Message Date
477185341e Branch and config updates 2023-04-29 19:03:25 -04:00
1db13fbe2e Update installer to detect .NET runtime 2023-04-29 18:42:53 -04:00
c64ec21bd9 Adjust feed read exception handling 2023-04-28 14:44:01 -04:00
45009a31b9 ID doesn't work in a release build for some reason 2023-04-28 13:02:18 -04:00
2a7ef356bd Set tray icon ID 2023-04-28 12:50:49 -04:00
105b6e7d30 Update package versions 2023-04-28 12:47:40 -04:00
965f753489 Update packages 2023-04-28 08:00:17 -04:00
019ebd46be Try restoring the application as well 2023-04-27 21:35:49 -04:00
33a3aac48c Try workaround for AppVeyor 2023-04-27 21:23:33 -04:00
93a839f7f3 Add back restore step 2023-04-27 20:56:36 -04:00
4fc28ed0c1 Update build/deploy configuration 2023-04-27 20:51:18 -04:00
9e10ac0ab9 Update minor version 2023-04-26 22:19:25 -04:00
ac7c20e6bf Fix toolbar button handling 2023-04-26 22:14:49 -04:00
7638d9c2c7 More UI updates and cleanup 2023-04-26 21:57:19 -04:00
edd01cc052 Keep tray icon lock menu option in sync 2023-04-24 21:30:56 -04:00
fb777f4837 Add localization file to tweak text 2023-04-24 21:04:11 -04:00
fcc6cc05e9 Fix icon resource 2023-04-24 20:20:43 -04:00
504cc80470 Back to WiX 3.11 for now 2023-04-24 19:04:26 -04:00
586a0497d6 Remove WinForms references and start on installer 2023-04-22 09:05:41 -04:00
d6a2fd5a46 More UI updates 2023-04-16 12:57:17 -04:00
5c0c84a068 Rework much of options UI 2023-04-13 20:05:55 -04:00
ace251fd4f Validation WIP 2023-04-12 17:14:12 -04:00
68aec56824 UI rework and fixes 2023-04-12 17:13:43 -04:00
242663c3e5 More cleanup and UI work 2023-04-12 13:44:15 -04:00
a81cf5e69f More cleanup and fixes 2023-04-12 11:35:19 -04:00
64d0f770ca Code cleanup 2023-04-07 22:24:52 -04:00
96d327270f Some cleanup 2023-04-07 22:10:03 -04:00
49842a1663 Fix resource reference and switch to package 2023-04-07 20:23:24 -04:00
e130be35a4 Update packages 2023-04-07 20:00:13 -04:00
eac0313f23 Switch to NuGet packages 2023-04-07 18:41:18 -04:00
6514f23329 Rework database loading/migration 2023-04-06 17:20:38 -04:00
b5f570688d More modernization
- Split generic "common" libraries into specific libraries
- Use other packages in lieu of custom code
- General cleanup
2023-04-05 16:06:38 -04:00
f480a6c373 Start modernization 2023-03-10 12:18:03 -05:00
a0214b98f1 Fix parsing of Atom feeds with attribute namespaces 2018-07-18 08:16:50 -04:00
59bb8ae3cd Make sure to dispose resources created during feed reading 2018-07-17 12:02:53 -04:00
840bd1acd0 Remove post-build steps 2018-04-06 13:10:58 -04:00
ed1d5566f2 Update Common version 2018-04-06 10:29:31 -04:00
be1bd39974 Add JSON binary to installer 2018-04-06 10:13:54 -04:00
92469c15aa Switch to getting updates from GitHub 2018-04-06 09:57:00 -04:00
0826ecfce3 Try to fix line endings on SQL scripts 2018-04-05 16:39:36 -04:00
114 changed files with 5947 additions and 7963 deletions

1
.gitattributes vendored
View File

@@ -2,6 +2,7 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.sqlce text eol=crlf
###############################################################################
# Set default behavior for command prompt diff.

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "Common.Wpf"]
path = Common.Wpf
url = https://github.com/ckaczor/Common.Wpf.git
[submodule "Common"]
path = Common
url = https://github.com/ckaczor/Common.git

View File

@@ -4,7 +4,7 @@
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Common.Wpf;component/Toolbar/SplitButton/SplitButtonStyle.xaml" />
<ResourceDictionary Source="pack://application:,,,/ChrisKaczor.Wpf.Controls.Toolbar;component/SplitButton/SplitButtonStyle.xaml" />
<ResourceDictionary Source="Style.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -1,115 +1,124 @@
using Common.Debug;
using Common.Helpers;
using Common.IO;
using Common.Settings;
using Common.Wpf.Extensions;
using ChrisKaczor.GenericSettingsProvider;
using ChrisKaczor.Wpf.Application;
using FeedCenter.Data;
using FeedCenter.Properties;
using Serilog;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows.Threading;
namespace FeedCenter
namespace FeedCenter;
public partial class App
{
public partial class App
// ReSharper disable ConvertPropertyToExpressionBody
private static bool IsDebugBuild
{
// ReSharper disable ConvertPropertyToExpressionBody
private static bool IsDebugBuild
get
{
get
{
#if DEBUG
return true;
return true;
#else
return false;
#endif
}
}
// ReSharper restore ConvertPropertyToExpressionBody
[STAThread]
public static void Main()
{
// Create and initialize the app object
var app = new App();
app.InitializeComponent();
// Create an isolation handle to see if we are already running
var isolationHandle = ApplicationIsolation.GetIsolationHandle(FeedCenter.Properties.Resources.ApplicationName);
// If there is another copy then pass it the command line and exit
if (isolationHandle == null)
{
InterprocessMessageSender.SendMessage(FeedCenter.Properties.Resources.ApplicationName, Environment.CommandLine);
return;
}
// Use the handle over the lifetime of the application
using (isolationHandle)
{
// Set the data directory based on debug or not
AppDomain.CurrentDomain.SetData("DataDirectory", SystemConfiguration.DataDirectory);
// Get the generic provider
var genericProvider = (GenericSettingsProvider) Settings.Default.Providers[typeof(GenericSettingsProvider).Name];
if (genericProvider == null)
return;
// Set the callbacks into the provider
genericProvider.OpenDataStore = SettingsStore.OpenDataStore;
genericProvider.CloseDataStore = SettingsStore.CloseDataStore;
genericProvider.GetSettingValue = SettingsStore.GetSettingValue;
genericProvider.SetSettingValue = SettingsStore.SetSettingValue;
genericProvider.DeleteSettingsForVersion = SettingsStore.DeleteSettingsForVersion;
genericProvider.GetVersionList = SettingsStore.GetVersionList;
genericProvider.DeleteOldVersionsOnUpgrade = !IsDebugBuild;
// Initialize the tracer with the current process ID
Tracer.Initialize(SystemConfiguration.UserSettingsPath, FeedCenter.Properties.Resources.ApplicationName, Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture), false);
Current.DispatcherUnhandledException += HandleCurrentDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += HandleCurrentDomainUnhandledException;
// Check if we need to upgrade settings from a previous version
if (Settings.Default.FirstRun)
{
Settings.Default.Upgrade();
Settings.Default.FirstRun = false;
Settings.Default.Save();
}
// Create the main window before the splash otherwise WPF gets messed up
var mainWindow = new MainWindow();
// Show the splash window
var splashWindow = new SplashWindow();
splashWindow.ShowDialog();
// Set whether we should auto-start (if not debugging)
if (!IsDebugBuild)
Current.SetStartWithWindows(Settings.Default.StartWithWindows);
// Initialize the window
mainWindow.Initialize();
// Run the app
app.Run(mainWindow);
// Terminate the tracer
Tracer.Dispose();
}
}
private static void HandleCurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Tracer.WriteException((Exception) e.ExceptionObject);
Tracer.Flush();
}
private static void HandleCurrentDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Tracer.WriteException(e.Exception);
Tracer.Flush();
}
}
}
// ReSharper restore ConvertPropertyToExpressionBody
public static string Name => FeedCenter.Properties.Resources.ApplicationName;
[STAThread]
public static void Main()
{
// Create and initialize the app object
var app = new App();
app.InitializeComponent();
// Create an single instance handle to see if we are already running
var isolationHandle = SingleInstance.GetSingleInstanceHandleAsync(Name).Result;
// If there is another copy then pass it the command line and exit
if (isolationHandle == null)
return;
// Use the handle over the lifetime of the application
using (isolationHandle)
{
// Set the path
LegacyDatabase.DatabasePath = SystemConfiguration.DataDirectory;
LegacyDatabase.DatabaseFile = Path.Combine(SystemConfiguration.DataDirectory,
Settings.Default.DatabaseFile_Legacy);
Database.DatabasePath = SystemConfiguration.DataDirectory;
Database.DatabaseFile = Path.Combine(SystemConfiguration.DataDirectory, Settings.Default.DatabaseFile);
// Get the generic provider
var genericProvider =
(GenericSettingsProvider) Settings.Default.Providers[nameof(GenericSettingsProvider)];
if (genericProvider == null)
return;
// Set the callbacks into the provider
genericProvider.OpenDataStore = SettingsStore.OpenDataStore;
genericProvider.GetSettingValue = SettingsStore.GetSettingValue;
genericProvider.SetSettingValue = SettingsStore.SetSettingValue;
Log.Logger = new LoggerConfiguration()
.Enrich.WithThreadId()
.WriteTo.Console()
.WriteTo.File(
Path.Join(SystemConfiguration.UserSettingsPath,
$"{FeedCenter.Properties.Resources.ApplicationName}_.txt"),
rollingInterval: RollingInterval.Day, retainedFileCountLimit: 5,
outputTemplate: "[{Timestamp:u} - {ThreadId} - {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
Log.Logger.Information("---");
Log.Logger.Information("Application started");
Log.Logger.Information("Command line arguments:");
foreach (var arg in Environment.GetCommandLineArgs()
.Select((value, index) => (Value: value, Index: index)))
Log.Logger.Information("\tArg {0}: {1}", arg.Index, arg.Value);
Current.DispatcherUnhandledException += HandleCurrentDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += HandleCurrentDomainUnhandledException;
// Check if we need to upgrade settings from a previous version
if (Settings.Default.FirstRun)
{
Settings.Default.Upgrade();
Settings.Default.FirstRun = false;
Settings.Default.Save();
}
// Create the main window before the splash otherwise WPF gets messed up
var mainWindow = new MainWindow();
// Show the splash window
var splashWindow = new SplashWindow();
splashWindow.ShowDialog();
// Set whether we should auto-start (if not debugging)
if (!IsDebugBuild)
Current.SetStartWithWindows(Settings.Default.StartWithWindows);
// Run the app
app.Run(mainWindow);
}
}
private static void HandleCurrentDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Log.Logger.Error((Exception) e.ExceptionObject, "Exception");
}
private static void HandleCurrentDispatcherUnhandledException(object sender,
DispatcherUnhandledExceptionEventArgs e)
{
Log.Logger.Error(e.Exception, "Exception");
}
}

View File

@@ -1,65 +0,0 @@
using Common.Debug;
using Common.Internet;
using FeedCenter.Properties;
using System;
using System.Diagnostics;
namespace FeedCenter
{
public static class BrowserCommon
{
public static Browser FindBrowser(string browserKey)
{
Browser browser = null;
// Get the list of installed browsers
var browsers = Browser.DetectInstalledBrowsers();
// Make sure the desired browser exists
if (browsers.ContainsKey(browserKey))
{
// Get the browser
browser = browsers[browserKey];
}
return browser;
}
public static bool OpenLink(string url)
{
// Get the browser
Browser browser = FindBrowser(Settings.Default.Browser);
// Start the browser
return OpenLink(browser, url);
}
public static bool OpenLink(Browser browser, string url)
{
try
{
// Don't bother with empty links
if (String.IsNullOrEmpty(url))
return true;
// Add quotes around the URL for safety
url = $"\"{url}\"";
// Start the browser
if (browser == null)
Process.Start(url);
else
Process.Start(browser.Command, url);
return true;
}
catch (Exception exception)
{
// Just log the exception
Tracer.WriteException(exception);
return false;
}
}
}
}

View File

@@ -1,27 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FeedCenter
{
using System;
using System.Collections.Generic;
public partial class Category
{
public Category()
{
this.Feeds = new HashSet<Feed>();
}
public System.Guid ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Feed> Feeds { get; set; }
}
}

View File

@@ -1,223 +1,24 @@
using Common.Debug;
using FeedCenter.Properties;
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.IO;
namespace FeedCenter.Data
namespace FeedCenter.Data;
public static class Database
{
public static class Database
public static string DatabaseFile { get; set; }
public static string DatabasePath { get; set; }
public static FeedCenterEntities Entities { get; set; }
public static bool Exists => File.Exists(DatabaseFile);
public static bool Loaded { get; set; }
public static void Load()
{
#region Static database settings
if (Loaded) return;
public static string DatabasePath;
Entities = new FeedCenterEntities();
#endregion
#region File version
private enum SqlServerCeFileVersion
{
Unknown,
Version20,
Version30,
Version35,
Version40,
}
private static SqlServerCeFileVersion GetFileVersion(string databasePath)
{
// Create a mapping of version numbers to the version enumeration
var versionMapping = new Dictionary<int, SqlServerCeFileVersion>
{
{ 0x73616261, SqlServerCeFileVersion.Version20 },
{ 0x002dd714, SqlServerCeFileVersion.Version30 },
{ 0x00357b9d, SqlServerCeFileVersion.Version35 },
{ 0x003d0900, SqlServerCeFileVersion.Version40 }
};
int signature;
try
{
// Open the database file
using (var stream = new FileStream(databasePath, FileMode.Open, FileAccess.Read))
{
// Read the file using the binary reader
var reader = new BinaryReader(stream);
// Seek to the version signature
stream.Seek(16, SeekOrigin.Begin);
// Read the version signature
signature = reader.ReadInt32();
}
}
catch (Exception exception)
{
Tracer.WriteException(exception);
throw;
}
// If we know about the version number then return the right enumeration - otherwise unknown
return versionMapping.ContainsKey(signature) ? versionMapping[signature] : SqlServerCeFileVersion.Unknown;
}
#endregion
public static bool DatabaseExists => File.Exists(DatabasePath);
public static void CreateDatabase()
{
Tracer.WriteLine("Creating database engine");
// Create the database engine
using (var engine = new SqlCeEngine($"Data Source={DatabasePath}"))
{
Tracer.WriteLine("Creating database");
// Create the database itself
engine.CreateDatabase();
Tracer.WriteLine("Running database script");
// Run the creation script
ExecuteScript(Resources.CreateDatabase);
}
}
private static int GetVersion(SqlCeConnection connection)
{
string versionString;
try
{
// Check the database version table
using (var command = new SqlCeCommand("SELECT Value FROM DatabaseVersion", connection))
versionString = command.ExecuteScalar().ToString();
}
catch (SqlCeException)
{
// Check the setting table for the version
using (var command = new SqlCeCommand("SELECT Value FROM Setting WHERE Name = 'DatabaseVersion'", connection))
versionString = command.ExecuteScalar().ToString();
}
if (string.IsNullOrEmpty(versionString))
versionString = "0";
Tracer.WriteLine("Database version: {0}", versionString);
return int.Parse(versionString);
}
public static void UpdateDatabase()
{
Tracer.WriteLine("Getting database file version");
// Get the database file version
var fileVersion = GetFileVersion(DatabasePath);
Tracer.WriteLine("Database file version: {0}", fileVersion);
// See if we need to upgrade the database file version
if (fileVersion != SqlServerCeFileVersion.Version40)
{
Tracer.WriteLine("Creating database engine");
// Create the database engine
using (var engine = new SqlCeEngine($"Data Source={DatabasePath}"))
{
Tracer.WriteLine("Upgrading database");
// Upgrade the database (if needed)
engine.Upgrade();
}
}
Tracer.WriteLine("Getting database version");
// Create a database connection
using (var connection = new SqlCeConnection($"Data Source={DatabasePath}"))
{
// Open the connection
connection.Open();
// Get the database version
var databaseVersion = GetVersion(connection);
// Create a dictionary of database upgrade scripts and their version numbers
var scriptList = new Dictionary<int, string>();
// Loop over the properties of the resource object looking for update scripts
foreach (var property in typeof(Resources).GetProperties().Where(property => property.Name.StartsWith("DatabaseUpdate")))
{
// Get the name of the property
var propertyName = property.Name;
// Extract the version from the name
var version = int.Parse(propertyName.Substring(propertyName.IndexOf("_", StringComparison.Ordinal) + 1));
// Add to the script list
scriptList[version] = propertyName;
}
// Loop over the scripts ordered by version
foreach (var pair in scriptList.OrderBy(pair => pair.Key))
{
// If the database version is less than or equal to the script version the script needs to run
if (databaseVersion <= pair.Key)
{
// Get the script text
var scriptText = Resources.ResourceManager.GetString(pair.Value);
// Run the script
ExecuteScript(scriptText);
}
}
}
}
public static void MaintainDatabase()
{
Tracer.WriteLine("Creating database engine");
// Create the database engine
using (var engine = new SqlCeEngine($"Data Source={DatabasePath}"))
{
Tracer.WriteLine("Shrinking database");
// Compact the database
engine.Shrink();
}
}
private static void ExecuteScript(string scriptText)
{
// Create a database connection
using (var connection = new SqlCeConnection($"Data Source={DatabasePath}"))
{
// Open the connection
connection.Open();
// Setup the delimiters
var delimiters = new[] { "\r\nGO\r\n" };
// Split the script at the delimiters
var statements = scriptText.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
// Loop over each statement in the script
foreach (var statement in statements)
{
// Execute the statement
using (var command = new SqlCeCommand(statement, connection))
command.ExecuteNonQuery();
}
}
}
Loaded = true;
}
}
}

View File

@@ -1,13 +0,0 @@
using System.Data.SqlTypes;
namespace FeedCenter.Data
{
public static class Extensions
{
#region SqlDateTime
public static SqlDateTime SqlDateTimeZero = new SqlDateTime(0, 0);
#endregion
}
}

View File

@@ -0,0 +1,234 @@
using Dapper;
using FeedCenter.Options;
using FeedCenter.Properties;
using Realms;
using Serilog;
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
namespace FeedCenter.Data;
public static class LegacyDatabase
{
public static string DatabaseFile { get; set; }
public static string DatabasePath { get; set; }
private enum SqlServerCeFileVersion
{
Unknown,
Version20,
Version30,
Version35,
Version40,
}
private static SqlServerCeFileVersion GetFileVersion(string databasePath)
{
// Create a mapping of version numbers to the version enumeration
var versionMapping = new Dictionary<int, SqlServerCeFileVersion>
{
{ 0x73616261, SqlServerCeFileVersion.Version20 },
{ 0x002dd714, SqlServerCeFileVersion.Version30 },
{ 0x00357b9d, SqlServerCeFileVersion.Version35 },
{ 0x003d0900, SqlServerCeFileVersion.Version40 }
};
int signature;
try
{
// Open the database file
using var stream = new FileStream(databasePath, FileMode.Open, FileAccess.Read);
// Read the file using the binary reader
var reader = new BinaryReader(stream);
// Seek to the version signature
stream.Seek(16, SeekOrigin.Begin);
// Read the version signature
signature = reader.ReadInt32();
}
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception");
throw;
}
// If we know about the version number then return the right enumeration - otherwise unknown
return versionMapping.TryGetValue(signature, out var value) ? value : SqlServerCeFileVersion.Unknown;
}
public static bool Exists => File.Exists(DatabaseFile);
private static int GetVersion(SqlCeConnection connection)
{
string versionString;
try
{
// Check the database version table
using var command = new SqlCeCommand("SELECT Value FROM DatabaseVersion", connection);
versionString = command.ExecuteScalar().ToString();
}
catch (SqlCeException)
{
// Check the setting table for the version
using var command = new SqlCeCommand("SELECT Value FROM Setting WHERE Name = 'DatabaseVersion'", connection);
versionString = command.ExecuteScalar().ToString();
}
if (string.IsNullOrEmpty(versionString))
versionString = "0";
Log.Logger.Information("Database version: {0}", versionString);
return int.Parse(versionString);
}
public static void UpdateDatabase()
{
Log.Logger.Information("Getting database file version");
// Get the database file version
var fileVersion = GetFileVersion(DatabaseFile);
Log.Logger.Information("Database file version: {0}", fileVersion);
// See if we need to upgrade the database file version
if (fileVersion != SqlServerCeFileVersion.Version40)
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Upgrading database");
// Upgrade the database (if needed)
engine.Upgrade();
}
Log.Logger.Information("Getting database version");
// Create a database connection
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
// Open the connection
connection.Open();
// Get the database version
var databaseVersion = GetVersion(connection);
// Create a dictionary of database upgrade scripts and their version numbers
var scriptList = new Dictionary<int, string>();
// Loop over the properties of the resource object looking for update scripts
foreach (var property in typeof(Resources).GetProperties().Where(property => property.Name.StartsWith("DatabaseUpdate")))
{
// Get the name of the property
var propertyName = property.Name;
// Extract the version from the name
var version = int.Parse(propertyName[(propertyName.IndexOf("_", StringComparison.Ordinal) + 1)..]);
// Add to the script list
scriptList[version] = propertyName;
}
// Loop over the scripts ordered by version
foreach (var pair in scriptList.OrderBy(pair => pair.Key))
{
// If the database version is beyond this script then we can skip it
if (databaseVersion > pair.Key) continue;
// Get the script text
var scriptText = Resources.ResourceManager.GetString(pair.Value);
// Run the script
ExecuteScript(scriptText);
}
}
public static void MaintainDatabase()
{
Log.Logger.Information("Creating database engine");
// Create the database engine
using var engine = new SqlCeEngine($"Data Source={DatabaseFile}");
Log.Logger.Information("Shrinking database");
// Compact the database
engine.Shrink();
}
public static void MigrateDatabase()
{
var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}");
var realm = Realm.GetInstance(realmConfiguration);
if (!File.Exists(DatabaseFile))
return;
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
connection.Open();
var settings = connection.Query<Setting>("SELECT * FROM Setting").OrderBy(s => s.Version).ToList();
var categories = connection.Query<Category>("SELECT * FROM Category").ToList();
var feeds = connection.Query<Feed>("SELECT * FROM Feed").ToList();
var feedItems = connection.Query<FeedItem>("SELECT * FROM FeedItem").ToList();
realm.Write(() =>
{
foreach (var category in categories)
{
category.IsDefault = category.Name == Category.DefaultName;
}
foreach (var feed in feeds)
{
feed.CategoryId = categories.First(c => c.Id == feed.CategoryId).Id;
}
foreach (var feedItem in feedItems)
{
var feed = feeds.First(f => f.Id == feedItem.FeedId);
feed.Items.Add(feedItem);
}
realm.Add(feeds);
realm.Add(categories);
realm.Add(settings, true);
});
connection.Close();
File.Move(DatabaseFile, DatabaseFile + "_bak");
}
private static void ExecuteScript(string scriptText)
{
// Create a database connection
using var connection = new SqlCeConnection($"Data Source={DatabaseFile}");
// Open the connection
connection.Open();
// Setup the delimiters
var delimiters = new[] { "\r\nGO\r\n" };
// Split the script at the delimiters
var statements = scriptText.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
// Loop over each statement in the script
foreach (var statement in statements)
{
// Execute the statement
using var command = new SqlCeCommand(statement, connection);
command.ExecuteNonQuery();
}
}
}

View File

@@ -0,0 +1,28 @@
using Realms;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace FeedCenter.Data;
public class RealmObservableCollection<T> : ObservableCollection<T> where T : IRealmObject
{
private readonly Realm _realm;
public RealmObservableCollection(Realm realm) : base(realm.All<T>())
{
_realm = realm;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (T item in e.OldItems)
_realm.Remove(item);
if (e.NewItems != null)
foreach (T item in e.NewItems)
_realm.Add(item);
base.OnCollectionChanged(e);
}
}

Binary file not shown.

View File

@@ -1,139 +1,52 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Entity.Infrastructure;
using FeedCenter.Data;
using FeedCenter.Options;
using Realms;
using System;
using System.Linq;
namespace FeedCenter
namespace FeedCenter;
public class FeedCenterEntities
{
public partial class FeedCenterEntities
public Realm RealmInstance { get; }
public RealmObservableCollection<Category> Categories { get; }
public RealmObservableCollection<Feed> Feeds { get; private set; }
public RealmObservableCollection<Setting> Settings { get; private set; }
public FeedCenterEntities()
{
#region Dispose
var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}");
protected override void Dispose(bool disposing)
RealmInstance = Realm.GetInstance(realmConfiguration);
Settings = new RealmObservableCollection<Setting>(RealmInstance);
Feeds = new RealmObservableCollection<Feed>(RealmInstance);
Categories = new RealmObservableCollection<Category>(RealmInstance);
if (!Categories.Any())
{
if (disposing)
{
var manager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
manager.ObjectStateManagerChanged -= HandleObjectStateManagerObjectStateManagerChanged;
_hookedStateManager = false;
}
base.Dispose(disposing);
RealmInstance.Write(() => Categories.Add(Category.CreateDefault()));
}
#endregion
private bool _hookedStateManager;
#region All categories
private ObservableCollection<Category> _allCategories;
public ObservableCollection<Category> AllCategories
{
get
{
if (_allCategories == null)
{
_allCategories = new ObservableCollection<Category>(Categories);
if (!_hookedStateManager)
{
var manager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
manager.ObjectStateManagerChanged += HandleObjectStateManagerObjectStateManagerChanged;
_hookedStateManager = true;
}
}
return _allCategories;
}
}
public Category DefaultCategory
{
get { return AllCategories.First(c => c.IsDefault); }
}
#endregion
#region All feeds
private ObservableCollection<Feed> _allFeeds;
public ObservableCollection<Feed> AllFeeds
{
get
{
if (_allFeeds == null)
{
_allFeeds = new ObservableCollection<Feed>(Feeds);
if (!_hookedStateManager)
{
var manager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
manager.ObjectStateManagerChanged += HandleObjectStateManagerObjectStateManagerChanged;
_hookedStateManager = true;
}
}
return _allFeeds;
}
}
#endregion
#region Object state manager
void HandleObjectStateManagerObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
{
var element = e.Element as Category;
if (element != null)
{
if (_allCategories == null)
return;
var category = element;
switch (e.Action)
{
case CollectionChangeAction.Add:
_allCategories.Add(category);
break;
case CollectionChangeAction.Remove:
_allCategories.Remove(category);
break;
case CollectionChangeAction.Refresh:
_allCategories.Clear();
foreach (var loopCategory in Categories)
_allCategories.Add(loopCategory);
break;
}
}
else if (e.Element is Feed)
{
if (_allFeeds == null)
return;
var feed = (Feed) e.Element;
switch (e.Action)
{
case CollectionChangeAction.Add:
_allFeeds.Add(feed);
break;
case CollectionChangeAction.Remove:
_allFeeds.Remove(feed);
break;
case CollectionChangeAction.Refresh:
_allFeeds.Clear();
foreach (var loopfeed in Feeds)
_allFeeds.Add(loopfeed);
break;
}
}
}
#endregion
}
}
public void Refresh()
{
RealmInstance.Refresh();
}
public void SaveChanges(Action action)
{
RealmInstance.Write(action);
}
public Transaction BeginTransaction()
{
return RealmInstance.BeginWrite();
}
public Category DefaultCategory
{
get { return Categories.First(c => c.IsDefault); }
}
}

View File

@@ -1,59 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FeedCenter
{
using System;
using System.Collections.Generic;
public partial class Feed
{
public Feed()
{
this.Name = "";
this.Title = "";
this.Source = "";
this.Link = "";
this.Description = "";
this.LastChecked = new DateTime(599266080000000000, DateTimeKind.Unspecified);
this.CheckInterval = 60;
this.Enabled = true;
this.Authenticate = false;
this.Username = "";
this.Password = "";
this.Domain = "";
this.LastUpdated = new DateTime(599266080000000000, DateTimeKind.Unspecified);
this.Actions = new HashSet<FeedAction>();
this.Items = new HashSet<FeedItem>();
}
public System.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 System.DateTime LastChecked { get; set; }
public int CheckInterval { get; set; }
public bool Enabled { get; set; }
public bool Authenticate { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Domain { get; set; }
public FeedCenter.FeedReadResult LastReadResult { get; set; }
public System.DateTime LastUpdated { get; set; }
public FeedCenter.FeedItemComparison ItemComparison { get; set; }
private System.Guid CategoryID { get; set; }
public FeedCenter.MultipleOpenAction MultipleOpenAction { get; set; }
public virtual Category Category { get; set; }
public virtual ICollection<FeedAction> Actions { get; set; }
public virtual ICollection<FeedItem> Items { get; set; }
}
}

View File

@@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FeedCenter
{
using System;
using System.Collections.Generic;
public partial class FeedAction
{
public System.Guid ID { get; set; }
public System.Guid FeedID { get; set; }
public int Field { get; set; }
public string Search { get; set; }
public string Replace { get; set; }
public int Sequence { get; set; }
public virtual Feed Feed { get; set; }
}
}

View File

@@ -1,473 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{BD3D12F2-DE23-4466-83B1-1EB617A877A4}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FeedCenter</RootNamespace>
<AssemblyName>FeedCenter</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<IsWebBootstrapper>false</IsWebBootstrapper>
<SccProjectName>
</SccProjectName>
<SccLocalPath>
</SccLocalPath>
<SccAuxPath>
</SccAuxPath>
<SccProvider>
</SccProvider>
<PublishUrl>\\server\d\FeedCenter\</PublishUrl>
<Install>true</Install>
<InstallFrom>Unc</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ProductName>Feed Center</ProductName>
<PublisherName>Feed Center</PublisherName>
<CreateWebPageOnPublish>true</CreateWebPageOnPublish>
<WebPage>Publish.html</WebPage>
<OpenBrowserOnPublish>false</OpenBrowserOnPublish>
<ApplicationRevision>215</ApplicationRevision>
<ApplicationVersion>0.1.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject>
</StartupObject>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\Application.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<TargetZone>LocalIntranet</TargetZone>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>false</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<CodeAnalysisLogFile>bin\Debug\FeedCenter.exe.CodeAnalysisLog.xml</CodeAnalysisLogFile>
<CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
<CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRuleSetDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets</CodeAnalysisRuleSetDirectories>
<CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>
<CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
<CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
<UseVSHostingProcess>true</UseVSHostingProcess>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<CodeAnalysisLogFile>bin\Release\FeedCenter.exe.CodeAnalysisLog.xml</CodeAnalysisLogFile>
<CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
<CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.cs</CodeAnalysisModuleSuppressionsFile>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisRuleSetDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets</CodeAnalysisRuleSetDirectories>
<CodeAnalysisRuleDirectories>;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServer">
<HintPath>..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServerCompact">
<HintPath>..\packages\EntityFramework.SqlServerCompact.6.1.1\lib\net45\EntityFramework.SqlServerCompact.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.9.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NameBasedGrid, Version=0.10.1.0, Culture=neutral, PublicKeyToken=a434c4ad23d0fd33, processorArchitecture=MSIL">
<HintPath>..\packages\NameBasedGrid.0.10.1\lib\net40\NameBasedGrid.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.configuration" />
<Reference Include="System.Data" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8854.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8854.1\lib\net40\System.Data.SqlServerCe.Entity.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Compile Include="FeedChooserWindow.xaml.cs">
<DependentUpon>FeedChooserWindow.xaml</DependentUpon>
</Compile>
<Compile Include="MainWindow\CategoryList.cs" />
<Compile Include="MainWindow\CommandLine.cs" />
<Compile Include="MainWindow\DragDrop.cs" />
<Compile Include="MainWindow\FeedCreation.cs" />
<Compile Include="MainWindow\FeedList.cs" />
<Compile Include="MainWindow\FeedReading.cs" />
<Compile Include="MainWindow\Header.cs" />
<Compile Include="MainWindow\Timer.cs" />
<Compile Include="MainWindow\Toolbar.cs" />
<Compile Include="MainWindow\WindowHandler.cs" />
<Compile Include="SystemConfiguration.cs" />
<Compile Include="MainWindow\UpdateHandler.cs" />
<Page Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="BrowserCommon.cs" />
<Compile Include="Category.cs">
<DependentUpon>Model.tt</DependentUpon>
</Compile>
<Compile Include="Data\Extensions.cs" />
<Compile Include="Entities.cs" />
<Compile Include="Feed.cs">
<DependentUpon>Model.tt</DependentUpon>
</Compile>
<Compile Include="FeedAction.cs">
<DependentUpon>Model.tt</DependentUpon>
</Compile>
<Compile Include="FeedErrorWindow.xaml.cs">
<DependentUpon>FeedErrorWindow.xaml</DependentUpon>
</Compile>
<Compile Include="FeedItem.cs">
<DependentUpon>Model.tt</DependentUpon>
</Compile>
<Compile Include="Feeds\Category.cs" />
<Compile Include="Model.Context.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Model.Context.tt</DependentUpon>
</Compile>
<Compile Include="Model.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Model.tt</DependentUpon>
</Compile>
<Compile Include="Model.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Model.edmx</DependentUpon>
</Compile>
<Compile Include="NotificationIcon.cs" />
<Compile Include="Options\AboutOptionsPanel.xaml.cs">
<DependentUpon>AboutOptionsPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Options\BulkFeedWindow.xaml.cs">
<DependentUpon>BulkFeedWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Options\CategoryWindow.xaml.cs">
<DependentUpon>CategoryWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Options\DisplayOptionsPanel.xaml.cs">
<DependentUpon>DisplayOptionsPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Options\FeedsOptionsPanel.xaml.cs">
<DependentUpon>FeedsOptionsPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Options\FeedWindow.xaml.cs">
<DependentUpon>FeedWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Options\GeneralOptionsPanel.xaml.cs">
<DependentUpon>GeneralOptionsPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Options\Options.cs" />
<Compile Include="Options\OptionsPanelBase.cs" />
<Compile Include="Options\OptionsWindow.xaml.cs">
<DependentUpon>OptionsWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Options\UpdateOptionsPanel.xaml.cs">
<DependentUpon>UpdateOptionsPanel.xaml</DependentUpon>
</Compile>
<Compile Include="Setting.cs">
<DependentUpon>Model.tt</DependentUpon>
</Compile>
<Compile Include="SettingsStore.cs" />
<Compile Include="SplashWindow.xaml.cs">
<DependentUpon>SplashWindow.xaml</DependentUpon>
</Compile>
<Page Include="FeedChooserWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="FeedErrorWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="MainWindow\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Data\Database.cs" />
<Compile Include="FeedParsers\AtomParser.cs" />
<Compile Include="FeedParsers\FeedParserBase.cs" />
<Compile Include="FeedParsers\RdfParser.cs" />
<Compile Include="FeedParsers\RssParser.cs" />
<Compile Include="Feeds\Feed.cs" />
<Compile Include="Feeds\FeedAction.cs" />
<Compile Include="Feeds\FeedItem.cs" />
<Compile Include="MainWindow\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="Options\AboutOptionsPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\BulkFeedWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\CategoryWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\DisplayOptionsPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\FeedsOptionsPanel.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\FeedWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\GeneralOptionsPanel.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Options\OptionsWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Options\UpdateOptionsPanel.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="SplashWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Style.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<None Include="app.config">
<SubType>Designer</SubType>
</None>
<EntityDeploy Include="Model.edmx">
<Generator>EntityModelCodeGenerator</Generator>
<LastGenOutput>Model.Designer.cs</LastGenOutput>
</EntityDeploy>
<None Include="Model.Context.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Model.Context.cs</LastGenOutput>
<DependentUpon>Model.edmx</DependentUpon>
</None>
<None Include="Model.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Model.cs</LastGenOutput>
<DependentUpon>Model.edmx</DependentUpon>
</None>
<None Include="packages.config" />
<None Include="Properties\app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Application.ico" />
</ItemGroup>
<ItemGroup>
<None Include="Scripts\DatabaseUpdate_6.sqlce" />
<None Include="Scripts\DatabaseUpdate_5.sqlce" />
<None Include="Scripts\DatabaseUpdate_4.sqlce" />
<None Include="Scripts\DatabaseUpdate_3.sqlce" />
<None Include="Scripts\DatabaseUpdate_2.sqlce" />
<None Include="Scripts\DatabaseUpdate_1.sqlce" />
<None Include="Scripts\CreateDatabase.sqlce" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.4.5">
<Visible>False</Visible>
<ProductName>Windows Installer 4.5</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Rss-Download.ico" />
<Resource Include="Resources\Comments-edit.ico" />
<Resource Include="Resources\Warning.ico" />
<Resource Include="Resources\News.ico" />
<Resource Include="Resources\Compile.ico" />
<Resource Include="Resources\Left.ico" />
<Resource Include="Resources\Right.ico" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common.Wpf\Common.Native\Common.Native.csproj">
<Project>{ed1c07a1-54f5-4796-8b06-2a0bb1960d84}</Project>
<Name>Common.Native</Name>
</ProjectReference>
<ProjectReference Include="..\Common.Wpf\Common.Wpf.csproj">
<Project>{0074c983-550e-4094-9e8c-f566fb669297}</Project>
<Name>Common.Wpf</Name>
</ProjectReference>
<ProjectReference Include="..\Common\Common.csproj">
<Project>{17864d82-457d-4a0a-bc10-1d07f2b3a5d6}</Project>
<Name>Common</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8854.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8854.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net70-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>false</UseWindowsForms>
<UseWPF>true</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
</PropertyGroup>
<PropertyGroup>
<EnableDefaultApplicationDefinition>false</EnableDefaultApplicationDefinition>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>false</GenerateManifests>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\Application.ico" />
<None Remove="Resources\Comments-edit.ico" />
<None Remove="Resources\Compile.ico" />
<None Remove="Resources\Left.ico" />
<None Remove="Resources\News.ico" />
<None Remove="Resources\Right.ico" />
<None Remove="Resources\Rss-Download.ico" />
<None Remove="Resources\Warning.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ChrisKaczor.ApplicationUpdate" Version="1.0.5" />
<PackageReference Include="ChrisKaczor.GenericSettingsProvider" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.InstalledBrowsers" Version="1.0.4" />
<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.3" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.Link" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.Toolbar" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Validation" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.ControlBox" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.SnappingWindow" Version="1.0.3" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="DebounceThrottle" Version="2.0.0" />
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.0.108" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="HtmlTextWriter" Version="2.1.1" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="Microsoft.SqlServer.Compact" Version="4.0.8876.1" GeneratePathProperty="true">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.1" />
<PackageReference Include="NameBasedGrid" Version="0.10.1">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Realm" Version="10.21.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Application.ico" />
<Resource Include="Resources\Comments-edit.ico" />
<Resource Include="Resources\Compile.ico" />
<Resource Include="Resources\Left.ico" />
<Resource Include="Resources\News.ico" />
<Resource Include="Resources\Right.ico" />
<Resource Include="Resources\Rss-Download.ico" />
<Resource Include="Resources\Warning.ico" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<Target Name="AddSqlServerCompact_x86" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)' == 'win-x86'">
<Exec Command="xcopy /s /y /i &quot;$(PkgMicrosoft_SqlServer_Compact)\NativeBinaries\x86\*.*&quot; &quot;$(PublishDir)x86&quot;" />
</Target>
<Target Name="AddSqlServerCompact_x64" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)' == 'win-x64'">
<Exec Command="xcopy /s /y /i &quot;$(PkgMicrosoft_SqlServer_Compact)\NativeBinaries\amd64\*.*&quot; &quot;$(PublishDir)amd64&quot;" />
</Target>
<PropertyGroup>
<PostBuildEvent>
xcopy /s /y /i "$(PkgMicrosoft_SqlServer_Compact)\NativeBinaries\x86\*.*" "$(TargetDir)x86"
xcopy /s /y /i "$(PkgMicrosoft_SqlServer_Compact)\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"
</PostBuildEvent>
<ApplicationIcon>Resources\Application.ico</ApplicationIcon>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="FeedCenterEntities" Identifier="FeedCenter.FeedCenterEntities" ProviderType="Microsoft.VisualStudio.DataDesign.DataSourceProviders.EntityDataModel.EdmDataSourceProvider" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>FeedCenter.FeedCenterEntities, Model.Designer.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="FeedCenterEntities" Identifier="FeedCenter.FeedCenterEntities" ProviderType="Microsoft.VisualStudio.DataDesign.DataSourceProviders.EntityDataModel.EdmDataSourceProvider" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>FeedCenter.FeedCenterEntities, Model.Designer.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="FeedCenterEntities" Identifier="FeedCenter.FeedCenterEntities" ProviderType="Microsoft.VisualStudio.DataDesign.DataSourceProviders.EntityDataModel.EdmDataSourceProvider" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>FeedCenter.FeedCenterEntities, Model.Designer.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

View File

@@ -3,8 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:windows="clr-namespace:Common.Wpf.Windows;assembly=Common.Wpf"
xmlns:my="clr-namespace:FeedCenter.Properties"
xmlns:controlBox="clr-namespace:ChrisKaczor.Wpf.Windows;assembly=ChrisKaczor.Wpf.Windows.ControlBox"
mc:Ignorable="d"
Title="{x:Static my:Resources.FeedChooserWindow}"
Height="247.297"
@@ -12,14 +12,26 @@
WindowStartupLocation="CenterOwner"
Icon="/FeedCenter;component/Resources/Application.ico"
FocusManager.FocusedElement="{Binding ElementName=FeedDataGrid}"
windows:ControlBox.HasMaximizeButton="False"
windows:ControlBox.HasMinimizeButton="False">
<Grid>
controlBox:ControlBox.HasMaximizeButton="False"
controlBox:ControlBox.HasMinimizeButton="False">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False"
<DataGrid Grid.Row="0"
Grid.Column="0"
AutoGenerateColumns="False"
x:Name="FeedDataGrid"
CanUserReorderColumns="False"
GridLinesVisibility="None"
@@ -27,16 +39,11 @@
IsReadOnly="True"
CanUserResizeRows="False"
HeadersVisibility="Column"
Margin="6"
BorderThickness="1,1,1,1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
Background="{x:Null}"
CanUserSortColumns="True"
MouseDoubleClick="HandleMouseDoubleClick">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderThickness"
Value="0" />
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static my:Resources.FeedNameColumnHeader}"
Binding="{Binding Item2}"
@@ -44,22 +51,24 @@
SortDirection="Ascending" />
</DataGrid.Columns>
</DataGrid>
<Button Content="{x:Static my:Resources.OkayButton}"
Height="23"
IsDefault="True"
Width="75"
Click="HandleOkayButtonClick"
Margin="0,0,90,10"
Grid.Row="1"
VerticalAlignment="Bottom"
HorizontalAlignment="Right" />
<Button Content="{x:Static my:Resources.CancelButton}"
Height="23"
IsCancel="True"
Width="75"
Margin="0,0,10,10"
Grid.Row="1"
VerticalAlignment="Bottom"
HorizontalAlignment="Right" />
<StackPanel
Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal"
Margin="0,5,0,0"
HorizontalAlignment="Right">
<Button Content="{x:Static my:Resources.OkayButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
Margin="0,0,5,0"
IsDefault="True"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static my:Resources.CancelButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsCancel="True" />
</StackPanel>
</Grid>
</Window>
</Window>

View File

@@ -2,51 +2,50 @@
using System.Collections.Generic;
using System.Windows;
namespace FeedCenter
namespace FeedCenter;
public partial class FeedChooserWindow
{
public partial class FeedChooserWindow
private string _returnLink;
public FeedChooserWindow()
{
private string _returnLink;
InitializeComponent();
}
public FeedChooserWindow()
{
InitializeComponent();
}
public string Display(Window owner, List<Tuple<string, string>> rssLinks)
{
// Bind to the list
FeedDataGrid.ItemsSource = rssLinks;
FeedDataGrid.SelectedIndex = 0;
public string Display(Window owner, List<Tuple<string, string>> rssLinks)
{
// Bind to the list
FeedDataGrid.ItemsSource = rssLinks;
FeedDataGrid.SelectedIndex = 0;
// Set the window owner
Owner = owner;
// Set the window owner
Owner = owner;
ShowDialog();
ShowDialog();
return _returnLink;
}
return _returnLink;
}
private void Save()
{
var selectedItem = (Tuple<string, string>) FeedDataGrid.SelectedItem;
private void Save()
{
var selectedItem = (Tuple<string, string>) FeedDataGrid.SelectedItem;
_returnLink = selectedItem.Item1;
_returnLink = selectedItem.Item1;
Close();
}
Close();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
Save();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
private void HandleMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (FeedDataGrid.SelectedItem != null)
{
Save();
}
private void HandleMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (FeedDataGrid.SelectedItem != null)
{
Save();
}
}
}
}
}

View File

@@ -1,39 +1,52 @@
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:FeedCenter.Properties"
xmlns:linkControl="clr-namespace:Common.Wpf.LinkControl;assembly=Common.Wpf"
xmlns:windows="clr-namespace:Common.Wpf.Windows;assembly=Common.Wpf"
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"
mc:Ignorable="d"
x:Class="FeedCenter.FeedErrorWindow"
Title="{x:Static my:Resources.FeedErrorWindow}"
Height="300"
Width="550"
WindowStartupLocation="CenterOwner"
Icon="/FeedCenter;component/Resources/Application.ico"
windows:ControlBox.HasMaximizeButton="False"
windows:ControlBox.HasMinimizeButton="False">
<Grid>
controlBox:ControlBox.HasMaximizeButton="False"
controlBox:ControlBox.HasMinimizeButton="False">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="225*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False"
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DataGrid Grid.Row="0"
Grid.Column="0"
AutoGenerateColumns="False"
x:Name="FeedDataGrid"
CanUserReorderColumns="False"
GridLinesVisibility="None"
SelectionMode="Single"
IsReadOnly="True"
CanUserResizeRows="False"
BorderThickness="1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
HeadersVisibility="Column"
Margin="6,6,6,0"
Background="{x:Null}"
CanUserSortColumns="True">
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderThickness"
Value="0" />
</Style>
</DataGrid.CellStyle>
CanUserSortColumns="True"
d:DataContext="{d:DesignInstance Type=feedCenter:Feed}" SelectionChanged="FeedDataGrid_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static my:Resources.FeedNameColumnHeader}"
Binding="{Binding Name}"
@@ -49,57 +62,42 @@
</DataGrid>
<Border Grid.Row="1"
BorderThickness="1,0,1,1"
Margin="6,0,6,3"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<StackPanel Orientation="Horizontal"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<linkControl:LinkControl x:Name="EditFeedButton"
Margin="2"
Click="HandleEditFeedButtonClick"
Text="{x:Static my:Resources.EditLink}"
ToolTip="{x:Static my:Resources.EditFeedButton}" />
<linkControl:LinkControl x:Name="DeleteFeedButton"
Margin="2"
Click="HandleDeleteFeedButtonClick"
Text="{x:Static my:Resources.DeleteLink}"
ToolTip="{x:Static my:Resources.DeleteFeedButton}" />
<linkControl:LinkControl x:Name="RefreshCurrent"
Margin="2"
Click="HandleRefreshCurrentButtonClick"
Text="{x:Static my:Resources.RefreshCurrent}"
ToolTip="{x:Static my:Resources.RefreshCurrent}" />
<linkControl:LinkControl x:Name="OpenPage"
Margin="6,2,2,2"
Click="HandleOpenPageButtonClick"
Text="{x:Static my:Resources.OpenPage}"
ToolTip="{x:Static my:Resources.OpenPage}" />
<linkControl:LinkControl x:Name="OpenFeed"
Margin="2"
Click="HandleOpenFeedButtonClick"
Text="{x:Static my:Resources.OpenFeed}"
ToolTip="{x:Static my:Resources.OpenFeed}" />
<linkControl:Link x:Name="EditFeedButton"
Margin="2"
Click="HandleEditFeedButtonClick"
Text="{x:Static my:Resources.EditLink}"
ToolTip="{x:Static my:Resources.EditFeedButton}" />
<linkControl:Link x:Name="DeleteFeedButton"
Margin="2"
Click="HandleDeleteFeedButtonClick"
Text="{x:Static my:Resources.DeleteLink}"
ToolTip="{x:Static my:Resources.DeleteFeedButton}" />
<linkControl:Link x:Name="RefreshCurrent"
Margin="2"
Click="HandleRefreshCurrentButtonClick"
Text="{x:Static my:Resources.RefreshCurrent}"
ToolTip="{x:Static my:Resources.RefreshCurrent}" />
<linkControl:Link x:Name="OpenPage"
Margin="6,2,2,2"
Click="HandleOpenPageButtonClick"
Text="{x:Static my:Resources.OpenPage}"
ToolTip="{x:Static my:Resources.OpenPage}" />
<linkControl:Link x:Name="OpenFeed"
Margin="2"
Click="HandleOpenFeedButtonClick"
Text="{x:Static my:Resources.OpenFeed}"
ToolTip="{x:Static my:Resources.OpenFeed}" />
</StackPanel>
</Border>
<Grid DockPanel.Dock="Right"
Grid.Row="2"
Margin="6,3,6,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Content="{x:Static my:Resources.OkayButton}"
Height="23"
IsDefault="True"
Width="75"
Grid.Column="1"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static my:Resources.CancelButton}"
Height="23"
IsCancel="True"
Width="75"
Margin="6,0,0,0"
Grid.Column="2" />
</Grid>
<Button
Grid.Row="2"
Grid.Column="0"
Margin="0,6,0,0"
Content="{x:Static my:Resources.CloseButton}"
HorizontalAlignment="Right"
IsCancel="True" />
</Grid>
</Window>
</Window>

View File

@@ -1,131 +1,142 @@
using FeedCenter.Options;
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
namespace FeedCenter;
public partial class FeedErrorWindow
{
public partial class FeedErrorWindow
private CollectionViewSource _collectionViewSource;
public FeedErrorWindow()
{
public FeedErrorWindow()
{
InitializeComponent();
}
private FeedCenterEntities _database;
private CollectionViewSource _collectionViewSource;
public bool? Display(Window owner)
{
_database = new FeedCenterEntities();
// Create a view and sort it by name
_collectionViewSource = new CollectionViewSource { Source = _database.AllFeeds };
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
// Bind to the list
FeedDataGrid.ItemsSource = _collectionViewSource.View;
FeedDataGrid.SelectedIndex = 0;
// Set the window owner
Owner = owner;
// Show the dialog and result the result
return ShowDialog();
}
private void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{
var feed = (Feed) e.Item;
e.Accepted = (feed.LastReadResult != FeedReadResult.Success);
}
private void HandleEditFeedButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedFeed();
}
private void HandleDeleteFeedButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedFeed();
}
private void EditSelectedFeed()
{
if (FeedDataGrid.SelectedItem == null)
return;
var feed = (Feed) FeedDataGrid.SelectedItem;
var feedWindow = new FeedWindow();
feedWindow.Display(_database, feed, GetWindow(this));
}
private void DeleteSelectedFeed()
{
var feed = (Feed) FeedDataGrid.SelectedItem;
_database.Feeds.Remove(feed);
SetFeedButtonStates();
}
private void SetFeedButtonStates()
{
EditFeedButton.IsEnabled = (FeedDataGrid.SelectedItem != null);
DeleteFeedButton.IsEnabled = (FeedDataGrid.SelectedItem != null);
RefreshCurrent.IsEnabled = (FeedDataGrid.SelectedItem != null);
OpenPage.IsEnabled = (FeedDataGrid.SelectedItem != null);
OpenFeed.IsEnabled = (FeedDataGrid.SelectedItem != null);
}
private void HandleOpenPageButtonClick(object sender, RoutedEventArgs e)
{
var feed = (Feed) FeedDataGrid.SelectedItem;
BrowserCommon.OpenLink(feed.Link);
}
private void HandleOpenFeedButtonClick(object sender, RoutedEventArgs e)
{
var feed = (Feed) FeedDataGrid.SelectedItem;
BrowserCommon.OpenLink(feed.Source);
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
// Save the actual settings
_database.SaveChanges();
DialogResult = true;
Close();
}
private async void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e)
{
IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait;
var feed = (Feed) FeedDataGrid.SelectedItem;
await feed.ReadAsync(_database, true);
var selectedIndex = FeedDataGrid.SelectedIndex;
_collectionViewSource.View.Refresh();
if (selectedIndex >= FeedDataGrid.Items.Count)
FeedDataGrid.SelectedIndex = FeedDataGrid.Items.Count - 1;
else
FeedDataGrid.SelectedIndex = selectedIndex;
SetFeedButtonStates();
Mouse.OverrideCursor = null;
IsEnabled = true;
}
InitializeComponent();
}
}
public void Display(Window owner)
{
// Create a view and sort it by name
_collectionViewSource = new CollectionViewSource { Source = Database.Entities.Feeds };
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
// Bind to the list
FeedDataGrid.ItemsSource = _collectionViewSource.View;
FeedDataGrid.SelectedIndex = 0;
// Set the window owner
Owner = owner;
// Show the dialog and result the result
ShowDialog();
}
private static void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{
var feed = (Feed) e.Item;
e.Accepted = feed.LastReadResult != FeedReadResult.Success;
}
private void HandleEditFeedButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedFeed();
}
private void HandleDeleteFeedButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedFeed();
}
private void EditSelectedFeed()
{
if (FeedDataGrid.SelectedItem == null)
return;
var feed = (Feed) FeedDataGrid.SelectedItem;
var feedWindow = new FeedWindow();
feedWindow.Display(feed, GetWindow(this));
}
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.Entities.SaveChanges(() => Database.Entities.Feeds.Remove(feed));
SetFeedButtonStates();
}
private void SetFeedButtonStates()
{
var feed = FeedDataGrid.SelectedItem as Feed;
EditFeedButton.IsEnabled = feed != null;
DeleteFeedButton.IsEnabled = feed != null;
RefreshCurrent.IsEnabled = feed != null;
OpenPage.IsEnabled = feed != null && !string.IsNullOrEmpty(feed.Link);
OpenFeed.IsEnabled = FeedDataGrid.SelectedItem != null;
}
private void HandleOpenPageButtonClick(object sender, RoutedEventArgs e)
{
var feed = (Feed) FeedDataGrid.SelectedItem;
InstalledBrowser.OpenLink(Settings.Default.Browser, feed.Link);
}
private void HandleOpenFeedButtonClick(object sender, RoutedEventArgs e)
{
var feed = (Feed) FeedDataGrid.SelectedItem;
InstalledBrowser.OpenLink(Settings.Default.Browser, feed.Source);
}
private async void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e)
{
IsEnabled = false;
Mouse.OverrideCursor = Cursors.Wait;
var feedId = ((Feed) FeedDataGrid.SelectedItem).Id;
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;
_collectionViewSource.View.Refresh();
if (selectedIndex >= FeedDataGrid.Items.Count)
FeedDataGrid.SelectedIndex = FeedDataGrid.Items.Count - 1;
else
FeedDataGrid.SelectedIndex = selectedIndex;
SetFeedButtonStates();
Mouse.OverrideCursor = null;
IsEnabled = true;
}
private void FeedDataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
SetFeedButtonStates();
}
}

View File

@@ -1,38 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FeedCenter
{
using System;
using System.Collections.Generic;
public partial class FeedItem
{
public FeedItem()
{
this.Title = "";
this.Link = "";
this.Description = "";
this.LastFound = new DateTime(599266080000000000, DateTimeKind.Unspecified);
}
public System.Guid ID { get; set; }
public System.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 System.DateTime LastFound { get; set; }
public bool New { get; set; }
public string Guid { get; set; }
public int Sequence { get; set; }
public virtual Feed Feed { get; set; }
}
}

View File

@@ -1,135 +1,142 @@
using System;
using Common.Debug;
using Serilog;
using System;
using System.Xml;
namespace FeedCenter.FeedParsers
namespace FeedCenter.FeedParsers;
internal class AtomParser : FeedParserBase
{
internal class AtomParser : FeedParserBase
public AtomParser(Feed feed) : base(feed) { }
public override FeedReadResult ParseFeed(string feedText)
{
public AtomParser(Feed feed) : base(feed) { }
public override FeedReadResult ParseFeed(string feedText)
try
{
try
{
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Load the XML document from the text
document.LoadXml(feedText);
// Load the XML document from the text
document.LoadXml(feedText);
// Get the root node
XmlNode rootNode = document.DocumentElement;
// Get the root node
XmlNode rootNode = document.DocumentElement;
// If we didn't find a root node then bail
if (rootNode == null)
return FeedReadResult.UnknownError;
// If we didn't find a root node then bail
if (rootNode == null)
return FeedReadResult.UnknownError;
// Initialize the sequence number for items
var sequence = 0;
// Initialize the sequence number for items
var sequence = 0;
// Loop over all nodes in the root node
foreach (XmlNode node in rootNode.ChildNodes)
{
// Handle each node that we find
switch (node.Name)
{
case "title":
Feed.Title = System.Net.WebUtility.HtmlDecode(node.InnerText).Trim();
break;
case "link":
string rel = null;
if (node.Attributes == null)
break;
XmlNode relNode = node.Attributes["rel"];
if (relNode != null)
rel = relNode.InnerText;
if (string.IsNullOrEmpty(rel) || rel == "alternate")
Feed.Link = node.Attributes["href"].InnerText.Trim();
break;
case "subtitle":
Feed.Description = node.InnerText;
break;
case "entry":
HandleFeedItem(node, ref sequence);
break;
}
}
return FeedReadResult.Success;
}
catch (XmlException xmlException)
{
Tracer.WriteLine("XML error: " + xmlException.Message + "\n" + feedText);
return FeedReadResult.InvalidXml;
}
}
protected override FeedItem ParseFeedItem(XmlNode node)
{
// Create a new feed item
var feedItem = FeedItem.Create();
// Loop over all nodes in the feed node
foreach (XmlNode childNode in node.ChildNodes)
// Loop over all nodes in the root node
foreach (XmlNode node in rootNode.ChildNodes)
{
// Handle each node that we find
switch (childNode.Name.ToLower())
switch (node.Name)
{
case "title":
feedItem.Title = System.Net.WebUtility.HtmlDecode(childNode.InnerText).Trim();
break;
case "id":
feedItem.Guid = childNode.InnerText;
break;
case "content":
feedItem.Description = System.Net.WebUtility.HtmlDecode(childNode.InnerText);
Feed.Title = System.Net.WebUtility.HtmlDecode(node.InnerText).Trim();
break;
case "link":
string rel = null;
if (childNode.Attributes == null)
if (node.Attributes == null)
break;
XmlNode relNode = childNode.Attributes["rel"];
XmlNode relNode = GetAttribute(node, "rel");
if (relNode != null)
rel = relNode.InnerText.Trim();
rel = relNode.InnerText;
if (string.IsNullOrEmpty(rel) || rel == "alternate")
{
var link = childNode.Attributes["href"].InnerText;
Feed.Link = GetAttribute(node, "href").InnerText.Trim();
if (link.StartsWith("/"))
{
var uri = new Uri(Feed.Link);
break;
link = uri.Scheme + "://" + uri.Host + link;
}
feedItem.Link = link;
}
case "subtitle":
Feed.Description = node.InnerText;
break;
case "entry":
HandleFeedItem(node, ref sequence);
break;
}
}
if (string.IsNullOrWhiteSpace(feedItem.Guid))
feedItem.Guid = feedItem.Link;
return FeedReadResult.Success;
}
catch (XmlException xmlException)
{
Log.Logger.Error(xmlException, "Exception: {0}", feedText);
return feedItem;
return FeedReadResult.InvalidXml;
}
}
}
protected override FeedItem ParseFeedItem(XmlNode node)
{
// Create a new feed item
var feedItem = FeedItem.Create();
// Loop over all nodes in the feed node
foreach (XmlNode childNode in node.ChildNodes)
{
// Handle each node that we find
switch (childNode.Name.ToLower())
{
case "title":
feedItem.Title = System.Net.WebUtility.HtmlDecode(childNode.InnerText).Trim();
break;
case "id":
feedItem.Guid = childNode.InnerText;
break;
case "content":
feedItem.Description = System.Net.WebUtility.HtmlDecode(childNode.InnerText);
break;
case "link":
string rel = null;
if (childNode.Attributes == null)
break;
XmlNode relNode = GetAttribute(childNode, "rel");
if (relNode != null)
rel = relNode.InnerText.Trim();
if (string.IsNullOrEmpty(rel) || rel == "alternate")
{
var link = GetAttribute(childNode, "href").InnerText;
if (link.StartsWith("/"))
{
var uri = new Uri(Feed.Link);
link = uri.Scheme + "://" + uri.Host + link;
}
feedItem.Link = link;
}
break;
}
}
if (string.IsNullOrWhiteSpace(feedItem.Guid))
feedItem.Guid = feedItem.Link;
return feedItem;
}
private static XmlAttribute GetAttribute(XmlNode node, string attributeName)
{
if (node?.Attributes == null)
return null;
return node.Attributes[attributeName, node.NamespaceURI] ?? node.Attributes[attributeName];
}
}

View File

@@ -0,0 +1,7 @@
namespace FeedCenter.FeedParsers;
internal enum FeedParseError
{
Unknown = 0,
InvalidXml = 1
}

View File

@@ -0,0 +1,13 @@
using System;
namespace FeedCenter.FeedParsers;
internal class FeedParseException : ApplicationException
{
public FeedParseException(FeedParseError feedParseError)
{
ParseError = feedParseError;
}
public FeedParseError ParseError { get; set; }
}

View File

@@ -1,161 +1,132 @@
using Common.Debug;
using Serilog;
using System;
using System.Linq;
using System.Xml;
namespace FeedCenter.FeedParsers
namespace FeedCenter.FeedParsers;
internal abstract class FeedParserBase
{
[Serializable]
internal class InvalidFeedFormatException : ApplicationException
protected readonly Feed Feed;
protected FeedParserBase(Feed feed)
{
internal InvalidFeedFormatException(Exception exception)
: base(string.Empty, exception)
{
}
Feed = feed;
}
internal abstract class FeedParserBase
public abstract FeedReadResult ParseFeed(string feedText);
protected abstract FeedItem ParseFeedItem(XmlNode node);
protected void HandleFeedItem(XmlNode node, ref int sequence)
{
#region Member variables
// Build a feed item from the node
var newFeedItem = ParseFeedItem(node);
protected readonly Feed Feed;
if (newFeedItem == null)
return;
#endregion
// Check for feed items with no guid or link
if (string.IsNullOrWhiteSpace(newFeedItem.Guid) && string.IsNullOrWhiteSpace(newFeedItem.Link))
return;
#region Constructor
// Look for an item that has the same guid
var existingFeedItem = Feed.Items.FirstOrDefault(item => item.Guid == newFeedItem.Guid && item.Id != newFeedItem.Id);
protected FeedParserBase(Feed feed)
// Check to see if we already have this feed item
if (existingFeedItem == null)
{
Feed = feed;
Log.Logger.Information("New link: " + newFeedItem.Link);
// Set the item as new
newFeedItem.New = true;
// Add the item to the list
Feed.Items.Add(newFeedItem);
// Feed was updated
Feed.LastUpdated = DateTime.Now;
}
else
{
Log.Logger.Information("Existing link: " + newFeedItem.Link);
// Update the fields in the existing item
existingFeedItem.Link = newFeedItem.Link;
existingFeedItem.Title = newFeedItem.Title;
existingFeedItem.Guid = newFeedItem.Guid;
existingFeedItem.Description = newFeedItem.Description;
// Item is no longer new
existingFeedItem.New = false;
// Switch over to the existing item for the rest
newFeedItem = existingFeedItem;
}
#endregion
// Item was last seen now
newFeedItem.LastFound = Feed.LastChecked;
#region Methods
// Set the sequence
newFeedItem.Sequence = sequence;
public abstract FeedReadResult ParseFeed(string feedText);
// Increment the sequence
sequence++;
}
protected abstract FeedItem ParseFeedItem(XmlNode node);
public static FeedParserBase CreateFeedParser(Feed feed, string feedText)
{
var feedType = DetectFeedType(feedText);
protected void HandleFeedItem(XmlNode node, ref int sequence)
return feedType switch
{
// Build a feed item from the node
FeedItem newFeedItem = ParseFeedItem(node);
FeedType.Rss => new RssParser(feed),
FeedType.Rdf => new RdfParser(feed),
FeedType.Atom => new AtomParser(feed),
_ => throw new ArgumentException($"Feed type {feedType} is not supported")
};
}
if (newFeedItem == null)
return;
// Check for feed items with no guid or link
if (string.IsNullOrWhiteSpace(newFeedItem.Guid) && string.IsNullOrWhiteSpace(newFeedItem.Link))
return;
// Look for an item that has the same guid
FeedItem existingFeedItem = Feed.Items.FirstOrDefault(item => item.Guid == newFeedItem.Guid && item.ID != newFeedItem.ID);
// Check to see if we already have this feed item
if (existingFeedItem == null)
{
Tracer.WriteLine("New link: " + newFeedItem.Link);
// Associate the new item with the right feed
newFeedItem.Feed = Feed;
// Set the item as new
newFeedItem.New = true;
// Add the item to the list
Feed.Items.Add(newFeedItem);
// Feed was updated
Feed.LastUpdated = DateTime.Now;
}
else
{
Tracer.WriteLine("Existing link: " + newFeedItem.Link);
// Update the fields in the existing item
existingFeedItem.Link = newFeedItem.Link;
existingFeedItem.Title = newFeedItem.Title;
existingFeedItem.Guid = newFeedItem.Guid;
existingFeedItem.Description = newFeedItem.Description;
// Item is no longer new
existingFeedItem.New = false;
// Switch over to the existing item for the rest
newFeedItem = existingFeedItem;
}
// Item was last seen now
newFeedItem.LastFound = Feed.LastChecked;
// Set the sequence
newFeedItem.Sequence = sequence;
// Increment the sequence
sequence++;
}
#endregion
#region Parser creation and detection
public static FeedParserBase CreateFeedParser(Feed feed, string feedText)
public static FeedType DetectFeedType(string feedText)
{
try
{
FeedType feedType = DetectFeedType(feedText);
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
switch (feedType)
// Load the XML document from the text
document.LoadXml(feedText);
// Loop over all child nodes
foreach (XmlNode node in document.ChildNodes)
{
case FeedType.Rss:
return new RssParser(feed);
case FeedType.Rdf:
return new RdfParser(feed);
case FeedType.Atom:
return new AtomParser(feed);
}
throw new ArgumentException($"Feed type {feedType} is not supported");
}
public static FeedType DetectFeedType(string feedText)
{
try
{
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Load the XML document from the text
document.LoadXml(feedText);
// Loop over all child nodes
foreach (XmlNode node in document.ChildNodes)
switch (node.Name)
{
switch (node.Name)
{
case "rss":
return FeedType.Rss;
case "rss":
return FeedType.Rss;
case "rdf:RDF":
return FeedType.Rdf;
case "rdf:RDF":
return FeedType.Rdf;
case "feed":
return FeedType.Atom;
}
case "feed":
return FeedType.Atom;
}
}
// No clue!
return FeedType.Unknown;
}
catch (Exception exception)
{
Tracer.WriteException(exception);
return FeedType.Unknown;
}
// No clue!
return FeedType.Unknown;
}
catch (XmlException xmlException)
{
Log.Logger.Error(xmlException, "Exception: {0}", feedText);
#endregion
throw new FeedParseException(FeedParseError.InvalidXml);
}
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception: {0}", feedText);
throw new FeedParseException(FeedParseError.InvalidXml);
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace FeedCenter.FeedParsers;
[Serializable]
internal class InvalidFeedFormatException : ApplicationException
{
internal InvalidFeedFormatException(Exception exception)
: base(string.Empty, exception)
{
}
}

View File

@@ -1,114 +1,113 @@
using Common.Debug;
using Common.Xml;
using FeedCenter.Xml;
using Serilog;
using System.Xml;
namespace FeedCenter.FeedParsers
namespace FeedCenter.FeedParsers;
internal class RdfParser : FeedParserBase
{
internal class RdfParser : FeedParserBase
public RdfParser(Feed feed) : base(feed) { }
public override FeedReadResult ParseFeed(string feedText)
{
public RdfParser(Feed feed) : base(feed) { }
public override FeedReadResult ParseFeed(string feedText)
try
{
try
{
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Load the XML document from the text
document.LoadXml(feedText);
// Load the XML document from the text
document.LoadXml(feedText);
// Create the namespace manager
XmlNamespaceManager namespaceManager = document.GetAllNamespaces();
// Create the namespace manager
var namespaceManager = document.GetAllNamespaces();
// Get the root node
XmlNode rootNode = document.DocumentElement;
// Get the root node
XmlNode rootNode = document.DocumentElement;
// If we didn't find a root node then bail
if (rootNode == null)
return FeedReadResult.UnknownError;
// If we didn't find a root node then bail
if (rootNode == null)
return FeedReadResult.UnknownError;
// Get the channel node
XmlNode channelNode = rootNode.SelectSingleNode("default:channel", namespaceManager);
if (channelNode == null)
return FeedReadResult.InvalidXml;
// Loop over all nodes in the channel node
foreach (XmlNode node in channelNode.ChildNodes)
{
// Handle each node that we find
switch (node.Name)
{
case "title":
Feed.Title = System.Net.WebUtility.HtmlDecode(node.InnerText).Trim();
break;
case "link":
Feed.Link = node.InnerText.Trim();
break;
case "description":
Feed.Description = node.InnerText;
break;
}
}
// Initialize the sequence number for items
int sequence = 0;
// Loop over all nodes in the channel node
foreach (XmlNode node in rootNode.ChildNodes)
{
// Handle each node that we find
switch (node.Name)
{
case "item":
HandleFeedItem(node, ref sequence);
break;
}
}
return FeedReadResult.Success;
}
catch (XmlException xmlException)
{
Tracer.WriteLine("XML error: " + xmlException.Message + "\n" + feedText);
// Get the channel node
var channelNode = rootNode.SelectSingleNode("default:channel", namespaceManager);
if (channelNode == null)
return FeedReadResult.InvalidXml;
}
}
protected override FeedItem ParseFeedItem(XmlNode node)
{
// Create a new feed item
FeedItem feedItem = FeedItem.Create();
// Loop over all nodes in the feed node
foreach (XmlNode childNode in node.ChildNodes)
// Loop over all nodes in the channel node
foreach (XmlNode node in channelNode.ChildNodes)
{
// Handle each node that we find
switch (childNode.Name.ToLower())
switch (node.Name)
{
case "title":
feedItem.Title = System.Net.WebUtility.HtmlDecode(childNode.InnerText).Trim();
Feed.Title = System.Net.WebUtility.HtmlDecode(node.InnerText).Trim();
break;
case "link":
feedItem.Link = childNode.InnerText.Trim();
// RDF doesn't have a GUID node so we'll just use the link
feedItem.Guid = feedItem.Link;
Feed.Link = node.InnerText.Trim();
break;
case "description":
feedItem.Description = System.Net.WebUtility.HtmlDecode(childNode.InnerText);
Feed.Description = node.InnerText;
break;
}
}
return feedItem;
// Initialize the sequence number for items
var sequence = 0;
// Loop over all nodes in the channel node
foreach (XmlNode node in rootNode.ChildNodes)
{
// Handle each node that we find
switch (node.Name)
{
case "item":
HandleFeedItem(node, ref sequence);
break;
}
}
return FeedReadResult.Success;
}
catch (XmlException xmlException)
{
Log.Logger.Error(xmlException, "Exception: {0}", feedText);
return FeedReadResult.InvalidXml;
}
}
}
protected override FeedItem ParseFeedItem(XmlNode node)
{
// Create a new feed item
var feedItem = FeedItem.Create();
// Loop over all nodes in the feed node
foreach (XmlNode childNode in node.ChildNodes)
{
// Handle each node that we find
switch (childNode.Name.ToLower())
{
case "title":
feedItem.Title = System.Net.WebUtility.HtmlDecode(childNode.InnerText).Trim();
break;
case "link":
feedItem.Link = childNode.InnerText.Trim();
// RDF doesn't have a GUID node so we'll just use the link
feedItem.Guid = feedItem.Link;
break;
case "description":
feedItem.Description = System.Net.WebUtility.HtmlDecode(childNode.InnerText);
break;
}
}
return feedItem;
}
}

View File

@@ -1,123 +1,122 @@
using Common.Debug;
using Common.Xml;
using FeedCenter.Xml;
using Serilog;
using System;
using System.Xml;
namespace FeedCenter.FeedParsers
namespace FeedCenter.FeedParsers;
internal class RssParser : FeedParserBase
{
internal class RssParser : FeedParserBase
public RssParser(Feed feed) : base(feed) { }
public override FeedReadResult ParseFeed(string feedText)
{
public RssParser(Feed feed) : base(feed) { }
public override FeedReadResult ParseFeed(string feedText)
try
{
try
{
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Create the XML document
var document = new XmlDocument { XmlResolver = null };
// Load the XML document from the text
document.LoadXml(feedText);
// Load the XML document from the text
document.LoadXml(feedText);
// Create the namespace manager
XmlNamespaceManager namespaceManager = document.GetAllNamespaces();
// Create the namespace manager
var namespaceManager = document.GetAllNamespaces();
// Get the root node
XmlNode rootNode = document.DocumentElement;
// Get the root node
XmlNode rootNode = document.DocumentElement;
// If we didn't find a root node then bail
if (rootNode == null)
return FeedReadResult.UnknownError;
// If we didn't find a root node then bail
if (rootNode == null)
return FeedReadResult.UnknownError;
// Get the channel node
var channelNode = rootNode.SelectSingleNode("default:channel", namespaceManager) ??
rootNode.SelectSingleNode("channel", namespaceManager);
if (channelNode == null)
return FeedReadResult.InvalidXml;
// Initialize the sequence number for items
int sequence = 0;
// Loop over all nodes in the channel node
foreach (XmlNode node in channelNode.ChildNodes)
{
// Handle each node that we find
switch (node.Name)
{
case "title":
Feed.Title = System.Net.WebUtility.HtmlDecode(node.InnerText).Trim();
break;
case "link":
Feed.Link = node.InnerText.Trim();
break;
case "description":
Feed.Description = node.InnerText;
break;
case "item":
HandleFeedItem(node, ref sequence);
break;
}
}
return FeedReadResult.Success;
}
catch (XmlException xmlException)
{
Tracer.WriteLine("XML error: " + xmlException.Message + "\n" + feedText);
// Get the channel node
var channelNode = rootNode.SelectSingleNode("default:channel", namespaceManager) ??
rootNode.SelectSingleNode("channel", namespaceManager);
if (channelNode == null)
return FeedReadResult.InvalidXml;
}
}
protected override FeedItem ParseFeedItem(XmlNode node)
{
// Create a new feed item
FeedItem feedItem = FeedItem.Create();
// Initialize the sequence number for items
var sequence = 0;
// Loop over all nodes in the feed node
foreach (XmlNode childNode in node.ChildNodes)
// Loop over all nodes in the channel node
foreach (XmlNode node in channelNode.ChildNodes)
{
// Handle each node that we find
switch (childNode.Name.ToLower())
switch (node.Name)
{
case "title":
feedItem.Title = System.Net.WebUtility.HtmlDecode(childNode.InnerText).Trim();
Feed.Title = System.Net.WebUtility.HtmlDecode(node.InnerText).Trim();
break;
case "link":
feedItem.Link = childNode.InnerText.Trim();
break;
case "guid":
feedItem.Guid = childNode.InnerText.Trim();
bool permaLink = true;
if (childNode.Attributes != null)
{
var permaLinkNode = childNode.Attributes.GetNamedItem("isPermaLink");
permaLink = (permaLinkNode == null || permaLinkNode.Value == "true");
}
if (permaLink && Uri.IsWellFormedUriString(feedItem.Guid, UriKind.Absolute))
feedItem.Link = feedItem.Guid;
Feed.Link = node.InnerText.Trim();
break;
case "description":
feedItem.Description = System.Net.WebUtility.HtmlDecode(childNode.InnerText);
Feed.Description = node.InnerText;
break;
case "item":
HandleFeedItem(node, ref sequence);
break;
}
}
if (string.IsNullOrWhiteSpace(feedItem.Guid))
feedItem.Guid = feedItem.Link;
return FeedReadResult.Success;
}
catch (XmlException xmlException)
{
Log.Logger.Error(xmlException, "Exception: {0}", feedText);
return feedItem;
return FeedReadResult.InvalidXml;
}
}
}
protected override FeedItem ParseFeedItem(XmlNode node)
{
// Create a new feed item
var feedItem = FeedItem.Create();
// Loop over all nodes in the feed node
foreach (XmlNode childNode in node.ChildNodes)
{
// Handle each node that we find
switch (childNode.Name.ToLower())
{
case "title":
feedItem.Title = System.Net.WebUtility.HtmlDecode(childNode.InnerText).Trim();
break;
case "link":
feedItem.Link = childNode.InnerText.Trim();
break;
case "guid":
feedItem.Guid = childNode.InnerText.Trim();
var permaLink = true;
if (childNode.Attributes != null)
{
var permaLinkNode = childNode.Attributes.GetNamedItem("isPermaLink");
permaLink = permaLinkNode == null || permaLinkNode.Value == "true";
}
if (permaLink && Uri.IsWellFormedUriString(feedItem.Guid, UriKind.Absolute))
feedItem.Link = feedItem.Guid;
break;
case "description":
feedItem.Description = System.Net.WebUtility.HtmlDecode(childNode.InnerText);
break;
}
}
if (string.IsNullOrWhiteSpace(feedItem.Guid))
feedItem.Guid = feedItem.Link;
return feedItem;
}
}

View File

@@ -1,17 +1,71 @@
using System;
using JetBrains.Annotations;
using Realms;
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
namespace FeedCenter
namespace FeedCenter;
public class Category : RealmObject, INotifyDataErrorInfo
{
public partial class Category
public const string DefaultName = "< default >";
private readonly DataErrorDictionary _dataErrorDictionary;
public Category()
{
public static Category Create()
{
return new Category { ID = Guid.NewGuid() };
}
public bool IsDefault => Name == "< default >";
// ReSharper disable once UnusedMember.Global
public int SortKey => IsDefault ? 0 : 1;
_dataErrorDictionary = new DataErrorDictionary();
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
}
}
[PrimaryKey]
public Guid Id { get; set; } = Guid.NewGuid();
public bool IsDefault { get; internal set; }
public string Name
{
get => RawName;
set
{
RawName = value;
ValidateName();
RaisePropertyChanged();
}
}
[MapTo("Name")]
private string RawName { get; set; } = string.Empty;
[UsedImplicitly]
public int SortKey => IsDefault ? 0 : 1;
public bool HasErrors => _dataErrorDictionary.Any();
public IEnumerable GetErrors(string propertyName)
{
return _dataErrorDictionary.GetErrors(propertyName);
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void DataErrorDictionaryErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(e.PropertyName));
}
public static Category CreateDefault()
{
return new Category { Name = DefaultName, IsDefault = true };
}
private void ValidateName()
{
_dataErrorDictionary.ClearErrors(nameof(Name));
if (string.IsNullOrWhiteSpace(Name))
_dataErrorDictionary.AddError(nameof(Name), "Name cannot be empty");
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
namespace FeedCenter;
internal class DataErrorDictionary : Dictionary<string, List<string>>
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
return TryGetValue(propertyName, out var value) ? value : null;
}
public void AddError(string propertyName, string error)
{
if (!ContainsKey(propertyName))
this[propertyName] = new List<string>();
if (this[propertyName].Contains(error))
return;
this[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
public void ClearErrors(string propertyName)
{
if (!ContainsKey(propertyName))
return;
Remove(propertyName);
OnErrorsChanged(propertyName);
}
}

View File

@@ -1,358 +1,442 @@
using Common.Debug;
using Common.Xml;
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.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
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 System.Threading.Tasks;
using Common.Update;
namespace FeedCenter
namespace FeedCenter;
public partial class Feed : RealmObject, INotifyDataErrorInfo
{
#region Enumerations
private static HttpClient _httpClient;
public enum MultipleOpenAction
private readonly DataErrorDictionary _dataErrorDictionary;
public Feed()
{
IndividualPages,
SinglePage
_dataErrorDictionary = new DataErrorDictionary();
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
}
public enum FeedType
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]
public Guid Id { get; set; }
[UsedImplicitly]
public IList<FeedItem> Items { get; }
public DateTimeOffset LastChecked { get; set; }
public FeedReadResult LastReadResult
{
Unknown,
Rss,
Rdf,
Atom
get => Enum.TryParse(LastReadResultRaw, out FeedReadResult result) ? result : FeedReadResult.Success;
set => LastReadResultRaw = value.ToString();
}
public enum FeedItemComparison : byte
// ReSharper disable once UnusedMember.Global
public string LastReadResultDescription
{
Default,
Title
}
public enum FeedReadResult
{
Success,
NotModified,
NotDue,
UnknownError,
InvalidXml,
NotEnabled,
Unauthorized,
NoResponse,
NotFound,
Timeout,
ConnectionFailed,
ServerError
}
#endregion
public partial class Feed
{
public static Feed Create(FeedCenterEntities database)
get
{
return new Feed { ID = Guid.NewGuid(), CategoryID = database.DefaultCategory.ID };
// Cast the last read result to the proper enum
var lastReadResult = LastReadResult;
// Build the name of the resource using the enum name and the value
var resourceName = $"{nameof(FeedReadResult)}_{lastReadResult}";
// Try to get the value from the resources
var resourceValue = Resources.ResourceManager.GetString(resourceName);
// Return the value or just the enum value if not found
return resourceValue ?? lastReadResult.ToString();
}
}
#region Reading
private string LastReadResultRaw { get; set; }
public FeedReadResult Read(FeedCenterEntities database, bool forceRead = false)
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 => RawName;
set
{
Tracer.WriteLine("Reading feed: {0}", Source);
Tracer.IncrementIndentLevel();
RawName = value;
var result = ReadFeed(database, forceRead);
ValidateString(nameof(Name), RawName);
RaisePropertyChanged();
}
}
// Handle the result
switch (result)
[MapTo("Password")]
public string RawPassword { get; set; }
public string Password
{
get => RawPassword;
set
{
RawPassword = value;
if (!Authenticate)
{
case FeedReadResult.NotDue:
case FeedReadResult.NotEnabled:
case FeedReadResult.NotModified:
// Ignore
break;
default:
// Save as last result
LastReadResult = result;
break;
_dataErrorDictionary.ClearErrors(nameof(Password));
return;
}
// If the feed was successfully read and we have no last update timestamp - set the last update timestamp to now
if (result == FeedReadResult.Success && LastUpdated == Data.Extensions.SqlDateTimeZero.Value)
LastUpdated = DateTime.Now;
ValidateString(nameof(Password), RawPassword);
RaisePropertyChanged();
}
}
Tracer.DecrementIndentLevel();
Tracer.WriteLine("Done reading feed: {0}", result);
[MapTo("Name")]
private string RawName { get; set; } = string.Empty;
return result;
[MapTo("Source")]
private string RawSource { get; set; } = string.Empty;
public string Source
{
get => RawSource;
set
{
RawSource = value;
ValidateString(nameof(Source), RawSource);
RaisePropertyChanged();
}
}
public string Title { get; set; }
[MapTo("Username")]
public string RawUsername { get; set; }
public string Username
{
get => RawUsername;
set
{
RawUsername = value;
if (!Authenticate)
{
_dataErrorDictionary.ClearErrors(nameof(Username));
return;
}
ValidateString(nameof(Username), RawUsername);
RaisePropertyChanged();
}
}
public bool HasErrors => _dataErrorDictionary.Any();
public IEnumerable GetErrors(string propertyName)
{
return _dataErrorDictionary.GetErrors(propertyName);
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public static Feed Create()
{
return new Feed { Id = Guid.NewGuid(), CategoryId = Database.Entities.DefaultCategory.Id };
}
private void DataErrorDictionaryErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(e.PropertyName));
}
private void ValidateString(string propertyName, string value)
{
_dataErrorDictionary.ClearErrors(propertyName);
if (string.IsNullOrWhiteSpace(value))
_dataErrorDictionary.AddError(propertyName, $"{propertyName} cannot be empty");
}
public FeedReadResult Read(bool forceRead = false)
{
Log.Logger.Information("Reading feed: {0}", Source);
var result = ReadFeed(forceRead);
// Handle the result
switch (result)
{
case FeedReadResult.NotDue:
case FeedReadResult.NotEnabled:
case FeedReadResult.NotModified:
// Ignore
break;
default:
// Save as last result
LastReadResult = result;
break;
}
public async Task<FeedReadResult> ReadAsync(FeedCenterEntities database, bool forceRead = false)
// If the feed was successfully read and we have no last update timestamp - set the last update timestamp to now
if (result == FeedReadResult.Success && LastUpdated == default)
LastUpdated = DateTimeOffset.Now;
Log.Logger.Information("Done reading feed: {0}", result);
return result;
}
public Tuple<FeedType, string> DetectFeedType()
{
var retrieveResult = RetrieveFeed();
if (retrieveResult.Item1 != FeedReadResult.Success)
{
return await Task.Run(() => Read(database, forceRead));
return new Tuple<FeedType, string>(FeedType.Unknown, string.Empty);
}
public Tuple<FeedType, string> DetectFeedType()
var feedType = FeedType.Unknown;
try
{
feedType = FeedParserBase.DetectFeedType(retrieveResult.Item2);
}
catch
{
// Ignore
}
return new Tuple<FeedType, string>(feedType, retrieveResult.Item2);
}
private Tuple<FeedReadResult, string> RetrieveFeed()
{
try
{
// Create and configure the HTTP client if needed
if (_httpClient == null)
{
var clientHandler = new HttpClientHandler
{
// Set that we'll accept compressed data
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;
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
// Set a timeout
_httpClient.Timeout = TimeSpan.FromSeconds(10);
}
// If we need to authenticate then set the credentials
_httpClient.DefaultRequestHeaders.Authorization = Authenticate ? new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}"))) : null;
// Attempt to get the response
var response = _httpClient.GetAsync(Source).Result;
response.EnsureSuccessStatusCode();
var feedStream = response.Content.ReadAsStream();
// Create the text reader
using StreamReader textReader = new XmlSanitizingStream(feedStream, Encoding.UTF8);
// Get the feed text
var feedText = textReader.ReadToEnd();
if (string.IsNullOrEmpty(feedText))
return Tuple.Create(FeedReadResult.NoResponse, string.Empty);
// Get rid of any leading and trailing whitespace
feedText = feedText.Trim();
// Clean up common invalid XML characters
feedText = feedText.Replace("&nbsp;", "&#160;");
// Find ampersands that aren't properly escaped and replace them with escaped versions
var r = UnescapedAmpersandRegex();
feedText = r.Replace(feedText, "&amp;");
return Tuple.Create(FeedReadResult.Success, feedText);
}
catch (HttpRequestException httpRequestException)
{
Log.Logger.Error(httpRequestException, "Exception");
return HandleHttpRequestException(httpRequestException);
}
catch (AggregateException aggregateException)
{
Log.Logger.Error(aggregateException, "Exception");
return aggregateException.InnerException switch
{
TaskCanceledException => Tuple.Create(FeedReadResult.Timeout, string.Empty),
HttpRequestException httpRequestException => HandleHttpRequestException(httpRequestException),
_ => Tuple.Create(FeedReadResult.UnknownError, string.Empty)
};
}
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception");
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
}
}
private static Tuple<FeedReadResult, string> HandleHttpRequestException(HttpRequestException httpRequestException)
{
switch (httpRequestException.StatusCode)
{
case HttpStatusCode.ServiceUnavailable:
return Tuple.Create(FeedReadResult.TemporarilyUnavailable, string.Empty);
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:
case HttpStatusCode.Redirect:
return Tuple.Create(FeedReadResult.Moved, string.Empty);
}
if (httpRequestException.InnerException is not SocketException socketException)
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
return socketException.SocketErrorCode switch
{
SocketError.NoData => Tuple.Create(FeedReadResult.NoResponse, string.Empty),
SocketError.HostNotFound => Tuple.Create(FeedReadResult.NotFound, string.Empty),
_ => Tuple.Create(FeedReadResult.UnknownError, string.Empty)
};
}
private FeedReadResult ReadFeed(bool forceRead)
{
try
{
// If not enabled then do nothing
if (!Enabled)
return FeedReadResult.NotEnabled;
// Check if we're forcing a read
if (!forceRead)
{
// Figure out how long since we last checked
var timeSpan = DateTimeOffset.Now - LastChecked;
// Check if we are due to read the feed
if (timeSpan.TotalMinutes < CheckInterval)
return FeedReadResult.NotDue;
}
// We're checking it now so update the time
LastChecked = DateTimeOffset.Now;
// Read the feed text
var retrieveResult = RetrieveFeed();
if (retrieveResult.Item1 != FeedReadResult.Success)
// Get the information out of the async result
var result = retrieveResult.Item1;
var feedText = retrieveResult.Item2;
// If we didn't successfully retrieve the feed then stop
if (result != FeedReadResult.Success)
return result;
// Create a new RSS parser
var feedParser = FeedParserBase.CreateFeedParser(this, feedText);
// Parse the feed
result = feedParser.ParseFeed(feedText);
// If we didn't successfully parse the feed then stop
if (result != FeedReadResult.Success)
return result;
// Create the removed items list - if an item wasn't seen during this check then remove it
var removedItems = Items.Where(testItem => testItem.LastFound != LastChecked).ToList();
// If items were removed the feed was updated
if (removedItems.Count > 0)
LastUpdated = DateTime.Now;
// Loop over the items to be removed
foreach (var itemToRemove in removedItems)
{
return new Tuple<FeedType, string>(FeedType.Unknown, string.Empty);
// Remove the item from the list
Items.Remove(itemToRemove);
}
return new Tuple<FeedType, string>(FeedParserBase.DetectFeedType(retrieveResult.Item2), retrieveResult.Item2);
return FeedReadResult.Success;
}
private Tuple<FeedReadResult, string> RetrieveFeed()
catch (FeedParseException feedParseException)
{
try
{
// Add extra security protocols
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Log.Logger.Error(feedParseException, "Exception");
// Create the web request
var oRequest = WebRequest.Create(new Uri(Source));
// Attempt to cast to a web request
var webRequest = oRequest as HttpWebRequest;
// If this is an http request set some special properties
if (webRequest != null)
{
// Make sure to use HTTP version 1.1
webRequest.ProtocolVersion = HttpVersion.Version11;
// Set that we'll accept compressed data
webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
// Set a timeout
webRequest.Timeout = 10000;
// If we need to authenticate then set the credentials
if (Authenticate)
webRequest.Credentials = new NetworkCredential(Username, Password, Domain);
// Set a user agent string
if (string.IsNullOrWhiteSpace(Properties.Settings.Default.DefaultUserAgent))
webRequest.UserAgent = "FeedCenter/" + UpdateCheck.LocalVersion;
else
webRequest.UserAgent = Properties.Settings.Default.DefaultUserAgent;
}
// Set the default encoding
var encoding = Encoding.UTF8;
// Attempt to get the response
var response = (HttpWebResponse) oRequest.GetResponse();
// If the response included an encoding then change the encoding
if (response.ContentEncoding.Length > 0)
encoding = Encoding.GetEncoding(response.ContentEncoding);
// Get the response stream
var responseStream = response.GetResponseStream();
if (responseStream == null)
return Tuple.Create(FeedReadResult.NoResponse, string.Empty);
// Create the text reader
StreamReader textReader = new XmlSanitizingStream(responseStream, encoding);
// Get the feed text
var feedText = textReader.ReadToEnd();
// Get rid of any leading and trailing whitespace
feedText = feedText.Trim();
// Clean up common invalid XML characters
feedText = feedText.Replace("&nbsp;", "&#160;");
// Find ampersands that aren't properly escaped and replace them with escaped versions
var r = new Regex("&(?!(?:[a-z]+|#[0-9]+|#x[0-9a-f]+);)");
feedText = r.Replace(feedText, "&amp;");
return Tuple.Create(FeedReadResult.Success, feedText);
}
catch (IOException ioException)
{
Tracer.WriteLine(ioException.Message);
return Tuple.Create(FeedReadResult.ConnectionFailed, string.Empty);
}
catch (WebException webException)
{
var result = FeedReadResult.UnknownError;
var errorResponse = webException.Response as HttpWebResponse;
if (errorResponse != null)
{
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:
case WebExceptionStatus.NameResolutionFailure:
result = FeedReadResult.ConnectionFailed;
break;
case WebExceptionStatus.Timeout:
result = FeedReadResult.Timeout;
break;
}
Tracer.WriteException(webException);
if (result == FeedReadResult.UnknownError)
Debug.Print("Unknown error");
return Tuple.Create(result, string.Empty);
}
catch (Exception exception)
{
Tracer.WriteLine(exception.Message);
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
}
return FeedReadResult.InvalidXml;
}
private FeedReadResult ReadFeed(FeedCenterEntities database, bool forceRead)
catch (InvalidFeedFormatException exception)
{
try
{
// If not enabled then do nothing
if (!Enabled)
return FeedReadResult.NotEnabled;
Log.Logger.Error(exception, "Exception");
// Check if we're forcing a read
if (!forceRead)
{
// Figure out how long since we last checked
var timeSpan = DateTime.Now - LastChecked;
// Check if we are due to read the feed
if (timeSpan.TotalMinutes < CheckInterval)
return FeedReadResult.NotDue;
}
// We're checking it now so update the time
LastChecked = DateTime.Now;
// Read the feed text
var retrieveResult = RetrieveFeed();
// Get the information out of the async result
var result = retrieveResult.Item1;
var feedText = retrieveResult.Item2;
// If we didn't successfully retrieve the feed then stop
if (result != FeedReadResult.Success)
return result;
// Create a new RSS parser
var feedParser = FeedParserBase.CreateFeedParser(this, feedText);
// Parse the feed
result = feedParser.ParseFeed(feedText);
// If we didn't successfully parse the feed then stop
if (result != FeedReadResult.Success)
return result;
// Create the removed items list - if an item wasn't seen during this check then remove it
var removedItems = Items.Where(testItem => testItem.LastFound != LastChecked).ToList();
// If items were removed the feed was updated
if (removedItems.Count > 0)
LastUpdated = DateTime.Now;
// Loop over the items to be removed
foreach (var itemToRemove in removedItems)
{
// Delete the item from the database
database.FeedItems.Remove(itemToRemove);
// Remove the item from the list
Items.Remove(itemToRemove);
}
// Process actions on this feed
ProcessActions();
return FeedReadResult.Success;
}
catch (InvalidFeedFormatException exception)
{
Tracer.WriteException(exception.InnerException);
return FeedReadResult.InvalidXml;
}
catch (Exception exception)
{
Tracer.WriteLine(exception.Message);
return FeedReadResult.UnknownError;
}
return FeedReadResult.InvalidXml;
}
private void ProcessActions()
catch (Exception exception)
{
var sortedActions = from action in Actions orderby action.Sequence ascending select action;
Log.Logger.Error(exception, "Exception");
foreach (var feedAction in sortedActions)
{
switch (feedAction.Field)
{
case 0:
Title = Title.Replace(feedAction.Search, feedAction.Replace);
break;
}
}
}
#endregion
// ReSharper disable once UnusedMember.Global
public string LastReadResultDescription
{
get
{
// Cast the last read result to the proper enum
var lastReadResult = LastReadResult;
// Build the name of the resource using the enum name and the value
var resourceName = $"{typeof(FeedReadResult).Name}_{lastReadResult}";
// Try to get the value from the resources
var resourceValue = Properties.Resources.ResourceManager.GetString(resourceName);
// Return the value or just the enum value if not found
return resourceValue ?? lastReadResult.ToString();
}
return FeedReadResult.UnknownError;
}
}
}
[GeneratedRegex("&(?!(?:[a-z]+|#[0-9]+|#x[0-9a-f]+);)")]
private static partial Regex UnescapedAmpersandRegex();
}

View File

@@ -1,25 +0,0 @@
using System;
namespace FeedCenter
{
public partial class FeedAction
{
#region Constructor
public FeedAction()
{
ID = Guid.NewGuid();
}
#endregion
#region Methods
public override string ToString()
{
return string.Format(Properties.Resources.FeedActionDescription, Field, Search, Replace);
}
#endregion
}
}

View File

@@ -1,69 +1,81 @@
using System;
using System.Text.RegularExpressions;
using FeedCenter.Options;
using Realms;
namespace FeedCenter
namespace FeedCenter;
public partial class FeedItem : RealmObject
{
public partial class FeedItem
public bool BeenRead { get; set; }
public string Description { 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()
{
public static FeedItem Create()
{
return new FeedItem { ID = System.Guid.NewGuid() };
}
#region Methods
public override string ToString()
{
string title = Title;
switch (Properties.Settings.Default.MultipleLineDisplay)
{
case Options.MultipleLineDisplay.SingleLine:
// Strip any newlines from the title
title = Regex.Replace(title, @"\n", " ");
break;
case Options.MultipleLineDisplay.FirstLine:
// Find the first newline
int newlineIndex = title.IndexOf("\n", StringComparison.Ordinal);
// If a newline was found return everything before it
if (newlineIndex > -1)
title = title.Substring(0, newlineIndex);
break;
}
// Condense multiple spaces to one space
title = Regex.Replace(title, @"[ ]{2,}", " ");
// Condense tabs to one space
title = Regex.Replace(title, @"\t", " ");
// If the title is blank then put in the "no title" title
if (title.Length == 0)
title = Properties.Resources.NoTitleText;
return title;
}
//public void ProcessActions(IEnumerable<FeedAction> feedActions)
//{
// foreach (FeedAction feedAction in feedActions)
// {
// switch (feedAction.Field)
// {
// case 1:
// Title = Regex.Replace(Title, feedAction.Search, feedAction.Replace);
// break;
// }
// }
//}
#endregion
return new FeedItem { Id = System.Guid.NewGuid() };
}
}
public override string ToString()
{
var title = Title;
switch (Properties.Settings.Default.MultipleLineDisplay)
{
case MultipleLineDisplay.SingleLine:
// Strip any newlines from the title
title = NewlineRegex().Replace(title, " ");
break;
case MultipleLineDisplay.FirstLine:
// Find the first newline
var newlineIndex = title.IndexOf("\n", StringComparison.Ordinal);
// If a newline was found return everything before it
if (newlineIndex > -1)
title = title[..newlineIndex];
break;
case MultipleLineDisplay.Normal:
break;
default:
throw new ArgumentOutOfRangeException();
}
title ??= string.Empty;
// Condense multiple spaces to one space
title = MultipleSpaceRegex().Replace(title, " ");
// Condense tabs to one space
title = TabRegex().Replace(title, " ");
// If the title is blank then put in the "no title" title
if (title.Length == 0)
title = Properties.Resources.NoTitleText;
return title;
}
[GeneratedRegex("\\n")]
private static partial Regex NewlineRegex();
[GeneratedRegex("[ ]{2,}")]
private static partial Regex MultipleSpaceRegex();
[GeneratedRegex("\\t")]
private static partial Regex TabRegex();
}

View File

@@ -0,0 +1,19 @@
namespace FeedCenter;
public enum FeedReadResult
{
Success,
NotModified,
NotDue,
UnknownError,
InvalidXml,
NotEnabled,
Unauthorized,
NoResponse,
NotFound,
Timeout,
ConnectionFailed,
ServerError,
Moved,
TemporarilyUnavailable
}

View File

@@ -0,0 +1,9 @@
namespace FeedCenter;
public enum FeedType
{
Unknown,
Rss,
Rdf,
Atom
}

View File

@@ -0,0 +1,7 @@
namespace FeedCenter;
public enum MultipleOpenAction
{
IndividualPages,
SinglePage
}

View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Realm />
</Weavers>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Realm" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="DisableAnalytics" type="xs:boolean">
<xs:annotation>
<xs:documentation>Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics.cs</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -1,31 +1,50 @@
using System;
using FeedCenter.Properties;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using FeedCenter.Properties;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private void DisplayCategory()
{
private void DisplayCategory()
{
CategoryLabel.Text = string.Format(Properties.Resources.CategoryFilterHeader, _currentCategory == null ? Properties.Resources.AllCategory : _currentCategory.Name);
}
CategoryLabel.Text = string.Format(Properties.Resources.CategoryFilterHeader, _currentCategory == null ? Properties.Resources.AllCategory : _currentCategory.Name);
}
private void HandleCategoryButtonClick(object sender, RoutedEventArgs e)
{
// Create a new context menu
var contextMenu = new ContextMenu();
private void HandleCategoryButtonClick(object sender, RoutedEventArgs e)
{
// Create a new context menu
var contextMenu = new ContextMenu();
// Create the "all" menu item
var menuItem = new MenuItem
// Create the "all" menu item
var menuItem = new MenuItem
{
Header = Properties.Resources.AllCategory,
Tag = null,
// Set the current item to bold
FontWeight = _currentCategory == null ? FontWeights.Bold : FontWeights.Normal
};
// Handle the click
menuItem.Click += HandleCategoryMenuItemClick;
// Add the item to the list
contextMenu.Items.Add(menuItem);
// Loop over each feed
foreach (var category in _database.Categories.OrderBy(category => category.Name))
{
// Create a menu item
menuItem = new MenuItem
{
Header = Properties.Resources.AllCategory,
Tag = null,
Header = category.Name,
Tag = category,
// Set the current item to bold
FontWeight = _currentCategory == null ? FontWeights.Bold : FontWeights.Normal
FontWeight = category.Id == _currentCategory?.Id ? FontWeights.Bold : FontWeights.Normal
};
// Handle the click
@@ -33,68 +52,49 @@ namespace FeedCenter
// Add the item to the list
contextMenu.Items.Add(menuItem);
// Loop over each feed
foreach (var category in _database.Categories.OrderBy(category => category.Name))
{
// Create a menu item
menuItem = new MenuItem
{
Header = category.Name,
Tag = category,
// Set the current item to bold
FontWeight = category.ID == _currentCategory?.ID ? FontWeights.Bold : FontWeights.Normal
};
// Handle the click
menuItem.Click += HandleCategoryMenuItemClick;
// Add the item to the list
contextMenu.Items.Add(menuItem);
}
// Set the context menu placement to this button
contextMenu.PlacementTarget = this;
// Open the context menu
contextMenu.IsOpen = true;
}
private void HandleCategoryMenuItemClick(object sender, RoutedEventArgs e)
{
// Get the menu item clicked
var menuItem = (MenuItem) sender;
// Set the context menu placement to this button
contextMenu.PlacementTarget = this;
// Get the category from the menu item tab
var category = (Category) menuItem.Tag;
// If the category changed then reset the current feed to the first in the category
if (_currentCategory?.ID != category?.ID)
{
_currentFeed = category == null ? _database.Feeds.FirstOrDefault() : category.Feeds.FirstOrDefault();
}
// Set the current category
_currentCategory = category;
// Get the current feed list to match the category
_feedList = _currentCategory == null ? _database.Feeds : _database.Feeds.Where(feed => feed.Category.ID == _currentCategory.ID);
// Reset the feed index
_feedIndex = -1;
// Get the first feed
NextFeed();
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayCategory();
DisplayFeed();
Settings.Default.LastCategoryID = _currentCategory?.ID.ToString() ?? string.Empty;
}
// Open the context menu
contextMenu.IsOpen = true;
}
}
private void HandleCategoryMenuItemClick(object sender, RoutedEventArgs e)
{
// Get the menu item clicked
var menuItem = (MenuItem) sender;
// Get the category from the menu item tab
var category = (Category) menuItem.Tag;
// If the category changed then reset the current feed to the first in the category
if (_currentCategory?.Id != category?.Id)
{
_currentFeed = category == null ? _database.Feeds.FirstOrDefault() : _database.Feeds.FirstOrDefault(f => f.CategoryId == category.Id);
}
// Set the current category
_currentCategory = category;
// Get the current feed list to match the category
_feedList = _currentCategory == null ? _database.Feeds : _database.Feeds.Where(feed => feed.CategoryId == _currentCategory.Id);
// Refresh the feed index
_feedIndex = -1;
// Get the first feed
NextFeed();
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayCategory();
DisplayFeed();
UpdateToolbarButtonState();
Settings.Default.LastCategoryID = _currentCategory?.Id.ToString() ?? string.Empty;
}
}

View File

@@ -1,42 +1,37 @@
using Common.IO;
using System;
using System;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private void HandleCommandLine(string commandLine)
{
private InterprocessMessageListener _commandLineListener;
// If the command line is blank then ignore it
if (commandLine.Length == 0)
return;
private void HandleCommandLine(object sender, InterprocessMessageListener.InterprocessMessageEventArgs e)
{
// If the command line is blank then ignore it
if (e.Message.Length == 0)
return;
// Pad the command line with a trailing space just to be lazy in parsing
commandLine += " ";
// Pad the command line with a trailing space just to be lazy in parsing
var commandLine = e.Message + " ";
// Look for the feed URL in the command line
var startPosition = commandLine.IndexOf("feed://", StringComparison.Ordinal);
// Look for the feed URL in the command line
var startPosition = commandLine.IndexOf("feed://", StringComparison.Ordinal);
// If nothing was found then exit
if (startPosition <= 0) return;
// If we found one then we should extract and process it
if (startPosition > 0)
{
// Advance past the protocol
startPosition += 7;
// Advance past the protocol
startPosition += 7;
// Starting at the URL position look for the next space
var endPosition = commandLine.IndexOf(" ", startPosition, StringComparison.Ordinal);
// Starting at the URL position look for the next space
var endPosition = commandLine.IndexOf(" ", startPosition, StringComparison.Ordinal);
// Extract the feed URL
var feedUrl = commandLine.Substring(startPosition, endPosition - startPosition);
// Extract the feed URL
var feedUrl = commandLine[startPosition..endPosition];
// Add the HTTP protocol by default
feedUrl = "http://" + feedUrl;
// Add the HTTP protocol by default
feedUrl = "http://" + feedUrl;
// Create a new feed using the URL
HandleNewFeed(feedUrl);
}
}
// Create a new feed using the URL
HandleNewFeed(feedUrl);
}
}
}

View File

@@ -3,50 +3,52 @@ using System.Linq;
using System.Net;
using System.Windows;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private readonly string[] _chromeExtensions = { "chrome-extension://ehojfdcmnajoklleckniaifaijfnkpbi/subscribe.html?", "chrome-extension://nlbjncdgjeocebhnmkbbbdekmmmcbfjd/subscribe.html?" };
private void HandleDragOver(object sender, DragEventArgs e)
{
private readonly string[] _chromeExtensions = { "chrome-extension://ehojfdcmnajoklleckniaifaijfnkpbi/subscribe.html?", "chrome-extension://nlbjncdgjeocebhnmkbbbdekmmmcbfjd/subscribe.html?" };
// Default to not allowed
e.Effects = DragDropEffects.None;
e.Handled = true;
private void HandleDragOver(object sender, DragEventArgs e)
{
// Default to not allowed
e.Effects = DragDropEffects.None;
e.Handled = true;
// If there isn't any text in the data then it is not allowed
if (!e.Data.GetDataPresent(DataFormats.Text))
return;
// If there isn't any text in the data then it is not allowed
if (!e.Data.GetDataPresent(DataFormats.Text))
return;
// Get the data as a string
var data = (string) e.Data.GetData(DataFormats.Text);
// Get the data as a string
var data = (string) e.Data.GetData(DataFormats.Text);
// If the data doesn't look like a URI then it is not allowed
if (!Uri.IsWellFormedUriString(data, UriKind.Absolute))
return;
// If the data doesn't look like a URI then it is not allowed
if (!Uri.IsWellFormedUriString(data, UriKind.Absolute))
return;
// Allowed
e.Effects = DragDropEffects.Copy;
}
private void HandleDragDrop(object sender, DragEventArgs e)
{
// Get the data as a string
var data = (string) e.Data.GetData(DataFormats.Text);
// Check to see if the data starts with any known Chrome extension
var chromeExtension = _chromeExtensions.FirstOrDefault(c => data.StartsWith(c));
// Remove the Chrome extension URL and decode the URL
if (chromeExtension != null)
{
data = data.Substring(chromeExtension.Length);
data = WebUtility.UrlDecode(data);
}
// Handle the new feed but allow the drag/drop to complete
Dispatcher.BeginInvoke(new NewFeedDelegate(HandleNewFeed), data);
}
// Allowed
e.Effects = DragDropEffects.Copy;
}
}
private void HandleDragDrop(object sender, DragEventArgs e)
{
// Get the data as a string
var data = (string) e.Data.GetData(DataFormats.Text);
if (string.IsNullOrEmpty(data))
return;
// Check to see if the data starts with any known Chrome extension
var chromeExtension = _chromeExtensions.FirstOrDefault(data.StartsWith);
// Remove the Chrome extension URL and decode the URL
if (chromeExtension != null)
{
data = data[chromeExtension.Length..];
data = WebUtility.UrlDecode(data);
}
// Handle the new feed but allow the drag/drop to complete
Dispatcher.BeginInvoke(new NewFeedDelegate(HandleNewFeed), data);
}
}

View File

@@ -1,100 +1,122 @@
using System;
using FeedCenter.Options;
using System;
using System.Linq;
using System.Net;
using Common.Internet;
using FeedCenter.Options;
using System.Threading;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private delegate void NewFeedDelegate(string feedUrl);
private static string GetAbsoluteUrlString(string baseUrl, string url)
{
private delegate void NewFeedDelegate(string feedUrl);
private void HandleNewFeed(string feedUrl)
var uri = new Uri(url, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
uri = new Uri(new Uri(baseUrl), uri);
return uri.ToString();
}
private void HandleNewFeed(string feedUrl)
{
// Create and configure the new feed
var feed = Feed.Create();
feed.Source = feedUrl;
feed.CategoryId = _database.DefaultCategory.Id;
// Try to detect the feed type
var feedTypeResult = feed.DetectFeedType();
// If we can't figure it out it could be an HTML page
if (feedTypeResult.Item1 == FeedType.Unknown)
{
// Create and configure the new feed
var feed = Feed.Create(_database);
feed.Source = feedUrl;
feed.Category = _database.DefaultCategory;
// Try to detect the feed type
var feedTypeResult = feed.DetectFeedType();
// If we can't figure it out it could be an HTML page
if (feedTypeResult.Item1 == FeedType.Unknown)
// Only check if the feed was able to be read - otherwise fall through and show the dialog
if (feedTypeResult.Item2.Length > 0)
{
// Only check if the feed was able to be read - otherwise fall through and show the dialog
if (feedTypeResult.Item2.Length > 0)
// Create and load an HTML document with the text
var htmlDocument = new HtmlAgilityPack.HtmlDocument();
htmlDocument.LoadHtml(feedTypeResult.Item2);
// Look for all RSS or atom links in the document
var rssLinks = htmlDocument.DocumentNode.Descendants("link")
.Where(n => n.Attributes["type"] != null && (n.Attributes["type"].Value == "application/rss+xml" || n.Attributes["type"].Value == "application/atom+xml"))
.Select(n => new Tuple<string, string>(GetAbsoluteUrlString(feed.Source, n.Attributes["href"].Value), WebUtility.HtmlDecode(n.Attributes["title"]?.Value ?? feedUrl)))
.Distinct()
.ToList();
// If there was only one link found then switch to feed to it
if (rssLinks.Count == 1)
{
// Create and load an HTML document with the text
var htmlDocument = new HtmlAgilityPack.HtmlDocument();
htmlDocument.LoadHtml(feedTypeResult.Item2);
// Look for all RSS or atom links in the document
var rssLinks = htmlDocument.DocumentNode.Descendants("link")
.Where(n => n.Attributes["type"] != null && (n.Attributes["type"].Value == "application/rss+xml" || n.Attributes["type"].Value == "application/atom+xml"))
.Select(n => new Tuple<string, string>(UrlHelper.GetAbsoluteUrlString(feed.Source, n.Attributes["href"].Value), WebUtility.HtmlDecode(n.Attributes["title"]?.Value ?? feedUrl)))
.Distinct()
.ToList();
// If there was only one link found then switch to feed to it
if (rssLinks.Count == 1)
{
feed.Source = rssLinks[0].Item1;
}
else
{
var feedChooserWindow = new FeedChooserWindow();
var feedLink = feedChooserWindow.Display(this, rssLinks);
if (string.IsNullOrEmpty(feedLink))
return;
feed.Source = feedLink;
}
feed.Source = rssLinks[0].Item1;
}
}
// Read the feed for the first time
var feedReadResult = feed.Read(_database);
// See if we read the feed okay
if (feedReadResult == FeedReadResult.Success)
{
// Update the feed name to be the title
feed.Name = feed.Title;
// Add the feed to the feed table
_database.Feeds.Add(feed);
// Save the changes
_database.SaveChanges();
// Show a tip
NotificationIcon.ShowBalloonTip(string.Format(Properties.Resources.FeedAddedNotification, feed.Name), System.Windows.Forms.ToolTipIcon.Info);
// Re-initialize the feed display
DisplayFeed();
}
else
{
// Feed read failed - ceate a new feed window
var feedForm = new FeedWindow();
var dialogResult = feedForm.Display(_database, feed, this);
// Display the new feed form
if (dialogResult.HasValue && dialogResult.Value)
else
{
// Add the feed to the feed table
_database.Feeds.Add(feed);
var feedChooserWindow = new FeedChooserWindow();
var feedLink = feedChooserWindow.Display(this, rssLinks);
// Save the changes
_database.SaveChanges();
if (string.IsNullOrEmpty(feedLink))
return;
// Re-initialize the feed display
DisplayFeed();
feed.Source = feedLink;
}
}
}
// Read the feed for the first time
var feedReadResult = feed.Read(true);
// Check to see if this might be rate limited
if (feedReadResult == FeedReadResult.TemporarilyUnavailable)
{
// Wait a second
Thread.Sleep(1000);
// Try to read again
feedReadResult = feed.Read(true);
}
// See if we read the feed okay
if (feedReadResult == FeedReadResult.Success)
{
// Update the feed name to be the title
feed.Name = feed.Title;
// Add the feed to the feed table
_database.SaveChanges(() => _database.Feeds.Add(feed));
// Show a tip
NotificationIcon.ShowBalloonTip(string.Format(Properties.Resources.FeedAddedNotification, feed.Name), H.NotifyIcon.Core.NotificationIcon.Info);
_currentFeed = feed;
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
else
{
// Feed read failed - create a new feed window
var feedForm = new FeedWindow();
var dialogResult = feedForm.Display(feed, this);
// Display the new feed form
if (!dialogResult.HasValue || !dialogResult.Value)
return;
// Add the feed to the feed table
_database.SaveChanges(() => _database.Feeds.Add(feed));
_currentFeed = feed;
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
}
}
}

View File

@@ -1,135 +1,138 @@
using System;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Properties;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private void HandleLinkTextListMouseUp(object sender, MouseButtonEventArgs e)
{
private void HandleLinkTextListMouseUp(object sender, MouseButtonEventArgs e)
switch (e.ChangedButton)
{
switch (e.ChangedButton)
{
case MouseButton.XButton1:
case MouseButton.XButton1:
if (PreviousToolbarButton.IsEnabled)
PreviousFeed();
break;
case MouseButton.XButton2:
break;
case MouseButton.XButton2:
if (NextToolbarButton.IsEnabled)
NextFeed();
break;
}
}
private void HandleItemMouseUp(object sender, MouseButtonEventArgs e)
{
// Only handle the middle button
if (e.ChangedButton != MouseButton.Middle)
return;
// Get the feed item
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
// The feed item has been read and is no longer new
feedItem.BeenRead = true;
feedItem.New = false;
// Remove the item from the list
LinkTextList.Items.Remove(feedItem);
// Save the changes
_database.SaveChanges();
}
private void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Get the feed item
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
// Open the item link
if (BrowserCommon.OpenLink(feedItem.Link))
{
// The feed item has been read and is no longer new
feedItem.BeenRead = true;
feedItem.New = false;
// Remove the item from the list
LinkTextList.Items.Remove(feedItem);
// Save the changes
_database.SaveChanges();
}
}
private void HandleFeedButtonClick(object sender, RoutedEventArgs e)
{
// Create a new context menu
var contextMenu = new ContextMenu();
// Loop over each feed
foreach (var feed in _feedList.OrderBy(feed => feed.Name))
{
// Build a string to display the feed name and the unread count
var display = $"{feed.Name} ({feed.Items.Count(item => !item.BeenRead):d})";
// Create a menu item
var menuItem = new MenuItem
{
Header = display,
Tag = feed,
// Set the current item to bold
FontWeight = feed == _currentFeed ? FontWeights.Bold : FontWeights.Normal
};
// Handle the click
menuItem.Click += HandleFeedMenuItemClick;
// Add the item to the list
contextMenu.Items.Add(menuItem);
}
// Set the context menu placement to this button
contextMenu.PlacementTarget = this;
// Open the context menu
contextMenu.IsOpen = true;
}
private void HandleFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Get the menu item clicked
var menuItem = (MenuItem) sender;
// Get the feed from the menu item tab
var feed = (Feed) menuItem.Tag;
// Loop over all feeds and look for the index of the new one
var feedIndex = 0;
foreach (var loopFeed in _feedList.OrderBy(loopFeed => loopFeed.Name))
{
if (loopFeed == feed)
{
_feedIndex = feedIndex;
break;
}
feedIndex++;
}
// Set the current feed
_currentFeed = feed;
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayFeed();
break;
}
}
}
private void HandleItemMouseUp(object sender, MouseButtonEventArgs e)
{
// Only handle the middle button
if (e.ChangedButton != MouseButton.Middle)
return;
// Get the feed item
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
// The feed item has been read and is no longer new
_database.SaveChanges(() =>
{
feedItem.BeenRead = true;
feedItem.New = false;
});
// Remove the item from the list
LinkTextList.Items.Remove(feedItem);
}
private void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Get the feed item
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
// Try to open the item link
if (!InstalledBrowser.OpenLink(Settings.Default.Browser, feedItem.Link))
return;
// The feed item has been read and is no longer new
_database.SaveChanges(() =>
{
feedItem.BeenRead = true;
feedItem.New = false;
});
// Remove the item from the list
LinkTextList.Items.Remove(feedItem);
}
private void HandleFeedButtonClick(object sender, RoutedEventArgs e)
{
// Create a new context menu
var contextMenu = new ContextMenu();
// Loop over each feed
foreach (var feed in _feedList.OrderBy(feed => feed.Name))
{
// Build a string to display the feed name and the unread count
var display = $"{feed.Name} ({feed.Items.Count(item => !item.BeenRead):d})";
// Create a menu item
var menuItem = new MenuItem
{
Header = display,
Tag = feed,
// Set the current item to bold
FontWeight = feed.Id == _currentFeed.Id ? FontWeights.Bold : FontWeights.Normal
};
// Handle the click
menuItem.Click += HandleFeedMenuItemClick;
// Add the item to the list
contextMenu.Items.Add(menuItem);
}
// Set the context menu placement to this button
contextMenu.PlacementTarget = this;
// Open the context menu
contextMenu.IsOpen = true;
}
private void HandleFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Get the menu item clicked
var menuItem = (MenuItem) sender;
// Get the feed from the menu item tab
var feed = (Feed) menuItem.Tag;
// Loop over all feeds and look for the index of the new one
var feedIndex = 0;
foreach (var loopFeed in _feedList.OrderBy(loopFeed => loopFeed.Name))
{
if (loopFeed.Id == feed.Id)
{
_feedIndex = feedIndex;
break;
}
feedIndex++;
}
// Set the current feed
_currentFeed = feed;
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayFeed();
}
}

View File

@@ -1,4 +1,4 @@
using Common.Update;
using ChrisKaczor.ApplicationUpdate;
using FeedCenter.Properties;
using System;
using System.Collections.Generic;
@@ -7,179 +7,190 @@ using System.Linq;
using System.Threading;
using System.Windows;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private BackgroundWorker _feedReadWorker;
private class FeedReadWorkerInput
{
private BackgroundWorker _feedReadWorker;
public bool ForceRead { get; }
public Guid? FeedId { get; }
private class FeedReadWorkerInput
public FeedReadWorkerInput()
{
public bool ForceRead;
public Feed Feed;
}
private void SetProgressMode(bool value, int feedCount)
public FeedReadWorkerInput(bool forceRead)
{
// Reset the progress bar if we need it
if (value)
{
FeedReadProgress.Value = 0;
FeedReadProgress.Maximum = feedCount + 2;
FeedReadProgress.Visibility = Visibility.Visible;
}
else
{
FeedReadProgress.Visibility = Visibility.Collapsed;
}
ForceRead = forceRead;
}
private void ReadCurrentFeed(bool forceRead = false)
public FeedReadWorkerInput(bool forceRead, Guid? feedId)
{
// Don't read if we're already working
if (_feedReadWorker.IsBusy)
return;
// Don't read if there is nothing to read
if (!_database.Feeds.Any())
return;
// Switch to progress mode
SetProgressMode(true, 1);
// Create the input class
var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = _currentFeed };
// Start the worker
_feedReadWorker.RunWorkerAsync(workerInput);
}
private void ReadFeeds(bool forceRead = false)
{
// Don't read if we're already working
if (_feedReadWorker.IsBusy)
return;
// Don't read if there is nothing to read
if (!_database.Feeds.Any())
return;
// Switch to progress mode
SetProgressMode(true, _database.Feeds.Count());
// Create the input class
var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = null };
// Start the worker
_feedReadWorker.RunWorkerAsync(workerInput);
}
private void HandleFeedReadWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Set progress
FeedReadProgress.Value = e.ProgressPercentage;
}
private void HandleFeedReadWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Reset the database to current settings
ResetDatabase();
// Save settings
Settings.Default.Save();
// Set the read timestamp
_lastFeedRead = DateTime.Now;
// Update the current feed
DisplayFeed();
// Switch to normal mode
SetProgressMode(false, 0);
// Check for update
if (UpdateCheck.UpdateAvailable)
NewVersionLink.Visibility = Visibility.Visible;
UpdateErrorLink();
}
private void UpdateErrorLink()
{
var feedErrorCount = _database.Feeds.Count(f => f.LastReadResult != FeedReadResult.Success);
// Set the visibility of the error link
FeedErrorsLink.Visibility = feedErrorCount == 0 ? Visibility.Collapsed : Visibility.Visible;
// Set the text to match the number of errors
FeedErrorsLink.Text = feedErrorCount == 1
? Properties.Resources.FeedErrorLink
: string.Format(Properties.Resources.FeedErrorsLink, feedErrorCount);
}
private static void HandleFeedReadWorkerStart(object sender, DoWorkEventArgs e)
{
// 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;
// Setup for progress
var currentProgress = 0;
// 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.Feed != null)
feedsToRead.Add(database.Feeds.First(feed => feed.ID == workerInput.Feed.ID));
else
feedsToRead.AddRange(database.Feeds);
// Loop over each feed and read it
foreach (var feed in feedsToRead)
{
// Read the feed
feed.Read(database, workerInput.ForceRead);
// Increment progress
currentProgress += 1;
// Report progress
worker.ReportProgress(currentProgress);
}
// Save the changes
database.SaveChanges();
// 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)
{
// Get the update information
UpdateCheck.CheckForUpdate();
// Update the last check time
Settings.Default.LastVersionCheck = DateTime.Now;
}
// 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);
ForceRead = forceRead;
FeedId = feedId;
}
}
}
private void SetProgressMode(bool value, int feedCount)
{
// Refresh the progress bar if we need it
if (value)
{
FeedReadProgress.Value = 0;
FeedReadProgress.Maximum = feedCount + 2;
FeedReadProgress.Visibility = Visibility.Visible;
}
else
{
FeedReadProgress.Visibility = Visibility.Collapsed;
}
}
private void ReadCurrentFeed(bool forceRead = false)
{
// Don't read if we're already working
if (_feedReadWorker.IsBusy)
return;
// Don't read if there is nothing to read
if (!_database.Feeds.Any())
return;
// Switch to progress mode
SetProgressMode(true, 1);
// Create the input class
var workerInput = new FeedReadWorkerInput(forceRead, _currentFeed.Id);
// Start the worker
_feedReadWorker.RunWorkerAsync(workerInput);
}
private void ReadFeeds(bool forceRead = false)
{
// Don't read if we're already working
if (_feedReadWorker.IsBusy)
return;
// Don't read if there is nothing to read
if (!_database.Feeds.Any())
return;
// Switch to progress mode
SetProgressMode(true, _database.Feeds.Count);
// Create the input class
var workerInput = new FeedReadWorkerInput(forceRead);
// Start the worker
_feedReadWorker.RunWorkerAsync(workerInput);
}
private void HandleFeedReadWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Set progress
FeedReadProgress.Value = e.ProgressPercentage;
}
private void HandleFeedReadWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Refresh the database to current settings
ResetDatabase();
// Save settings
Settings.Default.Save();
// Set the read timestamp
_lastFeedRead = DateTime.Now;
// Update the current feed
DisplayFeed();
// Switch to normal mode
SetProgressMode(false, 0);
// Check for update
if (UpdateCheck.UpdateAvailable)
NewVersionLink.Visibility = Visibility.Visible;
UpdateErrorLink();
}
private void UpdateErrorLink()
{
var feedErrorCount = _database.Feeds.Count(f => f.LastReadResult != FeedReadResult.Success);
// Set the visibility of the error link
FeedErrorsLink.Visibility = feedErrorCount == 0 ? Visibility.Collapsed : Visibility.Visible;
// Set the text to match the number of errors
FeedErrorsLink.Text = feedErrorCount == 1
? Properties.Resources.FeedErrorLink
: string.Format(Properties.Resources.FeedErrorsLink, feedErrorCount);
}
private static void HandleFeedReadWorkerStart(object sender, DoWorkEventArgs e)
{
// 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;
// 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)
{
// Get the update information
UpdateCheck.CheckForUpdate().Wait();
// Update the last check time
Settings.Default.LastVersionCheck = DateTime.Now;
}
// 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);
}
}

View File

@@ -1,32 +1,32 @@
using FeedCenter.Properties;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Properties;
using System.Windows;
using System.Windows.Input;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private void HandleHeaderLabelMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
private void HandleHeaderLabelMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Ignore if the window is locked
if (Settings.Default.WindowLocked)
return;
// Ignore if the window is locked
if (Settings.Default.WindowLocked)
return;
// Start dragging
DragMove();
}
private void HandleCloseButtonClick(object sender, RoutedEventArgs e)
{
// Close the window
Close();
}
private void HandleFeedLabelMouseDown(object sender, MouseButtonEventArgs e)
{
// Open the link for the current feed on a left double click
if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left)
BrowserCommon.OpenLink(_currentFeed.Link);
}
// Start dragging
DragMove();
}
}
private void HandleCloseButtonClick(object sender, RoutedEventArgs e)
{
// Close the window
Close();
}
private void HandleFeedLabelMouseDown(object sender, MouseButtonEventArgs e)
{
// Open the link for the current feed on a left double click
if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left)
InstalledBrowser.OpenLink(Settings.Default.Browser, _currentFeed.Link);
}
}

View File

@@ -2,13 +2,13 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:windows="clr-namespace:Common.Wpf.Windows;assembly=Common.Wpf"
xmlns:toolbar="clr-namespace:Common.Wpf.Toolbar;assembly=Common.Wpf"
xmlns:splitButton="clr-namespace:Common.Wpf.Toolbar.SplitButton;assembly=Common.Wpf"
xmlns:linkControl="clr-namespace:Common.Wpf.LinkControl;assembly=Common.Wpf"
xmlns:htmlTextBlock="clr-namespace:Common.Wpf.HtmlTextBlock;assembly=Common.Wpf"
xmlns:windows="clr-namespace:ChrisKaczor.Wpf.Windows;assembly=ChrisKaczor.Wpf.Windows.SnappingWindow"
xmlns:toolbar="clr-namespace:ChrisKaczor.Wpf.Controls.Toolbar;assembly=ChrisKaczor.Wpf.Controls.Toolbar"
xmlns:splitButton="clr-namespace:ChrisKaczor.Wpf.Controls.Toolbar;assembly=ChrisKaczor.Wpf.Controls.Toolbar"
xmlns:htmlTextBlock="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.HtmlTextBlock"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:nameBasedGrid="clr-namespace:NameBasedGrid;assembly=NameBasedGrid"
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
Title="MainWindow"
Height="360"
Width="252"
@@ -80,18 +80,19 @@
FontFamily="Marlett"
Content="r"
FontSize="8"
Grid.Column="1"></Button>
Grid.Column="1">
</Button>
</Grid>
<linkControl:LinkControl Name="NewVersionLink"
Height="21"
nameBasedGrid:NameBasedGrid.Row="NewVersionRow"
Text="{x:Static properties:Resources.NewVersionLink}"
Background="AntiqueWhite"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Visibility="Collapsed"
Click="HandleNewVersionLinkClick">
</linkControl:LinkControl>
<controls:Link Name="NewVersionLink"
Height="21"
nameBasedGrid:NameBasedGrid.Row="NewVersionRow"
Text="{x:Static properties:Resources.NewVersionLink}"
Background="AntiqueWhite"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Visibility="Collapsed"
Click="HandleNewVersionLinkClick">
</controls:Link>
<Grid Name="CategoryGrid"
Height="21"
nameBasedGrid:NameBasedGrid.Row="CategoryRow"
@@ -152,7 +153,6 @@
Foreground="White"
nameBasedGrid:NameBasedGrid.Row="FeedListRow"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBoxItem Content="Test item" />
<ListBox.ItemTemplate>
<DataTemplate>
<htmlTextBlock:HtmlTextBlock Html="{Binding}"
@@ -186,12 +186,14 @@
<Setter Property="Panel.Background"
TargetName="Bd">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.HighlightBrushKey}" />
<DynamicResource
ResourceKey="{x:Static SystemColors.HighlightBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="TextElement.Foreground">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.HighlightTextBrushKey}" />
<DynamicResource
ResourceKey="{x:Static SystemColors.HighlightTextBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="Panel.Cursor"
@@ -295,17 +297,17 @@
</splitButton:SplitButton>
</ToolBar>
</ToolBarTray>
<linkControl:LinkControl Name="FeedErrorsLink"
Height="21"
nameBasedGrid:NameBasedGrid.Row="FeedErrorsRow"
Text="{x:Static properties:Resources.FeedErrorsLink}"
ToolTip="{x:Static properties:Resources.showErrorsToolbarButton}"
Background="AntiqueWhite"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Visibility="Collapsed"
Click="HandleShowErrorsButtonClick">
</linkControl:LinkControl>
<controls:Link Name="FeedErrorsLink"
Height="21"
nameBasedGrid:NameBasedGrid.Row="FeedErrorsRow"
Text="{x:Static properties:Resources.FeedErrorsLink}"
ToolTip="{x:Static properties:Resources.showErrorsToolbarButton}"
Background="AntiqueWhite"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Visibility="Collapsed"
Click="HandleShowErrorsButtonClick">
</controls:Link>
</nameBasedGrid:NameBasedGrid>
</Border>
</windows:SnappingWindow>
</windows:SnappingWindow>

View File

@@ -1,102 +1,129 @@
using Common.Debug;
using Common.Helpers;
using Common.IO;
using Common.Update;
using ChrisKaczor.ApplicationUpdate;
using ChrisKaczor.Wpf.Application;
using FeedCenter.Data;
using FeedCenter.Properties;
using Serilog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow : IDisposable
{
public partial class MainWindow
private Category _currentCategory;
private Feed _currentFeed;
private FeedCenterEntities _database;
private int _feedIndex;
private IEnumerable<Feed> _feedList;
public MainWindow()
{
private FeedCenterEntities _database;
private int _feedIndex;
InitializeComponent();
}
private Category _currentCategory;
private IQueryable<Feed> _feedList;
private Feed _currentFeed;
public void Dispose()
{
_mainTimer?.Dispose();
_feedReadWorker?.Dispose();
public MainWindow()
GC.SuppressFinalize(this);
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// Initialize the window
Initialize();
}
protected override async void OnClosed(EventArgs e)
{
base.OnClosed(e);
await SingleInstance.Stop();
}
public async void Initialize()
{
// Setup the update handler
InitializeUpdate();
// Show the notification icon
NotificationIcon.Initialize(this);
// Load window settings
LoadWindowSettings();
// Set the foreground color to something that can be seen
LinkTextList.Foreground = System.Drawing.SystemColors.Desktop.GetBrightness() < 0.5
? Brushes.White
: 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;
// Setup the database
_database = Database.Entities;
// Initialize the single instance listener
SingleInstance.MessageReceived += SingleInstance_MessageReceived;
await SingleInstance.StartAsync(App.Name);
// Handle any command line we were started with
HandleCommandLine(Environment.CommandLine);
// Create a timer to keep track of things we need to do
InitializeTimer();
// Initialize the feed display
InitializeDisplay();
// Check for update
if (Settings.Default.CheckVersionAtStartup)
await UpdateCheck.CheckForUpdate();
// Show the link if updates are available
if (UpdateCheck.UpdateAvailable)
NewVersionLink.Visibility = Visibility.Visible;
Log.Logger.Information("MainForm creation finished");
}
private void SingleInstance_MessageReceived(object sender, string commandLine)
{
HandleCommandLine(commandLine);
}
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Make sure we're on the right thread
if (!Dispatcher.CheckAccess())
{
InitializeComponent();
Dispatcher.Invoke(new EventHandler<PropertyChangedEventArgs>(HandlePropertyChanged), sender, e);
return;
}
public void Initialize()
switch (e.PropertyName)
{
// Setup the update handler
InitializeUpdate();
// Show the notification icon
NotificationIcon.Initialize(this);
// Load window settings
LoadWindowSettings();
// Set the foreground color to something that can be seen
LinkTextList.Foreground = (System.Drawing.SystemColors.Desktop.GetBrightness() < 0.5) ? Brushes.White : 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;
// Setup the database
_database = new FeedCenterEntities();
// Initialize the command line listener
_commandLineListener = new InterprocessMessageListener(Properties.Resources.ApplicationName);
_commandLineListener.MessageReceived += HandleCommandLine;
// Handle any command line we were started with
HandleCommandLine(null, new InterprocessMessageListener.InterprocessMessageEventArgs(Environment.CommandLine));
// Create a timer to keep track of things we need to do
InitializeTimer();
// Initialize the feed display
InitializeDisplay();
// Check for update
if (Settings.Default.CheckVersionAtStartup)
UpdateCheck.CheckForUpdate();
// Show the link if updates are available
if (UpdateCheck.UpdateAvailable)
NewVersionLink.Visibility = Visibility.Visible;
Tracer.WriteLine("MainForm creation finished");
}
#region Setting events
private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Make sure we're on the right thread
if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new EventHandler<PropertyChangedEventArgs>(HandlePropertyChanged), sender, e);
return;
}
if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.MultipleLineDisplay))
{
case nameof(Settings.Default.MultipleLineDisplay):
// Update the current feed
DisplayFeed();
}
else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.WindowLocked))
{
break;
case nameof(Settings.Default.WindowLocked):
// Update the window for the new window lock value
HandleWindowLockState();
}
else if (e.PropertyName == Reflection.GetPropertyName(() => Settings.Default.ToolbarLocation))
{
break;
case nameof(Settings.Default.ToolbarLocation):
// Update the window for the toolbar location
switch (Settings.Default.ToolbarLocation)
{
@@ -108,144 +135,207 @@ namespace FeedCenter
NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "BottomToolbarRow");
break;
case Dock.Left:
case Dock.Right:
default:
throw new NotSupportedException();
}
}
break;
}
}
private void ResetDatabase()
{
// Get the ID of the current feed
var currentId = _currentFeed?.IsValid ?? false ? _currentFeed.Id : Guid.Empty;
// Create a new database object
_database.Refresh();
_feedList = _currentCategory == null
? _database.Feeds.ToList()
: _database.Feeds.Where(feed => feed.CategoryId == _currentCategory.Id).ToList();
UpdateToolbarButtonState();
// Get a list of feeds ordered by name
var feedList = _feedList.OrderBy(f => f.Name).ToList();
// First try to find the current feed by ID to see if it is still there
var newIndex = feedList.FindIndex(f => f.Id == currentId);
if (newIndex == -1)
{
// The current feed isn't there anymore so see if we can find a feed at the old index
if (feedList.ElementAtOrDefault(_feedIndex) != null)
newIndex = _feedIndex;
// If there is no feed at the old location then give up and go back to the start
if (newIndex == -1 && feedList.Count > 0)
newIndex = 0;
}
#endregion
// Set the current index to the new index
_feedIndex = newIndex;
#region Feed display
// Re-get the current feed
_currentFeed = _feedIndex == -1
? null
: _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
}
private void UpdateToolbarButtonState()
private void UpdateToolbarButtonState()
{
// Cache the feed count to save (a little) time
var feedCount = Settings.Default.DisplayEmptyFeeds ? _feedList.Count() : _feedList.Count(x => x.Items.Any(y => !y.BeenRead));
// Set button states
PreviousToolbarButton.IsEnabled = feedCount > 1;
NextToolbarButton.IsEnabled = feedCount > 1;
RefreshToolbarButton.IsEnabled = feedCount > 0;
FeedButton.IsEnabled = feedCount > 0;
OpenAllToolbarButton.IsEnabled = feedCount > 0;
MarkReadToolbarButton.IsEnabled = feedCount > 0;
FeedLabel.Visibility = feedCount == 0 ? Visibility.Hidden : Visibility.Visible;
FeedButton.Visibility = feedCount == 0 ? Visibility.Hidden : Visibility.Visible;
CategoryGrid.Visibility = _database.Categories.Count > 1 ? Visibility.Visible : Visibility.Collapsed;
}
private void InitializeDisplay()
{
// Get the last category (defaulting to none)
_currentCategory = _database.Categories.FirstOrDefault(category => category.Id.ToString() == Settings.Default.LastCategoryID);
DisplayCategory();
// Get the current feed list to match the category
_feedList = _currentCategory == null
? _database.Feeds
: _database.Feeds.Where(feed => feed.CategoryId == _currentCategory.Id);
UpdateToolbarButtonState();
// Clear the link list
LinkTextList.Items.Clear();
// Refresh the feed index
_feedIndex = -1;
// Start the timer
StartTimer();
// Don't go further if we have no feeds
if (!_feedList.Any())
return;
// Get the first feed
NextFeed();
}
private void NextFeed()
{
var feedCount = _feedList.Count();
if (feedCount == 0)
return;
if (Settings.Default.DisplayEmptyFeeds)
{
// Cache the feed count to save (a little) time
var feedCount = _feedList?.Count() ?? 0;
// Increment the index and adjust if we've gone around the end
_feedIndex = (_feedIndex + 1) % feedCount;
// Set button states
PreviousToolbarButton.IsEnabled = (feedCount > 1);
NextToolbarButton.IsEnabled = (feedCount > 1);
RefreshToolbarButton.IsEnabled = (feedCount > 0);
FeedButton.IsEnabled = (feedCount > 0);
OpenAllToolbarButton.IsEnabled = (feedCount > 0);
MarkReadToolbarButton.IsEnabled = (feedCount > 0);
FeedLabel.Visibility = (feedCount == 0 ? Visibility.Hidden : Visibility.Visible);
FeedButton.Visibility = (feedCount > 1 ? Visibility.Hidden : Visibility.Visible);
CategoryGrid.Visibility = (_database.Categories.Count() > 1 ? Visibility.Visible : Visibility.Collapsed);
// Get the feed
_currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
}
private void InitializeDisplay()
else
{
// Get the last category (defaulting to none)
_currentCategory = _database.Categories.FirstOrDefault(category => category.ID.ToString() == Settings.Default.LastCategoryID);
DisplayCategory();
// Keep track if we found something
var found = false;
// Get the current feed list to match the category
_feedList = _currentCategory == null ? _database.Feeds : _database.Feeds.Where(feed => feed.Category.ID == _currentCategory.ID);
// Remember our starting position
var startIndex = _feedIndex == -1 ? 0 : _feedIndex;
UpdateToolbarButtonState();
// Increment the index and adjust if we've gone around the end
_feedIndex = (_feedIndex + 1) % feedCount;
// Clear the link list
LinkTextList.Items.Clear();
// Reset the feed index
_feedIndex = -1;
// Start the timer
StartTimer();
// Don't go further if we have no feeds
if (!_feedList.Any())
return;
// Get the first feed
NextFeed();
}
private void NextFeed()
{
var feedCount = _feedList.Count();
if (feedCount == 0)
return;
if (Settings.Default.DisplayEmptyFeeds)
// Loop until we come back to the start index
do
{
// Increment the index and adjust if we've gone around the end
_feedIndex = (_feedIndex + 1) % feedCount;
// Get the feed
_currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
}
else
{
// Keep track if we found something
var found = false;
// Remember our starting position
var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);
// If the current feed has unread items then we can display it
if (_currentFeed.Items.Any(item => !item.BeenRead))
{
found = true;
break;
}
// Increment the index and adjust if we've gone around the end
_feedIndex = (_feedIndex + 1) % feedCount;
} while (_feedIndex != startIndex);
// Loop until we come back to the start index
do
{
// Get the feed
_currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
// If the current feed has unread items then we can display it
if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
{
found = true;
break;
}
// Increment the index and adjust if we've gone around the end
_feedIndex = (_feedIndex + 1) % feedCount;
}
while (_feedIndex != startIndex);
// If nothing was found then clear the current feed
if (!found)
{
_feedIndex = -1;
_currentFeed = null;
}
// If nothing was found then clear the current feed
if (!found)
{
_feedIndex = -1;
_currentFeed = null;
}
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayFeed();
}
private void PreviousFeed()
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayFeed();
}
private void PreviousFeed()
{
var feedCount = _feedList.Count();
if (feedCount == 0)
return;
if (Settings.Default.DisplayEmptyFeeds)
{
var feedCount = _feedList.Count();
// Decrement the feed index
_feedIndex--;
if (feedCount == 0)
return;
// If we've gone below the start of the list then reset to the end
if (_feedIndex < 0)
_feedIndex = feedCount - 1;
if (Settings.Default.DisplayEmptyFeeds)
// Get the feed
_currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
}
else
{
// Keep track if we found something
var found = false;
// Remember our starting position
var startIndex = _feedIndex == -1 ? 0 : _feedIndex;
// Decrement the feed index
_feedIndex--;
// If we've gone below the start of the list then reset to the end
if (_feedIndex < 0)
_feedIndex = feedCount - 1;
// Loop until we come back to the start index
do
{
// Decrement the feed index
_feedIndex--;
// If we've gone below the start of the list then reset to the end
if (_feedIndex < 0)
_feedIndex = feedCount - 1;
// Get the feed
_currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
}
else
{
// Keep track if we found something
var found = false;
// Remember our starting position
var startIndex = (_feedIndex == -1 ? 0 : _feedIndex);
// If the current feed has unread items then we can display it
if (_currentFeed.Items.Any(item => !item.BeenRead))
{
found = true;
break;
}
// Decrement the feed index
_feedIndex--;
@@ -253,144 +343,80 @@ namespace FeedCenter
// If we've gone below the start of the list then reset to the end
if (_feedIndex < 0)
_feedIndex = feedCount - 1;
} while (_feedIndex != startIndex);
// Loop until we come back to the start index
do
{
// Get the feed
_currentFeed = _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex);
// If the current feed has unread items then we can display it
if (_currentFeed.Items.Count(item => !item.BeenRead) > 0)
{
found = true;
break;
}
// Decrement the feed index
_feedIndex--;
// If we've gone below the start of the list then reset to the end
if (_feedIndex < 0)
_feedIndex = feedCount - 1;
}
while (_feedIndex != startIndex);
// If nothing was found then clear the current feed
if (!found)
{
_feedIndex = -1;
_currentFeed = null;
}
}
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayFeed();
}
private void UpdateOpenAllButton()
{
var multipleOpenAction = _currentFeed.MultipleOpenAction;
switch (multipleOpenAction)
// If nothing was found then clear the current feed
if (!found)
{
case MultipleOpenAction.IndividualPages:
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
break;
case MultipleOpenAction.SinglePage:
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
break;
_feedIndex = -1;
_currentFeed = null;
}
}
private void DisplayFeed()
// Update the feed timestamp
_lastFeedDisplay = DateTime.Now;
// Update the display
DisplayFeed();
}
private void UpdateOpenAllButton()
{
var multipleOpenAction = _currentFeed.MultipleOpenAction;
switch (multipleOpenAction)
{
// Just clear the display if we have no feed
if (_currentFeed == null)
{
FeedLabel.Text = string.Empty;
FeedButton.Visibility = Visibility.Hidden;
LinkTextList.Items.Clear();
case MultipleOpenAction.IndividualPages:
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
break;
case MultipleOpenAction.SinglePage:
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
break;
}
}
return;
}
// Set the header to the feed title
FeedLabel.Text = (_currentFeed.Name.Length > 0 ? _currentFeed.Name : _currentFeed.Title);
FeedButton.Visibility = _feedList.Count() > 1 ? Visibility.Visible : Visibility.Hidden;
// Clear the current list
private void DisplayFeed()
{
// Just clear the display if we have no feed
if (_currentFeed == null)
{
FeedLabel.Text = string.Empty;
FeedButton.Visibility = Visibility.Hidden;
LinkTextList.Items.Clear();
// Sort the items by sequence
var sortedItems = _currentFeed.Items.Where(item => !item.BeenRead).OrderBy(item => item.Sequence);
// Loop over all items in the current feed
foreach (var feedItem in sortedItems)
{
// Add the list item
LinkTextList.Items.Add(feedItem);
}
UpdateOpenAllButton();
return;
}
private void MarkAllItemsAsRead()
// Set the header to the feed title
FeedLabel.Text = _currentFeed.Name.Length > 0 ? _currentFeed.Name : _currentFeed.Title;
FeedButton.Visibility = _feedList.Count() > 1 ? Visibility.Visible : Visibility.Hidden;
// Clear the current list
LinkTextList.Items.Clear();
// Sort the items by sequence
var sortedItems = _currentFeed.Items.Where(item => !item.BeenRead).OrderBy(item => item.Sequence);
// Loop over all items in the current feed
foreach (var feedItem in sortedItems)
{
// Add the list item
LinkTextList.Items.Add(feedItem);
}
UpdateOpenAllButton();
}
private void MarkAllItemsAsRead()
{
// Loop over all items and mark them as read
_database.SaveChanges(() =>
{
// Loop over all items and mark them as read
foreach (FeedItem feedItem in LinkTextList.Items)
feedItem.BeenRead = true;
});
// Save the changes
_database.SaveChanges();
// Clear the list
LinkTextList.Items.Clear();
}
#endregion
#region Database helpers
private void ResetDatabase()
{
// Get the ID of the current feed
var currentId = _currentFeed?.ID ?? Guid.Empty;
// Create a new database object
_database = new FeedCenterEntities();
_feedList = _currentCategory == null ? _database.Feeds : _database.Feeds.Where(feed => feed.Category.ID == _currentCategory.ID);
UpdateToolbarButtonState();
// Get a list of feeds ordered by name
var feedList = _feedList.OrderBy(f => f.Name).ToList();
// First try to find the current feed by ID to see if it is still there
var newIndex = feedList.FindIndex(f => f.ID == currentId);
if (newIndex == -1)
{
// The current feed isn't there anymore so see if we can find a feed at the old index
if (feedList.ElementAtOrDefault(_feedIndex) != null)
newIndex = _feedIndex;
// If there is no feed at the old location then give up and go back to the start
if (newIndex == -1 && feedList.Count > 0)
newIndex = 0;
}
// Set the current index to the new index
_feedIndex = newIndex;
// Re-get the current feed
_currentFeed = (_feedIndex == -1 ? null : _feedList.OrderBy(feed => feed.Name).AsEnumerable().ElementAt(_feedIndex));
}
#endregion
// Clear the list
LinkTextList.Items.Clear();
}
}
}

View File

@@ -1,39 +1,45 @@
using FeedCenter.Properties;
using System;
using System.Windows.Forms;
using System.Timers;
using System.Windows.Threading;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private Timer _mainTimer;
private DateTime _lastFeedRead;
private DateTime _lastFeedDisplay;
private Dispatcher _dispatcher;
private void InitializeTimer()
{
private Timer _mainTimer;
private DateTime _lastFeedRead;
private DateTime _lastFeedDisplay;
_dispatcher = Dispatcher.CurrentDispatcher;
private void InitializeTimer()
{
_mainTimer = new Timer { Interval = 1000 };
_mainTimer.Tick += HandleMainTimerTick;
}
_mainTimer = new Timer { Interval = 1000 };
_mainTimer.Elapsed += HandleMainTimerElapsed;
}
private void TerminateTimer()
{
StopTimer();
private void TerminateTimer()
{
StopTimer();
_mainTimer.Dispose();
}
_mainTimer.Dispose();
}
private void StartTimer()
{
_mainTimer.Start();
}
private void StartTimer()
{
_mainTimer.Start();
}
private void StopTimer()
{
_mainTimer.Stop();
}
private void StopTimer()
{
_mainTimer.Stop();
}
private void HandleMainTimerTick(object sender, EventArgs e)
private void HandleMainTimerElapsed(object sender, EventArgs e)
{
_dispatcher.Invoke(() =>
{
// If the background worker is busy then don't do anything
if (_feedReadWorker.IsBusy)
@@ -54,6 +60,6 @@ namespace FeedCenter
// Get the timer going again
StartTimer();
}
});
}
}
}

View File

@@ -1,254 +1,235 @@
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
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private void HandlePreviousToolbarButtonClick(object sender, RoutedEventArgs e)
{
private void HandlePreviousToolbarButtonClick(object sender, RoutedEventArgs e)
PreviousFeed();
}
private void HandleNextToolbarButtonClick(object sender, RoutedEventArgs e)
{
NextFeed();
}
private void OpenAllFeedItemsIndividually()
{
// Create a new list of feed items
var feedItems = (from FeedItem feedItem in LinkTextList.Items select feedItem).ToList();
// Cache the settings object
var settings = Settings.Default;
// Start with a longer sleep interval to give time for the browser to come up
var sleepInterval = settings.OpenAllSleepIntervalFirst;
// Loop over all items
foreach (var feedItem in feedItems)
{
PreviousFeed();
}
private void HandleNextToolbarButtonClick(object sender, RoutedEventArgs e)
{
NextFeed();
}
private void OpenAllFeedItemsIndividually()
{
// Create a new list of feed items
var feedItems = (from FeedItem feedItem in LinkTextList.Items select feedItem).ToList();
// Get the browser
var browser = BrowserCommon.FindBrowser(Settings.Default.Browser);
// Cache the settings object
var settings = Settings.Default;
// Start with a longer sleep interval to give time for the browser to come up
var sleepInterval = settings.OpenAllSleepIntervalFirst;
// Loop over all items
foreach (var feedItem in feedItems)
// Try to open the link
if (InstalledBrowser.OpenLink(Settings.Default.Browser, feedItem.Link))
{
// Try to open the link
if (BrowserCommon.OpenLink(browser, feedItem.Link))
{
// Mark the feed as read
feedItem.BeenRead = true;
// Mark the feed as read
_database.SaveChanges(() => feedItem.BeenRead = true);
// Remove the item
LinkTextList.Items.Remove(feedItem);
}
// Wait a little bit
Thread.Sleep(sleepInterval);
// Switch to the normal sleep interval
sleepInterval = settings.OpenAllSleepInterval;
// Remove the item
LinkTextList.Items.Remove(feedItem);
}
// Save the changes
_database.SaveChanges();
}
// Wait a little bit
Thread.Sleep(sleepInterval);
private void HandleOptionsToolbarButtonClick(object sender, RoutedEventArgs e)
{
// Create the options form
var optionsWindow = new OptionsWindow { Owner = this };
// Show the options form and get the result
var result = optionsWindow.ShowDialog();
// If okay was selected
if (result.HasValue && result.Value)
{
// Reset the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
}
private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
{
MarkAllItemsAsRead();
}
private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
{
// Create the feed error window
var feedErrorWindow = new FeedErrorWindow();
// Display the window
var result = feedErrorWindow.Display(this);
// If okay was selected
if (result.GetValueOrDefault())
{
// Reset the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
UpdateErrorLink();
}
}
private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
{
var menuItem = (MenuItem) e.Source;
if (Equals(menuItem, MenuRefresh))
ReadCurrentFeed(true);
else if (Equals(menuItem, MenuRefreshAll))
ReadFeeds(true);
}
private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
{
ReadFeeds(true);
}
private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
{
var menuItem = (MenuItem) e.Source;
if (Equals(menuItem, MenuOpenAllSinglePage))
OpenAllFeedItemsOnSinglePage();
else if (Equals(menuItem, MenuOpenAllMultiplePages))
OpenAllFeedItemsIndividually();
}
private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
{
var multipleOpenAction = _currentFeed.MultipleOpenAction;
switch (multipleOpenAction)
{
case MultipleOpenAction.IndividualPages:
OpenAllFeedItemsIndividually();
break;
case MultipleOpenAction.SinglePage:
OpenAllFeedItemsOnSinglePage();
break;
}
}
private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Create a new feed window
var feedWindow = new FeedWindow();
// Display the feed window and get the result
var result = feedWindow.Display(_database, _currentFeed, this);
// If OK was clicked...
if (result.HasValue && result.Value)
{
// Save
_database.SaveChanges();
// Update feed
DisplayFeed();
}
}
private void HandleDeleteCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Confirm this delete since it is for real
if (MessageBox.Show(this, Properties.Resources.ConfirmDelete, string.Empty, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
return;
// Get the current feed
var feedToDelete = _currentFeed;
// Move to the next feed
NextFeed();
// Delete all items
foreach (var item in feedToDelete.Items.ToList())
_database.FeedItems.Remove(item);
// Delete the feed
_database.Feeds.Remove(feedToDelete);
// Save
_database.SaveChanges();
}
private void OpenAllFeedItemsOnSinglePage()
{
var fileName = Path.GetTempFileName() + ".html";
TextWriter textWriter = new StreamWriter(fileName);
using (var htmlTextWriter = new HtmlTextWriter(textWriter))
{
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
htmlTextWriter.Write(_currentFeed.Title);
htmlTextWriter.RenderEndTag();
htmlTextWriter.AddAttribute("http-equiv", "Content-Type");
htmlTextWriter.AddAttribute("content", "text/html; charset=utf-8");
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Meta);
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Body);
var sortedItems = from item in _currentFeed.Items where !item.BeenRead orderby item.Sequence ascending select item;
var firstItem = true;
foreach (var item in sortedItems)
{
if (!firstItem)
{
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Hr);
htmlTextWriter.RenderEndTag();
}
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Div);
htmlTextWriter.AddAttribute(HtmlTextWriterAttribute.Href, item.Link);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.A);
htmlTextWriter.Write(item.Title.Length == 0 ? item.Link : item.Title);
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
htmlTextWriter.RenderEndTag();
htmlTextWriter.Write(item.Description);
htmlTextWriter.RenderEndTag();
firstItem = false;
}
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderEndTag();
}
textWriter.Flush();
textWriter.Close();
BrowserCommon.OpenLink(fileName);
MarkAllItemsAsRead();
// Switch to the normal sleep interval
sleepInterval = settings.OpenAllSleepInterval;
}
}
}
private void HandleOptionsToolbarButtonClick(object sender, RoutedEventArgs e)
{
// Create the options form
var optionsWindow = new OptionsWindow { Owner = this };
// Show the options window
optionsWindow.ShowDialog();
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
UpdateErrorLink();
}
private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
{
MarkAllItemsAsRead();
}
private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
{
// Create the feed error window
var feedErrorWindow = new FeedErrorWindow();
// Display the window
feedErrorWindow.Display(this);
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
UpdateErrorLink();
}
private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
{
var menuItem = (MenuItem) e.Source;
if (Equals(menuItem, MenuRefresh))
ReadCurrentFeed(true);
else if (Equals(menuItem, MenuRefreshAll))
ReadFeeds(true);
}
private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
{
ReadFeeds(true);
}
private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
{
var menuItem = (MenuItem) e.Source;
if (Equals(menuItem, MenuOpenAllSinglePage))
OpenAllFeedItemsOnSinglePage();
else if (Equals(menuItem, MenuOpenAllMultiplePages))
OpenAllFeedItemsIndividually();
}
private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
{
var multipleOpenAction = _currentFeed.MultipleOpenAction;
switch (multipleOpenAction)
{
case MultipleOpenAction.IndividualPages:
OpenAllFeedItemsIndividually();
break;
case MultipleOpenAction.SinglePage:
OpenAllFeedItemsOnSinglePage();
break;
}
}
private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Create a new feed window
var feedWindow = new FeedWindow();
// Display the feed window and get the result
var result = feedWindow.Display(_currentFeed, this);
// If OK was clicked...
if (result.HasValue && result.Value)
{
// Save
_database.SaveChanges(() => { });
// Update feed
DisplayFeed();
}
}
private void HandleDeleteCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Confirm this delete since it is for real
if (MessageBox.Show(this, Properties.Resources.ConfirmDeleteFeed, string.Empty, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
return;
// Get the current feed
var feedToDelete = _currentFeed;
// Move to the next feed
NextFeed();
// Delete the feed
_database.SaveChanges(() => _database.Feeds.Remove(feedToDelete));
}
private void OpenAllFeedItemsOnSinglePage()
{
var fileName = Path.GetTempFileName() + ".html";
TextWriter textWriter = new StreamWriter(fileName);
using (var htmlTextWriter = new HtmlTextWriter(textWriter))
{
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
htmlTextWriter.Write(_currentFeed.Title);
htmlTextWriter.RenderEndTag();
htmlTextWriter.AddAttribute("http-equiv", "Content-Type");
htmlTextWriter.AddAttribute("content", "text/html; charset=utf-8");
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Meta);
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Body);
var sortedItems = from item in _currentFeed.Items where !item.BeenRead orderby item.Sequence ascending select item;
var firstItem = true;
foreach (var item in sortedItems)
{
if (!firstItem)
{
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Hr);
htmlTextWriter.RenderEndTag();
}
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Div);
htmlTextWriter.AddAttribute(HtmlTextWriterAttribute.Href, item.Link);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.A);
htmlTextWriter.Write(item.Title.Length == 0 ? item.Link : item.Title);
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
htmlTextWriter.RenderEndTag();
htmlTextWriter.Write(item.Description);
htmlTextWriter.RenderEndTag();
firstItem = false;
}
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderEndTag();
}
textWriter.Flush();
textWriter.Close();
InstalledBrowser.OpenLink(Settings.Default.Browser, fileName);
MarkAllItemsAsRead();
}
}

View File

@@ -1,40 +1,39 @@
using Common.Update;
using ChrisKaczor.ApplicationUpdate;
using FeedCenter.Properties;
using System.Windows;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private static void InitializeUpdate()
{
private static void InitializeUpdate()
{
UpdateCheck.ApplicationName = Properties.Resources.ApplicationDisplayName;
UpdateCheck.UpdateServer = Settings.Default.VersionLocation;
UpdateCheck.UpdateFile = Settings.Default.VersionFile;
UpdateCheck.ApplicationShutdown = ApplicationShutdown;
UpdateCheck.ApplicationCurrentMessage = ApplicationCurrentMessage;
UpdateCheck.ApplicationUpdateMessage = ApplicationUpdateMessage;
}
private static bool ApplicationUpdateMessage(string title, string message)
{
return MessageBox.Show(message, title, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes;
}
private static void ApplicationCurrentMessage(string title, string message)
{
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);
}
private static void ApplicationShutdown()
{
Application.Current.Shutdown();
}
private void HandleNewVersionLinkClick(object sender, RoutedEventArgs e)
{
// Display update information
UpdateCheck.DisplayUpdateInformation(true);
}
UpdateCheck.Initialize(ServerType.GitHub,
Settings.Default.VersionLocation,
string.Empty,
Properties.Resources.ApplicationDisplayName,
ApplicationShutdown,
ApplicationCurrentMessage,
ApplicationUpdateMessage);
}
}
private static bool ApplicationUpdateMessage(string title, string message)
{
return MessageBox.Show(message, title, MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes;
}
private static void ApplicationCurrentMessage(string title, string message)
{
MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information);
}
private static void ApplicationShutdown()
{
Application.Current.Shutdown();
}
private void HandleNewVersionLinkClick(object sender, RoutedEventArgs e)
{
UpdateCheck.DisplayUpdateInformation(true);
}
}

View File

@@ -1,180 +1,172 @@
using FeedCenter.Properties;
using DebounceThrottle;
using FeedCenter.Properties;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using Common.Helpers;
namespace FeedCenter
namespace FeedCenter;
public partial class MainWindow
{
public partial class MainWindow
private void LoadWindowSettings()
{
private void LoadWindowSettings()
// Get the last window location
var windowLocation = Settings.Default.WindowLocation;
// Set the window into position
Left = windowLocation.X;
Top = windowLocation.Y;
// Get the last window size
var windowSize = Settings.Default.WindowSize;
// Set the window into the previous size if it is valid
if (!windowSize.Width.Equals(0) && !windowSize.Height.Equals(0))
{
// Get the last window location
var windowLocation = Settings.Default.WindowLocation;
// Set the window into position
Left = windowLocation.X;
Top = windowLocation.Y;
// Get the last window size
var windowSize = Settings.Default.WindowSize;
// Set the window into the previous size if it is valid
if (!windowSize.Width.Equals(0) && !windowSize.Height.Equals(0))
{
Width = windowSize.Width;
Height = windowSize.Height;
}
// Set the location of the navigation tray
switch (Settings.Default.ToolbarLocation)
{
case Dock.Top:
NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "TopToolbarRow");
break;
case Dock.Bottom:
NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "BottomToolbarRow");
break;
}
// Load the lock state
HandleWindowLockState();
Width = windowSize.Width;
Height = windowSize.Height;
}
private void SaveWindowSettings()
// Set the location of the navigation tray
switch (Settings.Default.ToolbarLocation)
{
// Set the last window location
Settings.Default.WindowLocation = new Point(Left, Top);
// Set the last window size
Settings.Default.WindowSize = new Size(Width, Height);
// Save the dock on the navigation tray
Settings.Default.ToolbarLocation = NameBasedGrid.NameBasedGrid.GetRow(NavigationToolbarTray) == "TopToolbarRow" ? Dock.Top : Dock.Bottom;
// Save settings
Settings.Default.Save();
case Dock.Top:
NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "TopToolbarRow");
break;
case Dock.Bottom:
NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "BottomToolbarRow");
break;
}
private void HandleWindowLockState()
{
// Set the resize mode for the window
ResizeMode = Settings.Default.WindowLocked ? ResizeMode.NoResize : ResizeMode.CanResize;
// Show or hide the border
WindowBorder.BorderBrush = Settings.Default.WindowLocked ? SystemColors.ActiveBorderBrush : Brushes.Transparent;
// Update the borders
UpdateBorder();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
// Ditch the worker
if (_feedReadWorker != null)
{
_feedReadWorker.CancelAsync();
_feedReadWorker.Dispose();
}
// Get rid of the timer
TerminateTimer();
// Save current window settings
SaveWindowSettings();
// Save settings
Settings.Default.Save();
// Save options
_database.SaveChanges();
// Get rid of the notification icon
NotificationIcon.Dispose();
}
private DelayedMethod _windowStateDelay;
private void HandleWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
if (_windowStateDelay == null)
_windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);
_windowStateDelay.Reset();
}
private void HandleWindowLocationChanged(object sender, EventArgs e)
{
if (_windowStateDelay == null)
_windowStateDelay = new DelayedMethod(500, UpdateWindowSettings);
_windowStateDelay.Reset();
}
private void UpdateBorder()
{
var windowInteropHelper = new WindowInteropHelper(this);
var screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle);
var rectangle = new System.Drawing.Rectangle
{
X = (int) Left,
Y = (int) Top,
Width = (int) Width,
Height = (int) Height
};
var borderThickness = new Thickness();
if (rectangle.Right != screen.WorkingArea.Right)
borderThickness.Right = 1;
if (rectangle.Left != screen.WorkingArea.Left)
borderThickness.Left = 1;
if (rectangle.Top != screen.WorkingArea.Top)
borderThickness.Top = 1;
if (rectangle.Bottom != screen.WorkingArea.Bottom)
borderThickness.Bottom = 1;
WindowBorder.BorderThickness = borderThickness;
}
private void UpdateWindowSettings()
{
// Save current window settings
SaveWindowSettings();
// Update the border
UpdateBorder();
}
private bool _activated;
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
if (_activated)
return;
_activated = true;
// Load the lock state
HandleWindowLockState();
// Watch for size and location changes
SizeChanged += HandleWindowSizeChanged;
LocationChanged += HandleWindowLocationChanged;
// Watch for setting changes
Settings.Default.PropertyChanged += HandlePropertyChanged;
}
// Load the lock state
HandleWindowLockState();
}
}
private void SaveWindowSettings()
{
// Set the last window location
Settings.Default.WindowLocation = new Point(Left, Top);
// Set the last window size
Settings.Default.WindowSize = new Size(Width, Height);
// Save the dock on the navigation tray
Settings.Default.ToolbarLocation = NameBasedGrid.NameBasedGrid.GetRow(NavigationToolbarTray) == "TopToolbarRow" ? Dock.Top : Dock.Bottom;
// Save settings
Settings.Default.Save();
}
private void HandleWindowLockState()
{
// Set the resize mode for the window
ResizeMode = Settings.Default.WindowLocked ? ResizeMode.NoResize : ResizeMode.CanResize;
// Show or hide the border
WindowBorder.BorderBrush = Settings.Default.WindowLocked ? SystemColors.ActiveBorderBrush : Brushes.Transparent;
// Update the borders
UpdateBorder();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
// Ditch the worker
if (_feedReadWorker != null)
{
_feedReadWorker.CancelAsync();
_feedReadWorker.Dispose();
}
// Get rid of the timer
TerminateTimer();
// Save current window settings
SaveWindowSettings();
// Save settings
_database.SaveChanges(Settings.Default.Save);
// Get rid of the notification icon
NotificationIcon.Dispose();
}
private readonly DebounceDispatcher _updateWindowSettingsDispatcher = new(500);
private void HandleWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
_updateWindowSettingsDispatcher.Debounce(() => Dispatcher.Invoke(UpdateWindowSettings));
}
private void HandleWindowLocationChanged(object sender, EventArgs e)
{
_updateWindowSettingsDispatcher.Debounce(() => Dispatcher.Invoke(UpdateWindowSettings));
}
private void UpdateBorder()
{
var windowInteropHelper = new WindowInteropHelper(this);
var screen = WpfScreenHelper.Screen.FromHandle(windowInteropHelper.Handle);
var rectangle = new Rect
{
X = Left,
Y = Top,
Width = Width,
Height = Height
};
var borderThickness = new Thickness();
if (!rectangle.Right.Equals(screen.WorkingArea.Right))
borderThickness.Right = 1;
if (!rectangle.Left.Equals(screen.WorkingArea.Left))
borderThickness.Left = 1;
if (!rectangle.Top.Equals(screen.WorkingArea.Top))
borderThickness.Top = 1;
if (!rectangle.Bottom.Equals(screen.WorkingArea.Bottom))
borderThickness.Bottom = 1;
WindowBorder.BorderThickness = borderThickness;
}
private void UpdateWindowSettings()
{
// Save current window settings
SaveWindowSettings();
// Update the border
UpdateBorder();
}
private bool _activated;
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
if (_activated)
return;
_activated = true;
// Load the lock state
HandleWindowLockState();
// Watch for size and location changes
SizeChanged += HandleWindowSizeChanged;
LocationChanged += HandleWindowLocationChanged;
// Watch for setting changes
Settings.Default.PropertyChanged += HandlePropertyChanged;
}
}

View File

@@ -1,34 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FeedCenter
{
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
public partial class FeedCenterEntities : DbContext
{
public FeedCenterEntities()
: base("name=FeedCenterEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Category> Categories { get; set; }
public virtual DbSet<Feed> Feeds { get; set; }
public virtual DbSet<FeedAction> FeedActions { get; set; }
public virtual DbSet<FeedItem> FeedItems { get; set; }
public virtual DbSet<Setting> Settings { get; set; }
}
}

View File

@@ -1,636 +0,0 @@
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF6.Utility.CS.ttinclude"#><#@
output extension=".cs"#><#
const string inputFile = @"Model.edmx";
var textTransform = DynamicTextTransformation.Create(this);
var code = new CodeGenerationTools(this);
var ef = new MetadataTools(this);
var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
var loader = new EdmMetadataLoader(textTransform.Host, textTransform.Errors);
var itemCollection = loader.CreateEdmItemCollection(inputFile);
var modelNamespace = loader.GetModelNamespace(inputFile);
var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);
var container = itemCollection.OfType<EntityContainer>().FirstOrDefault();
if (container == null)
{
return string.Empty;
}
#>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------
<#
var codeNamespace = code.VsNamespaceSuggestion();
if (!String.IsNullOrEmpty(codeNamespace))
{
#>
namespace <#=code.EscapeNamespace(codeNamespace)#>
{
<#
PushIndent(" ");
}
#>
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
<#
if (container.FunctionImports.Any())
{
#>
using System.Data.Entity.Core.Objects;
using System.Linq;
<#
}
#>
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
// Note: the DbSet members are defined below such that the getter and
// setter always have the same accessibility as the DbSet definition
if (Accessibility.ForReadOnlyProperty(entitySet) != "public")
{
#>
<#=codeStringGenerator.DbSetInitializer(entitySet)#>
<#
}
}
#>
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
<#
foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
{
#>
<#=codeStringGenerator.DbSet(entitySet)#>
<#
}
foreach (var edmFunction in container.FunctionImports)
{
WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: false);
}
#>
}
<#
if (!String.IsNullOrEmpty(codeNamespace))
{
PopIndent();
#>
}
<#
}
#>
<#+
private void WriteFunctionImport(TypeMapper typeMapper, CodeStringGenerator codeStringGenerator, EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
{
if (typeMapper.IsComposable(edmFunction))
{
#>
[DbFunction("<#=edmFunction.NamespaceName#>", "<#=edmFunction.Name#>")]
<#=codeStringGenerator.ComposableFunctionMethod(edmFunction, modelNamespace)#>
{
<#+
codeStringGenerator.WriteFunctionParameters(edmFunction, WriteFunctionParameter);
#>
<#=codeStringGenerator.ComposableCreateQuery(edmFunction, modelNamespace)#>
}
<#+
}
else
{
#>
<#=codeStringGenerator.FunctionMethod(edmFunction, modelNamespace, includeMergeOption)#>
{
<#+
codeStringGenerator.WriteFunctionParameters(edmFunction, WriteFunctionParameter);
#>
<#=codeStringGenerator.ExecuteFunction(edmFunction, modelNamespace, includeMergeOption)#>
}
<#+
if (typeMapper.GenerateMergeOptionFunction(edmFunction, includeMergeOption))
{
WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: true);
}
}
}
public void WriteFunctionParameter(string name, string isNotNull, string notNullInit, string nullInit)
{
#>
var <#=name#> = <#=isNotNull#> ?
<#=notNullInit#> :
<#=nullInit#>;
<#+
}
public const string TemplateId = "CSharp_DbContext_Context_EF6";
public class CodeStringGenerator
{
private readonly CodeGenerationTools _code;
private readonly TypeMapper _typeMapper;
private readonly MetadataTools _ef;
public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef)
{
ArgumentNotNull(code, "code");
ArgumentNotNull(typeMapper, "typeMapper");
ArgumentNotNull(ef, "ef");
_code = code;
_typeMapper = typeMapper;
_ef = ef;
}
public string Property(EdmProperty edmProperty)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}
public string NavigationProperty(NavigationProperty navProp)
{
var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
_code.Escape(navProp),
_code.SpaceAfter(Accessibility.ForGetter(navProp)),
_code.SpaceAfter(Accessibility.ForSetter(navProp)));
}
public string AccessibilityAndVirtual(string accessibility)
{
return accessibility + (accessibility != "private" ? " virtual" : "");
}
public string EntityClassOpening(EntityType entity)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1}partial class {2}{3}",
Accessibility.ForType(entity),
_code.SpaceAfter(_code.AbstractOption(entity)),
_code.Escape(entity),
_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}
public string EnumOpening(SimpleType enumType)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} enum {1} : {2}",
Accessibility.ForType(enumType),
_code.Escape(enumType),
_code.Escape(_typeMapper.UnderlyingClrType(enumType)));
}
public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter)
{
var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))
{
var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";
var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";
var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))";
writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit);
}
}
public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace)
{
var parameters = _typeMapper.GetParameters(edmFunction);
return string.Format(
CultureInfo.InvariantCulture,
"{0} IQueryable<{1}> {2}({3})",
AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
_typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
_code.Escape(edmFunction),
string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()));
}
public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace)
{
var parameters = _typeMapper.GetParameters(edmFunction);
return string.Format(
CultureInfo.InvariantCulture,
"return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});",
_typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
edmFunction.NamespaceName,
edmFunction.Name,
string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()),
_code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())));
}
public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
{
var parameters = _typeMapper.GetParameters(edmFunction);
var returnType = _typeMapper.GetReturnType(edmFunction);
var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray());
if (includeMergeOption)
{
paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption";
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2}({3})",
AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
_code.Escape(edmFunction),
paramList);
}
public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
{
var parameters = _typeMapper.GetParameters(edmFunction);
var returnType = _typeMapper.GetReturnType(edmFunction);
var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));
if (includeMergeOption)
{
callParams = ", mergeOption" + callParams;
}
return string.Format(
CultureInfo.InvariantCulture,
"return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});",
returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
edmFunction.Name,
callParams);
}
public string DbSet(EntitySet entitySet)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} virtual DbSet<{1}> {2} {{ get; set; }}",
Accessibility.ForReadOnlyProperty(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType),
_code.Escape(entitySet));
}
public string DbSetInitializer(EntitySet entitySet)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} = Set<{1}>();",
_code.Escape(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType));
}
public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
? string.Format(
CultureInfo.InvariantCulture,
"{0}using System;{1}" +
"{2}",
inHeader ? Environment.NewLine : "",
includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
inHeader ? "" : Environment.NewLine)
: "";
}
}
public class TypeMapper
{
private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName";
private readonly System.Collections.IList _errors;
private readonly CodeGenerationTools _code;
private readonly MetadataTools _ef;
public static string FixNamespaces(string typeName)
{
return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial.");
}
public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors)
{
ArgumentNotNull(code, "code");
ArgumentNotNull(ef, "ef");
ArgumentNotNull(errors, "errors");
_code = code;
_ef = ef;
_errors = errors;
}
public string GetTypeName(TypeUsage typeUsage)
{
return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
}
public string GetTypeName(EdmType edmType)
{
return GetTypeName(edmType, isNullable: null, modelNamespace: null);
}
public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
{
return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
}
public string GetTypeName(EdmType edmType, string modelNamespace)
{
return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
}
public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace)
{
if (edmType == null)
{
return null;
}
var collectionType = edmType as CollectionType;
if (collectionType != null)
{
return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace));
}
var typeName = _code.Escape(edmType.MetadataProperties
.Where(p => p.Name == ExternalTypeNameAttributeName)
.Select(p => (string)p.Value)
.FirstOrDefault())
?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ?
_code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) :
_code.Escape(edmType));
if (edmType is StructuralType)
{
return typeName;
}
if (edmType is SimpleType)
{
var clrType = UnderlyingClrType(edmType);
if (!IsEnumType(edmType))
{
typeName = _code.Escape(clrType);
}
typeName = FixNamespaces(typeName);
return clrType.IsValueType && isNullable == true ?
String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) :
typeName;
}
throw new ArgumentException("edmType");
}
public Type UnderlyingClrType(EdmType edmType)
{
ArgumentNotNull(edmType, "edmType");
var primitiveType = edmType as PrimitiveType;
if (primitiveType != null)
{
return primitiveType.ClrEquivalentType;
}
if (IsEnumType(edmType))
{
return GetEnumUnderlyingType(edmType).ClrEquivalentType;
}
return typeof(object);
}
public object GetEnumMemberValue(MetadataItem enumMember)
{
ArgumentNotNull(enumMember, "enumMember");
var valueProperty = enumMember.GetType().GetProperty("Value");
return valueProperty == null ? null : valueProperty.GetValue(enumMember, null);
}
public string GetEnumMemberName(MetadataItem enumMember)
{
ArgumentNotNull(enumMember, "enumMember");
var nameProperty = enumMember.GetType().GetProperty("Name");
return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null);
}
public System.Collections.IEnumerable GetEnumMembers(EdmType enumType)
{
ArgumentNotNull(enumType, "enumType");
var membersProperty = enumType.GetType().GetProperty("Members");
return membersProperty != null
? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null)
: Enumerable.Empty<MetadataItem>();
}
public bool EnumIsFlags(EdmType enumType)
{
ArgumentNotNull(enumType, "enumType");
var isFlagsProperty = enumType.GetType().GetProperty("IsFlags");
return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null);
}
public bool IsEnumType(GlobalItem edmType)
{
ArgumentNotNull(edmType, "edmType");
return edmType.GetType().Name == "EnumType";
}
public PrimitiveType GetEnumUnderlyingType(EdmType enumType)
{
ArgumentNotNull(enumType, "enumType");
return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null);
}
public string CreateLiteral(object value)
{
if (value == null || value.GetType() != typeof(TimeSpan))
{
return _code.CreateLiteral(value);
}
return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks);
}
public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
{
ArgumentNotNull(types, "types");
ArgumentNotNull(sourceFile, "sourceFile");
var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
if (types.Any(item => !hash.Add(item)))
{
_errors.Add(
new CompilerError(sourceFile, -1, -1, "6023",
String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict"))));
return false;
}
return true;
}
public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection)
{
return GetItemsToGenerate<SimpleType>(itemCollection)
.Where(e => IsEnumType(e));
}
public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType
{
return itemCollection
.OfType<T>()
.Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName))
.OrderBy(i => i.Name);
}
public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection)
{
return itemCollection
.Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i))
.Select(g => GetGlobalItemName(g));
}
public string GetGlobalItemName(GlobalItem item)
{
if (item is EdmType)
{
return ((EdmType)item).Name;
}
else
{
return ((EntityContainer)item).Name;
}
}
public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetComplexProperties(EntityType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
}
public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
}
public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type)
{
return type.NavigationProperties.Where(np => np.DeclaringType == type);
}
public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type)
{
return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
}
public FunctionParameter GetReturnParameter(EdmFunction edmFunction)
{
ArgumentNotNull(edmFunction, "edmFunction");
var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters");
return returnParamsProperty == null
? edmFunction.ReturnParameter
: ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault();
}
public bool IsComposable(EdmFunction edmFunction)
{
ArgumentNotNull(edmFunction, "edmFunction");
var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute");
return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null);
}
public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction)
{
return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
}
public TypeUsage GetReturnType(EdmFunction edmFunction)
{
var returnParam = GetReturnParameter(edmFunction);
return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage);
}
public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption)
{
var returnType = GetReturnType(edmFunction);
return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
}
}
public static void ArgumentNotNull<T>(T arg, string name) where T : class
{
if (arg == null)
{
throw new ArgumentNullException(name);
}
}
#>

View File

@@ -1,10 +0,0 @@
// T4 code generation is enabled for model 'D:\Code\Personal\FeedCenter\Application\Model.edmx'.
// To enable legacy code generation, change the value of the 'Code Generation Strategy' designer
// property to 'Legacy ObjectContext'. This property is available in the Properties Window when the model
// is open in the designer.
// If no context and entity classes have been generated, it may be because you created an empty model but
// have not yet chosen which version of Entity Framework to use. To generate a context class and entity
// classes for your model, open the model in the designer, right-click on the designer surface, and
// select 'Update Model from Database...', 'Generate Database from Model...', or 'Add Code Generation
// Item...'.

View File

@@ -1,9 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

View File

@@ -1,383 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">
<!-- EF Runtime content -->
<edmx:Runtime>
<!-- SSDL content -->
<edmx:StorageModels>
<Schema Namespace="FeedCenterModel.Store" Provider="System.Data.SqlServerCe.4.0" ProviderManifestToken="4.0" Alias="Self" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns:customannotation="http://schemas.microsoft.com/ado/2013/11/edm/customannotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">
<EntityType Name="Category">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="uniqueidentifier" Nullable="false" />
<Property Name="Name" Type="nvarchar" MaxLength="1000" Nullable="false" />
</EntityType>
<EntityType Name="Feed">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="uniqueidentifier" Nullable="false" />
<Property Name="Name" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Title" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Source" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Link" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Description" Type="ntext" Nullable="false" />
<Property Name="LastChecked" Type="datetime" Nullable="false" />
<Property Name="CheckInterval" Type="int" Nullable="false" />
<Property Name="Enabled" Type="bit" Nullable="false" />
<Property Name="Authenticate" Type="bit" Nullable="false" />
<Property Name="Username" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Password" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Domain" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="LastReadResult" Type="int" Nullable="false" />
<Property Name="LastUpdated" Type="datetime" Nullable="false" />
<Property Name="ItemComparison" Type="tinyint" Nullable="false" />
<Property Name="CategoryID" Type="uniqueidentifier" Nullable="false" />
<Property Name="MultipleOpenAction" Type="int" Nullable="false" />
</EntityType>
<EntityType Name="FeedAction">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="uniqueidentifier" Nullable="false" />
<Property Name="FeedID" Type="uniqueidentifier" Nullable="false" />
<Property Name="Field" Type="int" Nullable="false" />
<Property Name="Search" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Replace" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Sequence" Type="int" Nullable="false" />
</EntityType>
<EntityType Name="FeedItem">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Name="ID" Type="uniqueidentifier" Nullable="false" />
<Property Name="FeedID" Type="uniqueidentifier" Nullable="false" />
<Property Name="Title" Type="ntext" Nullable="false" />
<Property Name="Link" Type="nvarchar" MaxLength="1000" Nullable="false" />
<Property Name="Description" Type="ntext" Nullable="false" />
<Property Name="BeenRead" Type="bit" Nullable="false" />
<Property Name="LastFound" Type="datetime" Nullable="false" />
<Property Name="New" Type="bit" Nullable="false" />
<Property Name="Sequence" Type="int" Nullable="false" />
<Property Name="Guid" Type="nvarchar" MaxLength="1000" Nullable="false" />
</EntityType>
<EntityType Name="Setting">
<Key>
<PropertyRef Name="Name" />
<PropertyRef Name="Version" />
</Key>
<Property Name="Name" Type="nvarchar" MaxLength="500" Nullable="false" />
<Property Name="Value" Type="nvarchar" MaxLength="3500" Nullable="false" />
<Property Name="Version" Type="nvarchar" MaxLength="50" Nullable="false" />
</EntityType>
<Association Name="FK_Feed_Category">
<End Role="Category" Type="Self.Category" Multiplicity="1" />
<End Role="Feed" Type="Self.Feed" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Category">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="Feed">
<PropertyRef Name="CategoryID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="FK_FeedAction_Feed">
<End Role="Feed" Type="Self.Feed" Multiplicity="1">
<OnDelete Action="Cascade" />
</End>
<End Role="FeedAction" Type="Self.FeedAction" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Feed">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="FeedAction">
<PropertyRef Name="FeedID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="FK_FeedItem_Feed">
<End Role="Feed" Type="Self.Feed" Multiplicity="1">
<OnDelete Action="Cascade" />
</End>
<End Role="FeedItem" Type="Self.FeedItem" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Feed">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="FeedItem">
<PropertyRef Name="FeedID" />
</Dependent>
</ReferentialConstraint>
</Association>
<EntityContainer Name="FeedCenterModelStoreContainer">
<EntitySet Name="Category" EntityType="Self.Category" store:Type="Tables" />
<EntitySet Name="Feed" EntityType="Self.Feed" store:Type="Tables" />
<EntitySet Name="FeedAction" EntityType="Self.FeedAction" store:Type="Tables" />
<EntitySet Name="FeedItem" EntityType="Self.FeedItem" store:Type="Tables" />
<EntitySet Name="Setting" EntityType="Self.Setting" store:Type="Tables" />
<AssociationSet Name="FK_Feed_Category" Association="Self.FK_Feed_Category">
<End Role="Category" EntitySet="Category" />
<End Role="Feed" EntitySet="Feed" />
</AssociationSet>
<AssociationSet Name="FK_FeedAction_Feed" Association="Self.FK_FeedAction_Feed">
<End Role="Feed" EntitySet="Feed" />
<End Role="FeedAction" EntitySet="FeedAction" />
</AssociationSet>
<AssociationSet Name="FK_FeedItem_Feed" Association="Self.FK_FeedItem_Feed">
<End Role="Feed" EntitySet="Feed" />
<End Role="FeedItem" EntitySet="FeedItem" />
</AssociationSet>
</EntityContainer>
</Schema></edmx:StorageModels>
<!-- CSDL content -->
<edmx:ConceptualModels>
<Schema Namespace="FeedCenterModel" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="FeedCenterEntities" annotation:LazyLoadingEnabled="true" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation">
<EntitySet Name="Categories" EntityType="FeedCenterModel.Category" />
<EntitySet Name="Feeds" EntityType="FeedCenterModel.Feed" />
<EntitySet Name="FeedActions" EntityType="FeedCenterModel.FeedAction" />
<EntitySet Name="FeedItems" EntityType="FeedCenterModel.FeedItem" />
<EntitySet Name="Settings" EntityType="FeedCenterModel.Setting" />
<AssociationSet Name="FK_Feed_Category" Association="FeedCenterModel.FK_Feed_Category">
<End Role="Category" EntitySet="Categories" />
<End Role="Feed" EntitySet="Feeds" />
</AssociationSet>
<AssociationSet Name="FK_FeedAction_Feed" Association="FeedCenterModel.FK_FeedAction_Feed">
<End Role="Feed" EntitySet="Feeds" />
<End Role="FeedAction" EntitySet="FeedActions" />
</AssociationSet>
<AssociationSet Name="FK_FeedItem_Feed" Association="FeedCenterModel.FK_FeedItem_Feed">
<End Role="Feed" EntitySet="Feeds" />
<End Role="FeedItem" EntitySet="FeedItems" />
</AssociationSet>
</EntityContainer>
<EntityType Name="Category">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Type="Guid" Name="ID" Nullable="false" />
<Property Type="String" Name="Name" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" />
<NavigationProperty Name="Feeds" Relationship="FeedCenterModel.FK_Feed_Category" FromRole="Category" ToRole="Feed" />
</EntityType>
<EntityType Name="Feed">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Type="Guid" Name="ID" Nullable="false" />
<Property Type="String" Name="Name" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Title" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Source" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Link" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Description" Nullable="false" MaxLength="Max" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="DateTime" Name="LastChecked" Nullable="false" DefaultValue="1900-01-01 00:00:00.000Z" Precision="3" />
<Property Type="Int32" Name="CheckInterval" Nullable="false" DefaultValue="60" />
<Property Type="Boolean" Name="Enabled" Nullable="false" DefaultValue="True" />
<Property Type="Boolean" Name="Authenticate" Nullable="false" DefaultValue="False" />
<Property Type="String" Name="Username" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Password" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Domain" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="FeedCenterModel.FeedReadResult" Name="LastReadResult" Nullable="false" />
<Property Type="DateTime" Name="LastUpdated" Nullable="false" DefaultValue="1900-01-01 00:00:00.000Z" Precision="3" />
<Property Type="FeedCenterModel.FeedItemComparison" Name="ItemComparison" Nullable="false" />
<Property Type="Guid" Name="CategoryID" Nullable="false" a:GetterAccess="Private" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" a:SetterAccess="Private" />
<NavigationProperty Name="Category" Relationship="FeedCenterModel.FK_Feed_Category" FromRole="Feed" ToRole="Category" />
<NavigationProperty Name="Actions" Relationship="FeedCenterModel.FK_FeedAction_Feed" FromRole="Feed" ToRole="FeedAction" />
<NavigationProperty Name="Items" Relationship="FeedCenterModel.FK_FeedItem_Feed" FromRole="Feed" ToRole="FeedItem" />
<Property Type="FeedCenterModel.MultipleOpenAction" Name="MultipleOpenAction" Nullable="false" />
</EntityType>
<EntityType Name="FeedAction">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Type="Guid" Name="ID" Nullable="false" />
<Property Type="Guid" Name="FeedID" Nullable="false" />
<Property Type="Int32" Name="Field" Nullable="false" />
<Property Type="String" Name="Search" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" />
<Property Type="String" Name="Replace" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" />
<Property Type="Int32" Name="Sequence" Nullable="false" />
<NavigationProperty Name="Feed" Relationship="FeedCenterModel.FK_FeedAction_Feed" FromRole="FeedAction" ToRole="Feed" />
</EntityType>
<EntityType Name="FeedItem">
<Key>
<PropertyRef Name="ID" />
</Key>
<Property Type="Guid" Name="ID" Nullable="false" />
<Property Type="Guid" Name="FeedID" Nullable="false" />
<Property Type="String" Name="Title" Nullable="false" MaxLength="Max" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Link" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="String" Name="Description" Nullable="false" MaxLength="Max" FixedLength="false" Unicode="true" DefaultValue="" />
<Property Type="Boolean" Name="BeenRead" Nullable="false" />
<Property Type="DateTime" Name="LastFound" Nullable="false" DefaultValue="1900-01-01 00:00:00.000Z" Precision="3" />
<Property Type="Boolean" Name="New" Nullable="false" />
<NavigationProperty Name="Feed" Relationship="FeedCenterModel.FK_FeedItem_Feed" FromRole="FeedItem" ToRole="Feed" />
<Property Type="String" Name="Guid" Nullable="false" MaxLength="1000" FixedLength="false" Unicode="true" />
<Property Type="Int32" Name="Sequence" Nullable="false" />
</EntityType>
<EntityType Name="Setting">
<Key>
<PropertyRef Name="Name" />
<PropertyRef Name="Version" />
</Key>
<Property Type="String" Name="Name" Nullable="false" MaxLength="500" FixedLength="false" Unicode="true" />
<Property Type="String" Name="Value" Nullable="false" MaxLength="3500" FixedLength="false" Unicode="true" />
<Property Type="String" Name="Version" Nullable="false" MaxLength="50" FixedLength="false" Unicode="true" />
</EntityType>
<Association Name="FK_Feed_Category">
<End Type="FeedCenterModel.Category" Role="Category" Multiplicity="1" />
<End Type="FeedCenterModel.Feed" Role="Feed" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Category">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="Feed">
<PropertyRef Name="CategoryID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="FK_FeedAction_Feed">
<End Type="FeedCenterModel.Feed" Role="Feed" Multiplicity="1" />
<End Type="FeedCenterModel.FeedAction" Role="FeedAction" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Feed">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="FeedAction">
<PropertyRef Name="FeedID" />
</Dependent>
</ReferentialConstraint>
</Association>
<Association Name="FK_FeedItem_Feed">
<End Type="FeedCenterModel.Feed" Role="Feed" Multiplicity="1" />
<End Type="FeedCenterModel.FeedItem" Role="FeedItem" Multiplicity="*" />
<ReferentialConstraint>
<Principal Role="Feed">
<PropertyRef Name="ID" />
</Principal>
<Dependent Role="FeedItem">
<PropertyRef Name="FeedID" />
</Dependent>
</ReferentialConstraint>
</Association>
<EnumType Name="FeedReadResult" a:ExternalTypeName="FeedCenter.FeedReadResult" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />
<EnumType Name="FeedItemComparison" UnderlyingType="Byte" a:ExternalTypeName="FeedCenter.FeedItemComparison" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />
<EnumType Name="MultipleOpenAction" a:ExternalTypeName="FeedCenter.MultipleOpenAction" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration" />
</Schema>
</edmx:ConceptualModels>
<!-- C-S mapping content -->
<edmx:Mappings>
<Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">
<EntityContainerMapping StorageEntityContainer="FeedCenterModelStoreContainer" CdmEntityContainer="FeedCenterEntities">
<EntitySetMapping Name="Categories">
<EntityTypeMapping TypeName="FeedCenterModel.Category">
<MappingFragment StoreEntitySet="Category">
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="ID" ColumnName="ID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="Feeds">
<EntityTypeMapping TypeName="FeedCenterModel.Feed">
<MappingFragment StoreEntitySet="Feed">
<ScalarProperty Name="MultipleOpenAction" ColumnName="MultipleOpenAction" />
<ScalarProperty Name="CategoryID" ColumnName="CategoryID" />
<ScalarProperty Name="ItemComparison" ColumnName="ItemComparison" />
<ScalarProperty Name="LastUpdated" ColumnName="LastUpdated" />
<ScalarProperty Name="LastReadResult" ColumnName="LastReadResult" />
<ScalarProperty Name="Domain" ColumnName="Domain" />
<ScalarProperty Name="Password" ColumnName="Password" />
<ScalarProperty Name="Username" ColumnName="Username" />
<ScalarProperty Name="Authenticate" ColumnName="Authenticate" />
<ScalarProperty Name="Enabled" ColumnName="Enabled" />
<ScalarProperty Name="CheckInterval" ColumnName="CheckInterval" />
<ScalarProperty Name="LastChecked" ColumnName="LastChecked" />
<ScalarProperty Name="Description" ColumnName="Description" />
<ScalarProperty Name="Link" ColumnName="Link" />
<ScalarProperty Name="Source" ColumnName="Source" />
<ScalarProperty Name="Title" ColumnName="Title" />
<ScalarProperty Name="Name" ColumnName="Name" />
<ScalarProperty Name="ID" ColumnName="ID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="FeedActions">
<EntityTypeMapping TypeName="FeedCenterModel.FeedAction">
<MappingFragment StoreEntitySet="FeedAction">
<ScalarProperty Name="Sequence" ColumnName="Sequence" />
<ScalarProperty Name="Replace" ColumnName="Replace" />
<ScalarProperty Name="Search" ColumnName="Search" />
<ScalarProperty Name="Field" ColumnName="Field" />
<ScalarProperty Name="FeedID" ColumnName="FeedID" />
<ScalarProperty Name="ID" ColumnName="ID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="FeedItems">
<EntityTypeMapping TypeName="FeedCenterModel.FeedItem">
<MappingFragment StoreEntitySet="FeedItem">
<ScalarProperty Name="Sequence" ColumnName="Sequence" />
<ScalarProperty Name="Guid" ColumnName="Guid" />
<ScalarProperty Name="New" ColumnName="New" />
<ScalarProperty Name="LastFound" ColumnName="LastFound" />
<ScalarProperty Name="BeenRead" ColumnName="BeenRead" />
<ScalarProperty Name="Description" ColumnName="Description" />
<ScalarProperty Name="Link" ColumnName="Link" />
<ScalarProperty Name="Title" ColumnName="Title" />
<ScalarProperty Name="FeedID" ColumnName="FeedID" />
<ScalarProperty Name="ID" ColumnName="ID" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
<EntitySetMapping Name="Settings">
<EntityTypeMapping TypeName="FeedCenterModel.Setting">
<MappingFragment StoreEntitySet="Setting">
<ScalarProperty Name="Version" ColumnName="Version" />
<ScalarProperty Name="Value" ColumnName="Value" />
<ScalarProperty Name="Name" ColumnName="Name" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
</EntityContainerMapping>
</Mapping>
</edmx:Mappings>
</edmx:Runtime>
<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
<Designer xmlns="http://schemas.microsoft.com/ado/2009/11/edmx">
<Connection>
<DesignerInfoPropertySet>
<DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />
</DesignerInfoPropertySet>
</Connection>
<Options>
<DesignerInfoPropertySet>
<DesignerProperty Name="ValidateOnBuild" Value="true" />
<DesignerProperty Name="EnablePluralization" Value="True" />
<DesignerProperty Name="IncludeForeignKeysInModel" Value="True" />
<DesignerProperty Name="CodeGenerationStrategy" Value="None" />
<DesignerProperty Name="UseLegacyProvider" Value="False" />
</DesignerInfoPropertySet>
</Options>
<!-- Diagram content (shape and connector positions) -->
<Diagrams>
<Diagram Name="Model">
<EntityTypeShape EntityType="FeedCenterModel.Category" Width="1.5" PointX="0.75" PointY="3.375" Height="1.5956835937499996" />
<EntityTypeShape EntityType="FeedCenterModel.Feed" Width="1.5" PointX="3" PointY="1.625" Height="5.057109375" />
<EntityTypeShape EntityType="FeedCenterModel.FeedAction" Width="1.5" PointX="5.25" PointY="1.125" Height="2.3648893229166674" />
<EntityTypeShape EntityType="FeedCenterModel.FeedItem" Width="1.5" PointX="5.25" PointY="4.25" Height="2.9417936197916656" />
<EntityTypeShape EntityType="FeedCenterModel.Setting" Width="1.5" PointX="7.75" PointY="0.75" Height="1.5956835937499996" />
<AssociationConnector Association="FeedCenterModel.FK_Feed_Category">
<ConnectorPoint PointX="2.25" PointY="4.172841796875" />
<ConnectorPoint PointX="3" PointY="4.172841796875" />
</AssociationConnector>
<AssociationConnector Association="FeedCenterModel.FK_FeedAction_Feed">
<ConnectorPoint PointX="4.5" PointY="2.5574446614583337" />
<ConnectorPoint PointX="5.25" PointY="2.5574446614583337" />
</AssociationConnector>
<AssociationConnector Association="FeedCenterModel.FK_FeedItem_Feed">
<ConnectorPoint PointX="4.5" PointY="5.4660546875" />
<ConnectorPoint PointX="5.25" PointY="5.4660546875" />
</AssociationConnector>
</Diagram>
</Diagrams>
</Designer>
</edmx:Edmx>

View File

@@ -1,726 +0,0 @@
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF6.Utility.CS.ttinclude"#><#@
output extension=".cs"#><#
const string inputFile = @"Model.edmx";
var textTransform = DynamicTextTransformation.Create(this);
var code = new CodeGenerationTools(this);
var ef = new MetadataTools(this);
var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
var fileManager = EntityFrameworkTemplateFileManager.Create(this);
var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);
if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))
{
return string.Empty;
}
WriteHeader(codeStringGenerator, fileManager);
foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
{
fileManager.StartNewFile(entity.Name + ".cs");
BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
<#
var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
var complexProperties = typeMapper.GetComplexProperties(entity);
if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())
{
#>
public <#=code.Escape(entity)#>()
{
<#
foreach (var edmProperty in propertiesWithDefaultValues)
{
#>
this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
}
foreach (var navigationProperty in collectionNavigationProperties)
{
#>
this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
}
foreach (var complexProperty in complexProperties)
{
#>
this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
<#
}
#>
}
<#
}
var simpleProperties = typeMapper.GetSimpleProperties(entity);
if (simpleProperties.Any())
{
foreach (var edmProperty in simpleProperties)
{
#>
<#=codeStringGenerator.Property(edmProperty)#>
<#
}
}
if (complexProperties.Any())
{
#>
<#
foreach(var complexProperty in complexProperties)
{
#>
<#=codeStringGenerator.Property(complexProperty)#>
<#
}
}
var navigationProperties = typeMapper.GetNavigationProperties(entity);
if (navigationProperties.Any())
{
#>
<#
foreach (var navigationProperty in navigationProperties)
{
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
#>
}
<#
EndNamespace(code);
}
foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection))
{
fileManager.StartNewFile(complex.Name + ".cs");
BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
{
<#
var complexProperties = typeMapper.GetComplexProperties(complex);
var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex);
if (propertiesWithDefaultValues.Any() || complexProperties.Any())
{
#>
public <#=code.Escape(complex)#>()
{
<#
foreach (var edmProperty in propertiesWithDefaultValues)
{
#>
this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
}
foreach (var complexProperty in complexProperties)
{
#>
this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
<#
}
#>
}
<#
}
var simpleProperties = typeMapper.GetSimpleProperties(complex);
if (simpleProperties.Any())
{
foreach(var edmProperty in simpleProperties)
{
#>
<#=codeStringGenerator.Property(edmProperty)#>
<#
}
}
if (complexProperties.Any())
{
#>
<#
foreach(var edmProperty in complexProperties)
{
#>
<#=codeStringGenerator.Property(edmProperty)#>
<#
}
}
#>
}
<#
EndNamespace(code);
}
foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection))
{
fileManager.StartNewFile(enumType.Name + ".cs");
BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
<#
if (typeMapper.EnumIsFlags(enumType))
{
#>
[Flags]
<#
}
#>
<#=codeStringGenerator.EnumOpening(enumType)#>
{
<#
var foundOne = false;
foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType))
{
foundOne = true;
#>
<#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>,
<#
}
if (foundOne)
{
this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1);
}
#>
}
<#
EndNamespace(code);
}
fileManager.Process();
#>
<#+
public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager)
{
fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------
<#=codeStringGenerator.UsingDirectives(inHeader: true)#>
<#+
fileManager.EndBlock();
}
public void BeginNamespace(CodeGenerationTools code)
{
var codeNamespace = code.VsNamespaceSuggestion();
if (!String.IsNullOrEmpty(codeNamespace))
{
#>
namespace <#=code.EscapeNamespace(codeNamespace)#>
{
<#+
PushIndent(" ");
}
}
public void EndNamespace(CodeGenerationTools code)
{
if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion()))
{
PopIndent();
#>
}
<#+
}
}
public const string TemplateId = "CSharp_DbContext_Types_EF6";
public class CodeStringGenerator
{
private readonly CodeGenerationTools _code;
private readonly TypeMapper _typeMapper;
private readonly MetadataTools _ef;
public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef)
{
ArgumentNotNull(code, "code");
ArgumentNotNull(typeMapper, "typeMapper");
ArgumentNotNull(ef, "ef");
_code = code;
_typeMapper = typeMapper;
_ef = ef;
}
public string Property(EdmProperty edmProperty)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}
public string NavigationProperty(NavigationProperty navProp)
{
var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
_code.Escape(navProp),
_code.SpaceAfter(Accessibility.ForGetter(navProp)),
_code.SpaceAfter(Accessibility.ForSetter(navProp)));
}
public string AccessibilityAndVirtual(string accessibility)
{
return accessibility + (accessibility != "private" ? " virtual" : "");
}
public string EntityClassOpening(EntityType entity)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1}partial class {2}{3}",
Accessibility.ForType(entity),
_code.SpaceAfter(_code.AbstractOption(entity)),
_code.Escape(entity),
_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
}
public string EnumOpening(SimpleType enumType)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} enum {1} : {2}",
Accessibility.ForType(enumType),
_code.Escape(enumType),
_code.Escape(_typeMapper.UnderlyingClrType(enumType)));
}
public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter)
{
var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))
{
var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";
var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";
var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))";
writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit);
}
}
public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace)
{
var parameters = _typeMapper.GetParameters(edmFunction);
return string.Format(
CultureInfo.InvariantCulture,
"{0} IQueryable<{1}> {2}({3})",
AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
_typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
_code.Escape(edmFunction),
string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()));
}
public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace)
{
var parameters = _typeMapper.GetParameters(edmFunction);
return string.Format(
CultureInfo.InvariantCulture,
"return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});",
_typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
edmFunction.NamespaceName,
edmFunction.Name,
string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()),
_code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())));
}
public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
{
var parameters = _typeMapper.GetParameters(edmFunction);
var returnType = _typeMapper.GetReturnType(edmFunction);
var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray());
if (includeMergeOption)
{
paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption";
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2}({3})",
AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
_code.Escape(edmFunction),
paramList);
}
public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
{
var parameters = _typeMapper.GetParameters(edmFunction);
var returnType = _typeMapper.GetReturnType(edmFunction);
var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));
if (includeMergeOption)
{
callParams = ", mergeOption" + callParams;
}
return string.Format(
CultureInfo.InvariantCulture,
"return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});",
returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
edmFunction.Name,
callParams);
}
public string DbSet(EntitySet entitySet)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} virtual DbSet<{1}> {2} {{ get; set; }}",
Accessibility.ForReadOnlyProperty(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType),
_code.Escape(entitySet));
}
public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
? string.Format(
CultureInfo.InvariantCulture,
"{0}using System;{1}" +
"{2}",
inHeader ? Environment.NewLine : "",
includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
inHeader ? "" : Environment.NewLine)
: "";
}
}
public class TypeMapper
{
private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName";
private readonly System.Collections.IList _errors;
private readonly CodeGenerationTools _code;
private readonly MetadataTools _ef;
public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors)
{
ArgumentNotNull(code, "code");
ArgumentNotNull(ef, "ef");
ArgumentNotNull(errors, "errors");
_code = code;
_ef = ef;
_errors = errors;
}
public static string FixNamespaces(string typeName)
{
return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial.");
}
public string GetTypeName(TypeUsage typeUsage)
{
return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
}
public string GetTypeName(EdmType edmType)
{
return GetTypeName(edmType, isNullable: null, modelNamespace: null);
}
public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
{
return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
}
public string GetTypeName(EdmType edmType, string modelNamespace)
{
return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
}
public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace)
{
if (edmType == null)
{
return null;
}
var collectionType = edmType as CollectionType;
if (collectionType != null)
{
return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace));
}
var typeName = _code.Escape(edmType.MetadataProperties
.Where(p => p.Name == ExternalTypeNameAttributeName)
.Select(p => (string)p.Value)
.FirstOrDefault())
?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ?
_code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) :
_code.Escape(edmType));
if (edmType is StructuralType)
{
return typeName;
}
if (edmType is SimpleType)
{
var clrType = UnderlyingClrType(edmType);
if (!IsEnumType(edmType))
{
typeName = _code.Escape(clrType);
}
typeName = FixNamespaces(typeName);
return clrType.IsValueType && isNullable == true ?
String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) :
typeName;
}
throw new ArgumentException("edmType");
}
public Type UnderlyingClrType(EdmType edmType)
{
ArgumentNotNull(edmType, "edmType");
var primitiveType = edmType as PrimitiveType;
if (primitiveType != null)
{
return primitiveType.ClrEquivalentType;
}
if (IsEnumType(edmType))
{
return GetEnumUnderlyingType(edmType).ClrEquivalentType;
}
return typeof(object);
}
public object GetEnumMemberValue(MetadataItem enumMember)
{
ArgumentNotNull(enumMember, "enumMember");
var valueProperty = enumMember.GetType().GetProperty("Value");
return valueProperty == null ? null : valueProperty.GetValue(enumMember, null);
}
public string GetEnumMemberName(MetadataItem enumMember)
{
ArgumentNotNull(enumMember, "enumMember");
var nameProperty = enumMember.GetType().GetProperty("Name");
return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null);
}
public System.Collections.IEnumerable GetEnumMembers(EdmType enumType)
{
ArgumentNotNull(enumType, "enumType");
var membersProperty = enumType.GetType().GetProperty("Members");
return membersProperty != null
? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null)
: Enumerable.Empty<MetadataItem>();
}
public bool EnumIsFlags(EdmType enumType)
{
ArgumentNotNull(enumType, "enumType");
var isFlagsProperty = enumType.GetType().GetProperty("IsFlags");
return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null);
}
public bool IsEnumType(GlobalItem edmType)
{
ArgumentNotNull(edmType, "edmType");
return edmType.GetType().Name == "EnumType";
}
public PrimitiveType GetEnumUnderlyingType(EdmType enumType)
{
ArgumentNotNull(enumType, "enumType");
return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null);
}
public string CreateLiteral(object value)
{
if (value == null || value.GetType() != typeof(TimeSpan))
{
return _code.CreateLiteral(value);
}
return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks);
}
public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
{
ArgumentNotNull(types, "types");
ArgumentNotNull(sourceFile, "sourceFile");
var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
if (types.Any(item => !hash.Add(item)))
{
_errors.Add(
new CompilerError(sourceFile, -1, -1, "6023",
String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict"))));
return false;
}
return true;
}
public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection)
{
return GetItemsToGenerate<SimpleType>(itemCollection)
.Where(e => IsEnumType(e));
}
public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType
{
return itemCollection
.OfType<T>()
.Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName))
.OrderBy(i => i.Name);
}
public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection)
{
return itemCollection
.Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i))
.Select(g => GetGlobalItemName(g));
}
public string GetGlobalItemName(GlobalItem item)
{
if (item is EdmType)
{
return ((EdmType)item).Name;
}
else
{
return ((EntityContainer)item).Name;
}
}
public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetComplexProperties(EntityType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
}
public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
}
public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type)
{
return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
}
public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type)
{
return type.NavigationProperties.Where(np => np.DeclaringType == type);
}
public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type)
{
return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
}
public FunctionParameter GetReturnParameter(EdmFunction edmFunction)
{
ArgumentNotNull(edmFunction, "edmFunction");
var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters");
return returnParamsProperty == null
? edmFunction.ReturnParameter
: ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault();
}
public bool IsComposable(EdmFunction edmFunction)
{
ArgumentNotNull(edmFunction, "edmFunction");
var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute");
return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null);
}
public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction)
{
return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
}
public TypeUsage GetReturnType(EdmFunction edmFunction)
{
var returnParam = GetReturnParameter(edmFunction);
return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage);
}
public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption)
{
var returnType = GetReturnType(edmFunction);
return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
}
}
public static void ArgumentNotNull<T>(T arg, string name) where T : class
{
if (arg == null)
{
throw new ArgumentNullException(name);
}
}
#>

View File

@@ -1,81 +1,89 @@
using FeedCenter.Properties;
using System.Windows.Forms;
using H.NotifyIcon;
using System.Windows.Controls;
namespace FeedCenter
namespace FeedCenter;
internal static class NotificationIcon
{
internal static class NotificationIcon
private static MainWindow _mainWindow;
private static TaskbarIcon _notificationIcon;
private static MenuItem _lockMenuItem;
public static void Initialize(MainWindow mainWindow)
{
private static MainWindow _mainForm;
private static NotifyIcon _notificationIcon;
// Store the main window
_mainWindow = mainWindow;
public static void Initialize(MainWindow mainForm)
// Create the notification icon
_notificationIcon = new TaskbarIcon { Icon = Resources.Application };
_notificationIcon.TrayMouseDoubleClick += HandleNotificationIconDoubleClick;
// Setup the menu
var contextMenu = new ContextMenu();
contextMenu.Opened += HandleContextMenuOpened;
_lockMenuItem = new MenuItem()
{
// Store the main window
_mainForm = mainForm;
Header = Resources.NotificationIconContextMenuLocked,
IsChecked = Settings.Default.WindowLocked
};
_lockMenuItem.Click += HandleLockWindowClicked;
contextMenu.Items.Add(_lockMenuItem);
// Create the notification icon
_notificationIcon = new NotifyIcon { Icon = Resources.Application };
_notificationIcon.DoubleClick += HandleNotificationIconDoubleClick;
contextMenu.Items.Add(new Separator());
// Setup the menu
var contextMenuStrip = new ContextMenuStrip();
var toolStripMenuItem = new ToolStripMenuItem(Resources.NotificationIconContextMenuLocked, null, HandleLockWindowClicked)
{
Checked = Settings.Default.WindowLocked
};
contextMenuStrip.Items.Add(toolStripMenuItem);
contextMenuStrip.Items.Add(new ToolStripSeparator());
contextMenuStrip.Items.Add(Resources.NotificationIconContextMenuExit, null, HandleContextMenuExitClick);
// Set the menu into the icon
_notificationIcon.ContextMenuStrip = contextMenuStrip;
// Show the icon
_notificationIcon.Visible = true;
}
private static void HandleNotificationIconDoubleClick(object sender, System.EventArgs e)
var menuItem = new MenuItem()
{
// Bring the main form to the front
_mainForm.Activate();
}
Header = Resources.NotificationIconContextMenuExit
};
menuItem.Click += HandleContextMenuExitClick;
contextMenu.Items.Add(menuItem);
private static void HandleContextMenuExitClick(object sender, System.EventArgs e)
{
// Close the main form
_mainForm.Close();
}
// Set the menu into the icon
_notificationIcon.ContextMenu = contextMenu;
private static void HandleLockWindowClicked(object sender, System.EventArgs e)
{
// Toggle the lock setting
Settings.Default.WindowLocked = !Settings.Default.WindowLocked;
// Reset the menu choice
((ToolStripMenuItem) sender).Checked = Settings.Default.WindowLocked;
}
public static void Dispose()
{
// Get rid of the icon
_notificationIcon.Visible = false;
_notificationIcon.Dispose();
_notificationIcon = null;
_mainForm = null;
}
public static void ShowBalloonTip(string text, ToolTipIcon icon)
{
ShowBalloonTip(text, icon, Settings.Default.BalloonTipTimeout);
}
private static void ShowBalloonTip(string text, ToolTipIcon icon, int timeout)
{
_notificationIcon.ShowBalloonTip(timeout, Resources.ApplicationDisplayName, text, icon);
}
// Show the icon
_notificationIcon.ForceCreate(false);
}
}
private static void HandleContextMenuOpened(object sender, System.Windows.RoutedEventArgs e)
{
_lockMenuItem.IsChecked = Settings.Default.WindowLocked;
}
private static void HandleNotificationIconDoubleClick(object sender, System.EventArgs e)
{
// Bring the main form to the front
_mainWindow.Activate();
}
private static void HandleContextMenuExitClick(object sender, System.EventArgs e)
{
// Close the main form
_mainWindow.Close();
}
private static void HandleLockWindowClicked(object sender, System.EventArgs e)
{
// Toggle the lock setting
Settings.Default.WindowLocked = !Settings.Default.WindowLocked;
// Refresh the menu choice
_lockMenuItem.IsChecked = Settings.Default.WindowLocked;
}
public static void Dispose()
{
// Get rid of the icon
_notificationIcon.Dispose();
_notificationIcon = null;
_mainWindow = null;
}
public static void ShowBalloonTip(string text, H.NotifyIcon.Core.NotificationIcon icon)
{
_notificationIcon.ShowNotification(Resources.ApplicationDisplayName, text, icon);
}
}

View File

@@ -4,21 +4,18 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:options="clr-namespace:FeedCenter.Options"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:applicationUpdate="clr-namespace:ChrisKaczor.ApplicationUpdate;assembly=ChrisKaczor.ApplicationUpdate"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignHeight="150"
d:DesignWidth="300">
<Grid>
<TextBlock Text="Label"
Name="ApplicationNameLabel"
VerticalAlignment="Top"
FontWeight="Bold" />
<TextBlock Text="Label"
Margin="0,22,0,0"
Name="VersionLabel"
VerticalAlignment="Top" />
<TextBlock Text="Label"
Margin="0,44,0,0"
Name="CompanyLabel"
VerticalAlignment="Top" />
<StackPanel options:Spacing.Vertical="10">
<TextBlock Text="{x:Static properties:Resources.ApplicationDisplayName}"
FontWeight="Bold" />
<TextBlock Text="{Binding Source={x:Static applicationUpdate:UpdateCheck.LocalVersion}, StringFormat={x:Static properties:Resources.Version}}"
Name="VersionLabel" />
<TextBlock Text="Chris Kaczor" />
</StackPanel>
</Grid>
</options:OptionsPanelBase>
</options:OptionsPanelBase>

View File

@@ -1,36 +1,13 @@
using Common.Update;
using System.Reflection;
using System.Windows;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class AboutOptionsPanel
{
public partial class AboutOptionsPanel
public AboutOptionsPanel(Window parentWindow) : base(parentWindow)
{
public AboutOptionsPanel()
{
InitializeComponent();
}
public override void LoadPanel(FeedCenterEntities database)
{
base.LoadPanel(database);
ApplicationNameLabel.Text = Properties.Resources.ApplicationDisplayName;
string version = UpdateCheck.LocalVersion.ToString();
VersionLabel.Text = string.Format(Properties.Resources.Version, version);
CompanyLabel.Text = ((AssemblyCompanyAttribute) Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false)[0]).Company;
}
public override bool ValidatePanel()
{
return true;
}
public override void SavePanel()
{
}
public override string CategoryName => Properties.Resources.optionCategoryAbout;
InitializeComponent();
}
}
public override string CategoryName => Properties.Resources.optionCategoryAbout;
}

View File

@@ -5,36 +5,43 @@
Height="300"
Width="500"
xmlns:my="clr-namespace:FeedCenter.Properties"
xmlns:linkControl="clr-namespace:Common.Wpf.LinkControl;assembly=Common.Wpf"
xmlns:feedCenter="clr-namespace:FeedCenter"
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"
WindowStartupLocation="CenterOwner"
Icon="/FeedCenter;component/Resources/Application.ico"
FocusManager.FocusedElement="{Binding ElementName=FeedLinkFilterText}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="{x:Static my:Resources.FeedLinkFilterLabel}"
Margin="6"
Padding="0"
VerticalContentAlignment="Center"
Target="{Binding ElementName=FeedLinkFilterText}" />
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0"
Grid.Column="1"
Grid.Column="0"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static my:Resources.FeedLinkFilterLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Name="FeedLinkFilterText"
Margin="6"
TextChanged="HandleFilterTextChanged" />
<Border Grid.Row="1"
Grid.ColumnSpan="2"
Grid.Column="0"
Margin="6"
Margin="0,6"
BorderThickness="1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<Grid>
@@ -45,9 +52,9 @@
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="FilteredFeedsList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2"
Content="{Binding Item.Name}"
<DataTemplate DataType="options:CheckedFeedListItem">
<CheckBox Content="{Binding Item.Name}"
Margin="4"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
@@ -58,70 +65,62 @@
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<StackPanel Orientation="Horizontal"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<TextBlock Margin="2"
Text="{x:Static my:Resources.SelectLabel}">
</TextBlock>
<linkControl:LinkControl Margin="2"
Click="HandleSelectAll"
Text="{x:Static my:Resources.SelectAllLabel}">
</linkControl:LinkControl>
<linkControl:LinkControl Margin="2"
Click="HandleSelectNone"
Text="{x:Static my:Resources.SelectNoneLabel}">
</linkControl:LinkControl>
<linkControl:LinkControl Margin="2"
Click="HandleSelectInvert"
Text="{x:Static my:Resources.SelectInvertLabel}">
</linkControl:LinkControl>
<TextBlock Margin="4,2,2,2"
Text="{x:Static my:Resources.SelectLabel}" />
<controls:Link Margin="2,2,4,2"
Click="HandleSelectAll"
Text="{x:Static my:Resources.SelectAllLabel}">
</controls:Link>
<controls:Link Margin="2,2,4,2"
Click="HandleSelectNone"
Text="{x:Static my:Resources.SelectNoneLabel}">
</controls:Link>
<controls:Link Margin="2,2,4,2"
Click="HandleSelectInvert"
Text="{x:Static my:Resources.SelectInvertLabel}">
</controls:Link>
</StackPanel>
</Border>
</Grid>
</Border>
<Grid Grid.Row="2"
Grid.Column="0"
MouseRightButtonUp="HandleGridMouseRightButtonUp"
ToolTip="{x:Static my:Resources.EnableHint}">
<Label Content="{x:Static my:Resources.openLabel}"
Name="OpenLabel"
Padding="4,0,0,0"
Margin="6,8,6,6"
ToolTip="{x:Static my:Resources.DisableHint}"
IsEnabled="False" />
</Grid>
<Grid Grid.Column="1"
Grid.Row="2"
MouseRightButtonUp="HandleGridMouseRightButtonUp"
ToolTip="{x:Static my:Resources.EnableHint}">
<Grid Grid.Column="0"
Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox Name="OpenCheckBox"
Grid.Column="0" />
<ComboBox Name="OpenComboBox"
VerticalContentAlignment="Center"
Grid.Column="1"
SelectedIndex="0"
Margin="6"
ToolTip="{x:Static my:Resources.DisableHint}"
IsEnabled="False">
HorizontalContentAlignment="Stretch"
mah:TextBoxHelper.UseFloatingWatermark="True"
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}" />
<ComboBoxItem Content="{x:Static my:Resources.openAllSingleToolbarButton}"
Tag="{x:Static feedCenter:MultipleOpenAction.SinglePage}" />
</ComboBox>
</Grid>
<Button Content="{x:Static my:Resources.OkayButton}"
Height="23"
HorizontalAlignment="Right"
IsDefault="True"
Margin="0,6,87,6"
VerticalAlignment="Bottom"
Width="75"
Grid.Column="1"
Grid.Row="3"
Click="HandleOkButtonClick" />
<Button Content="{x:Static my:Resources.CancelButton}"
Grid.Column="1"
Height="23"
HorizontalAlignment="Right"
IsCancel="True"
Margin="0,6,6,6"
VerticalAlignment="Bottom"
Width="75"
Grid.Row="3" />
<StackPanel Grid.Column="0"
Grid.Row="3"
Orientation="Horizontal"
Margin="0,5,0,0"
HorizontalAlignment="Right">
<Button Content="{x:Static my:Resources.OkayButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
Margin="0,0,5,0"
IsDefault="True"
Click="HandleOkButtonClick" />
<Button Content="{x:Static my:Resources.CancelButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsCancel="True" />
</StackPanel>
</Grid>
</Window>
</Window>

View File

@@ -1,4 +1,4 @@
using Common.Wpf;
using FeedCenter.Data;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -6,96 +6,92 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class BulkFeedWindow
{
public partial class BulkFeedWindow
private List<CheckedListItem<Feed>> _checkedListBoxItems;
private CollectionViewSource _collectionViewSource;
public BulkFeedWindow()
{
private List<CheckedListItem<Feed>> _checkedListBoxItems;
private CollectionViewSource _collectionViewSource;
InitializeComponent();
}
public BulkFeedWindow()
{
InitializeComponent();
}
public void Display(Window window)
{
_checkedListBoxItems = new List<CheckedListItem<Feed>>();
public void Display(Window window, FeedCenterEntities database)
{
_checkedListBoxItems = new List<CheckedListItem<Feed>>();
foreach (var feed in Database.Entities.Feeds)
_checkedListBoxItems.Add(new CheckedListItem<Feed> { Item = feed });
foreach (var feed in database.AllFeeds)
_checkedListBoxItems.Add(new CheckedListItem<Feed> { Item = feed });
_collectionViewSource = new CollectionViewSource { Source = _checkedListBoxItems };
_collectionViewSource.SortDescriptions.Add(new SortDescription("Item.Name", ListSortDirection.Ascending));
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
_collectionViewSource = new CollectionViewSource { Source = _checkedListBoxItems };
_collectionViewSource.SortDescriptions.Add(new SortDescription("Item.Name", ListSortDirection.Ascending));
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
FilteredFeedsList.ItemsSource = _collectionViewSource.View;
FilteredFeedsList.ItemsSource = _collectionViewSource.View;
Owner = window;
Owner = window;
ShowDialog();
}
ShowDialog();
}
private void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{
var checkedListBoxItem = (CheckedListItem<Feed>) e.Item;
void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{
var checkedListBoxItem = (CheckedListItem<Feed>) e.Item;
var feed = checkedListBoxItem.Item;
var feed = checkedListBoxItem.Item;
e.Accepted = feed.Link.Contains(FeedLinkFilterText.Text);
}
e.Accepted = feed.Link.Contains(FeedLinkFilterText.Text);
}
private void HandleFilterTextChanged(object sender, TextChangedEventArgs e)
{
_collectionViewSource.View.Refresh();
}
private void HandleFilterTextChanged(object sender, TextChangedEventArgs e)
{
_collectionViewSource.View.Refresh();
}
private void HandleOkButtonClick(object sender, RoutedEventArgs e)
private void HandleOkButtonClick(object sender, RoutedEventArgs e)
{
Database.Entities.SaveChanges(() =>
{
foreach (var item in _checkedListBoxItems.Where(i => i.IsChecked))
{
if (OpenComboBox.IsEnabled)
item.Item.MultipleOpenAction = (MultipleOpenAction) ((ComboBoxItem) OpenComboBox.SelectedItem).Tag;
}
});
DialogResult = true;
Close();
}
DialogResult = true;
Close();
}
private void HandleSelectAll(object sender, RoutedEventArgs e)
private void HandleSelectAll(object sender, RoutedEventArgs e)
{
foreach (var viewItem in _collectionViewSource.View)
{
foreach (var viewItem in _collectionViewSource.View)
{
var checkedListItem = (CheckedListItem<Feed>) viewItem;
var checkedListItem = (CheckedListItem<Feed>) viewItem;
checkedListItem.IsChecked = true;
}
}
private void HandleSelectNone(object sender, RoutedEventArgs e)
{
foreach (var viewItem in _collectionViewSource.View)
{
var checkedListItem = (CheckedListItem<Feed>) viewItem;
checkedListItem.IsChecked = false;
}
}
private void HandleSelectInvert(object sender, RoutedEventArgs e)
{
foreach (var viewItem in _collectionViewSource.View)
{
var checkedListItem = (CheckedListItem<Feed>) viewItem;
checkedListItem.IsChecked = !checkedListItem.IsChecked;
}
}
private void HandleGridMouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
OpenLabel.IsEnabled = !OpenLabel.IsEnabled;
OpenComboBox.IsEnabled = !OpenComboBox.IsEnabled;
checkedListItem.IsChecked = true;
}
}
}
private void HandleSelectNone(object sender, RoutedEventArgs e)
{
foreach (var viewItem in _collectionViewSource.View)
{
var checkedListItem = (CheckedListItem<Feed>) viewItem;
checkedListItem.IsChecked = false;
}
}
private void HandleSelectInvert(object sender, RoutedEventArgs e)
{
foreach (var viewItem in _collectionViewSource.View)
{
var checkedListItem = (CheckedListItem<Feed>) viewItem;
checkedListItem.IsChecked = !checkedListItem.IsChecked;
}
}
}

View File

@@ -2,44 +2,50 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:feedCenter="clr-namespace:FeedCenter"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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}"
Title="CategoryWindow"
Height="119"
Width="339"
Width="300"
ResizeMode="NoResize"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
Icon="/FeedCenter;component/Resources/Application.ico"
FocusManager.FocusedElement="{Binding ElementName=NameTextBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="367*" />
</Grid.ColumnDefinitions>
<Label Content="{x:Static properties:Resources.feedCategoryLabel}"
HorizontalAlignment="Left"
Target="{Binding ElementName=NameTextBox}"
VerticalAlignment="Top"
VerticalContentAlignment="Center"
Margin="12,12,0,0" />
<TextBox Margin="7,14,12,0"
Name="NameTextBox"
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}"
VerticalAlignment="Top"
Grid.Column="1" />
<Button Content="{x:Static properties:Resources.OkayButton}"
Height="23"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsDefault="True"
Margin="0,0,93,12"
Click="HandleOkayButtonClick"
Grid.Column="1" />
<Button Content="{x:Static properties:Resources.CancelButton}"
Height="23"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsCancel="True"
Margin="0,0,12,12"
Grid.Column="1" />
</Grid>
</Window>
FocusManager.FocusedElement="{Binding ElementName=NameTextBox}"
controls:ControlBox.HasMinimizeButton="False"
controls:ControlBox.HasMaximizeButton="False">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel Margin="6"
options:Spacing.Vertical="5">
<TextBox mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.categoryNameLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<StackPanel
options:Spacing.Horizontal="5"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="{x:Static properties:Resources.OkayButton}"
Width="75"
IsDefault="True"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static properties:Resources.CancelButton}"
Width="75"
IsCancel="True" />
</StackPanel>
</StackPanel>
</Window>

View File

@@ -1,64 +1,50 @@
using Common.Wpf.Extensions;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows;
using ChrisKaczor.Wpf.Validation;
using FeedCenter.Data;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class CategoryWindow
{
public partial class CategoryWindow
public CategoryWindow()
{
public CategoryWindow()
{
InitializeComponent();
}
public bool? Display(Category category, Window owner)
{
// Set the data context
DataContext = category;
// Set the title based on the state of the category
Title = string.IsNullOrWhiteSpace(category.Name) ? Properties.Resources.CategoryWindowAdd : Properties.Resources.CategoryWindowEdit;
// Set the window owner
Owner = owner;
// Show the dialog and result the result
return ShowDialog();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
// Get a list of all explicit binding expressions
var bindingExpressions = this.GetBindingExpressions(new[] { UpdateSourceTrigger.Explicit });
// Loop over each binding expression and clear any existing error
bindingExpressions.ForEach(b => Validation.ClearInvalid(b.BindingExpression));
// Force all explicit bindings to update the source
bindingExpressions.ForEach(bindingExpression => bindingExpression.BindingExpression.UpdateSource());
// See if there are any errors
var hasError = bindingExpressions.Exists(bindingExpression => bindingExpression.BindingExpression.HasError);
// If there was an error then set focus to the bad controls
if (hasError)
{
// Get the first framework element with an error
var firstErrorElement = bindingExpressions.First(b => b.BindingExpression.HasError).FrameworkElement;
// Set focus
firstErrorElement.Focus();
return;
}
// Dialog is good
DialogResult = true;
// Close the dialog
Close();
}
InitializeComponent();
}
}
public bool? Display(Category category, Window owner)
{
// Set the data context
DataContext = category;
// Set the title based on the state of the category
Title = string.IsNullOrWhiteSpace(category.Name)
? Properties.Resources.CategoryWindowAdd
: Properties.Resources.CategoryWindowEdit;
// Set the window owner
Owner = owner;
// Show the dialog and result the result
return ShowDialog();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
var transaction = Database.Entities.BeginTransaction();
if (!this.IsValid())
{
transaction.Rollback();
return;
}
transaction.Commit();
Database.Entities.Refresh();
// Dialog is good
DialogResult = true;
// Close the dialog
Close();
}
}

View File

@@ -0,0 +1,6 @@
namespace FeedCenter.Options
{
public class CheckedFeedListItem : CheckedListItem<Feed>
{
}
}

View File

@@ -0,0 +1,41 @@
using System.ComponentModel;
namespace FeedCenter.Options;
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isChecked;
private readonly T _item;
public CheckedListItem() { }
public CheckedListItem(T item, bool isChecked = false)
{
_item = item;
_isChecked = isChecked;
}
public T Item
{
get => _item;
init
{
_item = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Item)));
}
}
public bool IsChecked
{
get => _isChecked;
set
{
_isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
}
}
}

View File

@@ -5,57 +5,38 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:options="clr-namespace:FeedCenter.Options"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
d:DesignHeight="150"
d:DesignWidth="400">
<StackPanel options:Spacing.Vertical="8">
<CheckBox Content="{x:Static properties:Resources.lockWindowCheckBox}"
Height="16"
HorizontalAlignment="Left"
Name="LockWindowCheckBox"
VerticalAlignment="Top"
Width="300"
Grid.ColumnSpan="2" />
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=WindowLocked}"
Click="OnSaveSettings" />
<CheckBox Content="{x:Static properties:Resources.displayEmptyFeedsCheckBox}"
Height="16"
HorizontalAlignment="Left"
Margin="0,22,0,0"
Name="DisplayEmptyFeedsCheckBox"
VerticalAlignment="Top"
Width="300"
Grid.ColumnSpan="2"
Grid.Column="0" />
<Label Grid.Column="0"
Content="{x:Static properties:Resources.toolbarLocationLabel}"
VerticalAlignment="Top"
Margin="0,50,0,0"
Padding="0,5,5,5"
Target="{Binding ElementName=ToolbarLocationComboBox}"
Width="97" />
<ComboBox Margin="8,53,0,0"
Name="ToolbarLocationComboBox"
VerticalAlignment="Top"
Grid.Column="1">
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=DisplayEmptyFeeds}"
Click="OnSaveSettings" />
<ComboBox
Name="ToolbarLocationComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.toolbarLocationLabel}"
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=ToolbarLocation}"
SelectedValuePath="Tag"
SelectionChanged="OnSaveSettings">
<ComboBoxItem Content="{x:Static properties:Resources.Top}"
Tag="{x:Static Dock.Top}" />
<ComboBoxItem Content="{x:Static properties:Resources.Bottom}"
Tag="{x:Static Dock.Bottom}" />
</ComboBox>
<Label Grid.Column="0"
Content="{x:Static properties:Resources.multipleLineDisplayLabel}"
VerticalAlignment="Top"
Margin="0,82,0,0"
Padding="0,5,5,5"
Target="{Binding ElementName=MultipleLineDisplayComboBox}"
Width="97" />
<ComboBox Margin="8,86,0,0"
Name="MultipleLineDisplayComboBox"
VerticalAlignment="Top"
Grid.Column="1">
<ComboBox
Name="MultipleLineDisplayComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.multipleLineDisplayLabel}"
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=MultipleLineDisplay}"
SelectedValuePath="Tag"
SelectionChanged="OnSaveSettings">
<ComboBoxItem Content="{x:Static properties:Resources.multipleLineDisplayNormal}"
Tag="{x:Static options:MultipleLineDisplay.Normal}" />
<ComboBoxItem Content="{x:Static properties:Resources.multipleLineDisplaySingleLine}"
@@ -63,5 +44,5 @@
<ComboBoxItem Content="{x:Static properties:Resources.multipleLineDisplayFirstLine}"
Tag="{x:Static options:MultipleLineDisplay.FirstLine}" />
</ComboBox>
</Grid>
</options:OptionsPanelBase>
</StackPanel>
</options:OptionsPanelBase>

View File

@@ -1,46 +1,33 @@
using FeedCenter.Properties;
using System.Linq;
using System.Windows.Controls;
using System.Windows;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class DisplayOptionsPanel
{
public partial class DisplayOptionsPanel
public DisplayOptionsPanel(Window parentWindow) : base(parentWindow)
{
public DisplayOptionsPanel()
{
InitializeComponent();
}
public override void LoadPanel(FeedCenterEntities database)
{
base.LoadPanel(database);
LockWindowCheckBox.IsChecked = Settings.Default.WindowLocked;
DisplayEmptyFeedsCheckBox.IsChecked = Settings.Default.DisplayEmptyFeeds;
ToolbarLocationComboBox.SelectedItem = ToolbarLocationComboBox.Items.Cast<ComboBoxItem>().First(comboBoxItem => (Dock) comboBoxItem.Tag == Settings.Default.ToolbarLocation);
MultipleLineDisplayComboBox.SelectedItem = MultipleLineDisplayComboBox.Items.Cast<ComboBoxItem>().First(comboBoxItem => (MultipleLineDisplay) comboBoxItem.Tag == Settings.Default.MultipleLineDisplay);
}
public override bool ValidatePanel()
{
return true;
}
public override void SavePanel()
{
if (LockWindowCheckBox.IsChecked.HasValue && Settings.Default.WindowLocked != LockWindowCheckBox.IsChecked.Value)
Settings.Default.WindowLocked = LockWindowCheckBox.IsChecked.Value;
if (DisplayEmptyFeedsCheckBox.IsChecked.HasValue && Settings.Default.DisplayEmptyFeeds != DisplayEmptyFeedsCheckBox.IsChecked.Value)
Settings.Default.DisplayEmptyFeeds = DisplayEmptyFeedsCheckBox.IsChecked.Value;
var dock = (Dock) ((ComboBoxItem) ToolbarLocationComboBox.SelectedItem).Tag;
Settings.Default.ToolbarLocation = dock;
var multipleLineDisplay = (MultipleLineDisplay) ((ComboBoxItem) MultipleLineDisplayComboBox.SelectedItem).Tag;
Settings.Default.MultipleLineDisplay = multipleLineDisplay;
}
public override string CategoryName => Properties.Resources.optionCategoryDisplay;
InitializeComponent();
}
}
public override string CategoryName => Properties.Resources.optionCategoryDisplay;
public override void LoadPanel()
{
base.LoadPanel();
MarkLoaded();
}
private void OnSaveSettings(object sender, RoutedEventArgs e)
{
SaveSettings();
}
private void SaveSettings()
{
if (!HasLoaded) return;
Settings.Default.Save();
}
}

View File

@@ -3,183 +3,139 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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}"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:options="clr-namespace:FeedCenter.Options"
mc:Ignorable="d"
Title="FeedWindow"
Height="300"
Height="350"
Width="450"
WindowStartupLocation="CenterOwner"
Icon="/FeedCenter;component/Resources/Application.ico"
FocusManager.FocusedElement="{Binding ElementName=UrlTextBox}">
<Grid>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TabControl Name="OptionsTabControl"
Margin="12,12,12,41">
Grid.Row="0"
Grid.Column="0"
mah:HeaderedControlHelper.HeaderFontSize="16"
mah:TabControlHelper.Underlined="SelectedTabItem">
<TabItem Header="{x:Static properties:Resources.generalTab}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Content="{x:Static properties:Resources.feedUrlLabel}"
VerticalContentAlignment="Center"
Target="{Binding ElementName=UrlTextBox}"
Margin="6"
Padding="0" />
<StackPanel Margin="0,4"
options:Spacing.Vertical="8">
<TextBox Name="UrlTextBox"
Grid.Row="0"
Grid.Column="1"
Text="{Binding Path=Source, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}"
Margin="6" />
<Label Content="{x:Static properties:Resources.feedNameLabel}"
VerticalContentAlignment="Center"
Target="{Binding ElementName=NameTextBox}"
Grid.Row="1"
Grid.Column="0"
Margin="6"
Padding="0" />
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.feedUrlLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Text="{Binding Path=Source, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<TextBox Name="NameTextBox"
Grid.Column="1"
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=true}"
Grid.Row="1"
Margin="6" />
<Label Content="{x:Static properties:Resources.feedCategoryLabel}"
Target="{Binding ElementName=CategoryComboBox}"
VerticalContentAlignment="Center"
Grid.Row="2"
Grid.Column="0"
Margin="6"
Padding="0" />
<ComboBox Grid.Column="1"
Name="CategoryComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.feedNameLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<ComboBox Name="CategoryComboBox"
DisplayMemberPath="Name"
SelectedValuePath="ID"
SelectedValue="{Binding Path=Category.ID}"
Grid.Row="2"
Margin="6" />
SelectedValuePath="Id"
SelectedValue="{Binding Path=CategoryId}"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.feedCategoryLabel}" />
<CheckBox Grid.ColumnSpan="2"
Grid.Column="0"
Name="ReadIntervalCheckBox"
<CheckBox Name="ReadIntervalCheckBox"
VerticalContentAlignment="Center"
IsChecked="{Binding Path=Enabled, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}"
Grid.Row="3"
Margin="6">
<DockPanel>
IsChecked="{Binding Path=Enabled, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}">
<StackPanel Orientation="Horizontal">
<Label Content="{x:Static properties:Resources.feedReadIntervalPrefix}"
HorizontalAlignment="Left"
Margin="0,0,5,0"
VerticalAlignment="Center"
Padding="0" />
<TextBox Width="50"
Text="{Binding Path=CheckInterval, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}"
IsEnabled="{Binding ElementName=ReadIntervalCheckBox, Path=IsChecked}" />
<mah:NumericUpDown Width="100"
Maximum="10080"
Minimum="1"
IsEnabled="{Binding ElementName=ReadIntervalCheckBox, Path=IsChecked}"
Value="{Binding CheckInterval, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<Label Content="{x:Static properties:Resources.feedReadIntervalSuffix}"
HorizontalAlignment="Left"
Margin="5,0,0,0"
VerticalAlignment="Center"
Padding="0" />
</DockPanel>
</StackPanel>
</CheckBox>
</Grid>
</StackPanel>
</TabItem>
<TabItem Header="{x:Static properties:Resources.readingTab}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Content="{x:Static properties:Resources.openLabel}"
Target="{Binding ElementName=OpenComboBox}"
Padding="0"
VerticalContentAlignment="Center"
Margin="6" />
<StackPanel Margin="0,4"
options:Spacing.Vertical="8">
<ComboBox Name="OpenComboBox"
VerticalContentAlignment="Center"
SelectedValue="{Binding Path=MultipleOpenAction, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=true}"
SelectedValuePath="Tag"
Grid.Row="0"
Grid.Column="1"
Margin="6">
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}" />
<ComboBoxItem Content="{x:Static properties:Resources.openAllMultipleToolbarButton}"
Tag="{x:Static feedCenter:MultipleOpenAction.IndividualPages}" />
</ComboBox>
</Grid>
</StackPanel>
</TabItem>
<TabItem Header="{x:Static properties:Resources.authenticationTab}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,4">
<CheckBox Content="{x:Static properties:Resources.requiresAuthenticationCheckBox}"
Margin="0,0,0,4"
Name="RequiresAuthenticationCheckBox"
Grid.ColumnSpan="2"
Grid.Row="0"
Grid.Column="0"
IsChecked="{Binding Path=Authenticate, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}"
Margin="6" />
<Label Content="{x:Static properties:Resources.authenticationUserNameLabel}"
Target="{Binding ElementName=AuthenticationUserNameTextBox}"
VerticalContentAlignment="Center"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
Grid.Row="1"
Grid.Column="0"
Margin="6"
Padding="20,0,0,0" />
IsChecked="{Binding Path=Authenticate, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<TextBox Name="AuthenticationUserNameTextBox"
Text="{Binding Path=Username, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}"
Grid.Column="1"
Margin="25,0,0,4"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
Grid.Row="1"
Margin="6" />
<Label Content="{x:Static properties:Resources.authenticationPasswordLabel}"
Target="{Binding ElementName=AuthenticationPasswordTextBox}"
VerticalContentAlignment="Center"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
Grid.Row="2"
Grid.Column="0"
Margin="6"
Padding="20,0,0,0" />
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.authenticationUserNameLabel}"
Text="{Binding Path=Username, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<PasswordBox Name="AuthenticationPasswordTextBox"
Grid.Column="1"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
Grid.Row="2"
Margin="6" />
</Grid>
Margin="25,0,0,8"
Style="{StaticResource MahApps.Styles.PasswordBox.Button.Revealed}"
mah:PasswordBoxBindingBehavior.Password="{Binding Password, UpdateSourceTrigger=Explicit, ValidatesOnDataErrors=True}"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.authenticationPasswordLabel}"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}" />
</StackPanel>
</TabItem>
</TabControl>
<Button Content="{x:Static properties:Resources.OkayButton}"
Height="23"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsDefault="True"
Margin="0,0,93,12"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static properties:Resources.CancelButton}"
Height="23"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsCancel="True"
Margin="0,0,12,12" />
<StackPanel
Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal"
Margin="0,5,0,0"
HorizontalAlignment="Right">
<Button Content="{x:Static properties:Resources.OkayButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
Margin="0,0,5,0"
IsDefault="True"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static properties:Resources.CancelButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsCancel="True" />
</StackPanel>
</Grid>
</Window>
</Window>

View File

@@ -1,91 +1,44 @@
using System.Data.Entity;
using Common.Wpf.Extensions;
using System.Linq;
using ChrisKaczor.Wpf.Validation;
using FeedCenter.Data;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class FeedWindow
{
public partial class FeedWindow
public FeedWindow()
{
public FeedWindow()
{
InitializeComponent();
}
public bool? Display(FeedCenterEntities database, Feed feed, Window owner)
{
database.Categories.Load();
// Bind the category combo box
CategoryComboBox.ItemsSource = database.Categories.Local;
// Set the data context
DataContext = feed;
// Set the title based on the state of the feed
Title = string.IsNullOrWhiteSpace(feed.Link) ? Properties.Resources.FeedWindowAdd : Properties.Resources.FeedWindowEdit;
// Set the window owner
Owner = owner;
// Show the dialog and result the result
return ShowDialog();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
var feed = (Feed) DataContext;
// Get a list of all framework elements and explicit binding expressions
var bindingExpressions = this.GetBindingExpressions(new[] { UpdateSourceTrigger.Explicit });
// Loop over each binding expression and clear any existing error
this.ClearAllValidationErrors(bindingExpressions);
// Force all explicit bindings to update the source
this.UpdateAllSources(bindingExpressions);
// See if there are any errors
var hasError = bindingExpressions.Any(b => b.BindingExpression.HasError);
// If there was an error then set focus to the bad controls
if (hasError)
{
// Get the first framework element with an error
var firstErrorElement = bindingExpressions.First(b => b.BindingExpression.HasError).FrameworkElement;
// Loop over each tab item
foreach (TabItem tabItem in OptionsTabControl.Items)
{
// Cast the content as visual
var content = (Visual) tabItem.Content;
// See if the control with the error is a descendant
if (firstErrorElement.IsDescendantOf(content))
{
// Select the tab
tabItem.IsSelected = true;
break;
}
}
// Set focus
firstErrorElement.Focus();
return;
}
if (RequiresAuthenticationCheckBox.IsChecked.GetValueOrDefault(false))
feed.Password = AuthenticationPasswordTextBox.Password;
// Dialog is good
DialogResult = true;
// Close the dialog
Close();
}
InitializeComponent();
}
}
public bool? Display(Feed feed, Window owner)
{
CategoryComboBox.ItemsSource = Database.Entities.Categories;
DataContext = feed;
Title = string.IsNullOrWhiteSpace(feed.Link) ? Properties.Resources.FeedWindowAdd : Properties.Resources.FeedWindowEdit;
Owner = owner;
return ShowDialog();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
var transaction = Database.Entities.BeginTransaction();
if (!this.IsValid(OptionsTabControl))
{
transaction.Rollback();
return;
}
transaction.Commit();
Database.Entities.Refresh();
DialogResult = true;
Close();
}
}

View File

@@ -4,11 +4,22 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:options="clr-namespace:FeedCenter.Options"
xmlns:linkControl="clr-namespace:Common.Wpf.LinkControl;assembly=Common.Wpf"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
xmlns:feedCenter="clr-namespace:FeedCenter"
mc:Ignorable="d"
d:DesignHeight="311"
d:DesignWidth="425">
<options:OptionsPanelBase.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</options:OptionsPanelBase.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
@@ -19,7 +30,43 @@
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DataGrid Name="FeedListBox"
<DataGrid Name="CategoryDataGrid"
SelectionChanged="HandleCategoryDataGridSelectionChanged"
Grid.Row="0"
SelectionMode="Single"
SelectionUnit="FullRow"
Grid.Column="0"
AutoGenerateColumns="False"
GridLinesVisibility="None"
CanUserResizeRows="False"
IsReadOnly="True"
HeadersVisibility="Column"
BorderThickness="1,1,1,1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
AllowDrop="True"
Background="{x:Null}"
d:DataContext="{d:DesignInstance feedCenter:Category }">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"
Header="{x:Static properties:Resources.CategoryNameColumnHeader}"
SortDirection="Ascending"
Width="*" />
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow"
BasedOn="{StaticResource MahApps.Styles.DataGridRow}">
<EventSetter Event="Drop"
Handler="HandleCategoryDataGridRowDrop" />
<EventSetter Event="DragEnter"
Handler="HandleCategoryDataGridRowDragEnter" />
<EventSetter Event="DragLeave"
Handler="HandleCategoryDataGridRowDragLeave" />
<EventSetter Event="MouseDoubleClick"
Handler="HandleCategoryDataGridRowMouseDoubleClick" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
<DataGrid Name="FeedDataGrid"
SelectionMode="Extended"
Grid.Column="2"
Grid.Row="0"
@@ -27,9 +74,13 @@
GridLinesVisibility="None"
CanUserResizeRows="False"
IsReadOnly="True"
SelectionUnit="FullRow"
HeadersVisibility="Column"
BorderThickness="1,1,1,1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
Background="{x:Null}"
PreviewMouseLeftButtonDown="HandleFeedListPreviewMouseLeftButtonDown">
SelectionChanged="HandleFeedDataGridSelectionChanged"
d:DataContext="{d:DesignInstance feedCenter:Feed }">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"
Header="{x:Static properties:Resources.FeedNameColumnHeader}"
@@ -39,55 +90,15 @@
Header="{x:Static properties:Resources.LastUpdatedColumnHeader}"
Width="Auto" />
</DataGrid.Columns>
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow"
BasedOn="{StaticResource MahApps.Styles.DataGridRow}">
<EventSetter Event="MouseDoubleClick"
Handler="HandleListBoxItemMouseDoubleClick" />
<EventSetter Event="PreviewMouseMove"
Handler="HandleListBoxItemPreviewMouseMove" />
Handler="HandleFeedDataGridRowMouseDoubleClick" />
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="HandleFeedDataGridRowPreviewMouseLeftButtonDown" />
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness"
Value="0" />
</Style>
</DataGrid.CellStyle>
</DataGrid>
<DataGrid Name="CategoryListBox"
SelectionChanged="HandleCategoryListBoxSelectionChanged"
Grid.Row="0"
SelectionMode="Extended"
Grid.Column="0"
AutoGenerateColumns="False"
GridLinesVisibility="None"
CanUserResizeRows="False"
IsReadOnly="True"
HeadersVisibility="Column"
AllowDrop="True"
Background="{x:Null}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"
Header="{x:Static properties:Resources.CategoryNameColumnHeader}"
SortDirection="Ascending"
Width="*" />
</DataGrid.Columns>
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="Drop"
Handler="HandleTextBlockDrop" />
<EventSetter Event="DragEnter"
Handler="HandleTextBlockDragEnter" />
<EventSetter Event="DragLeave"
Handler="HandleTextBlockDragLeave" />
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness"
Value="0" />
</Style>
</DataGrid.CellStyle>
</DataGrid.RowStyle>
</DataGrid>
<Border Grid.Column="2"
Grid.Row="1"
@@ -95,39 +106,39 @@
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<StackPanel Orientation="Horizontal"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<linkControl:LinkControl Name="AddFeedButton"
Margin="2"
Click="HandleAddFeedButtonClick"
Text="{x:Static properties:Resources.AddLink}"
ToolTip="{x:Static properties:Resources.AddFeedButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Name="EditFeedButton"
Margin="2"
Click="HandleEditFeedButtonClick"
Text="{x:Static properties:Resources.EditLink}"
ToolTip="{x:Static properties:Resources.EditFeedButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Name="DeleteFeedButton"
Margin="2"
Click="HandleDeleteFeedButtonClick"
Text="{x:Static properties:Resources.DeleteLink}"
ToolTip="{x:Static properties:Resources.DeleteFeedButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Margin="6,2,2,2"
Click="HandleImportButtonClick"
Text="{x:Static properties:Resources.ImportLink}"
ToolTip="{x:Static properties:Resources.ImportFeedsButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Margin="2"
Click="HandleExportButtonClick"
Text="{x:Static properties:Resources.ExportLink}"
ToolTip="{x:Static properties:Resources.ExportFeedsButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Margin="6,2,2,2"
Click="HandleMultipleEditClick"
Text="{x:Static properties:Resources.MultipleEditLink}"
ToolTip="{x:Static properties:Resources.MultipleEditButton}">
</linkControl:LinkControl>
<controls:Link Name="AddFeedButton"
Margin="2"
Click="HandleAddFeedButtonClick"
Text="{x:Static properties:Resources.AddLink}"
ToolTip="{x:Static properties:Resources.AddFeedButton}">
</controls:Link>
<controls:Link Name="EditFeedButton"
Margin="2"
Click="HandleEditFeedButtonClick"
Text="{x:Static properties:Resources.EditLink}"
ToolTip="{x:Static properties:Resources.EditFeedButton}">
</controls:Link>
<controls:Link Name="DeleteFeedButton"
Margin="2"
Click="HandleDeleteFeedButtonClick"
Text="{x:Static properties:Resources.DeleteLink}"
ToolTip="{x:Static properties:Resources.DeleteFeedButton}">
</controls:Link>
<controls:Link Margin="6,2,2,2"
Click="HandleImportButtonClick"
Text="{x:Static properties:Resources.ImportLink}"
ToolTip="{x:Static properties:Resources.ImportFeedsButton}">
</controls:Link>
<controls:Link Margin="2"
Click="HandleExportButtonClick"
Text="{x:Static properties:Resources.ExportLink}"
ToolTip="{x:Static properties:Resources.ExportFeedsButton}">
</controls:Link>
<controls:Link Margin="6,2,2,2"
Click="HandleMultipleEditClick"
Text="{x:Static properties:Resources.MultipleEditLink}"
ToolTip="{x:Static properties:Resources.MultipleEditButton}">
</controls:Link>
</StackPanel>
</Border>
<Border Grid.Row="1"
@@ -136,24 +147,24 @@
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<StackPanel Orientation="Horizontal"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<linkControl:LinkControl Name="AddCategoryButton"
Margin="2"
Click="HandleAddCategoryButtonClick"
Text="{x:Static properties:Resources.AddLink}"
ToolTip="{x:Static properties:Resources.AddCategoryButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Name="EditCategoryButton"
Margin="2"
Click="HandleEditCategoryButtonClick"
Text="{x:Static properties:Resources.EditLink}"
ToolTip="{x:Static properties:Resources.EditCategoryButton}">
</linkControl:LinkControl>
<linkControl:LinkControl Name="DeleteCategoryButton"
Margin="2"
Click="HandleDeleteCategoryButtonClick"
Text="{x:Static properties:Resources.DeleteLink}"
ToolTip="{x:Static properties:Resources.DeleteCategoryButton}">
</linkControl:LinkControl>
<controls:Link Name="AddCategoryButton"
Margin="2"
Click="HandleAddCategoryButtonClick"
Text="{x:Static properties:Resources.AddLink}"
ToolTip="{x:Static properties:Resources.AddCategoryButton}">
</controls:Link>
<controls:Link Name="EditCategoryButton"
Margin="2"
Click="HandleEditCategoryButtonClick"
Text="{x:Static properties:Resources.EditLink}"
ToolTip="{x:Static properties:Resources.EditCategoryButton}">
</controls:Link>
<controls:Link Name="DeleteCategoryButton"
Margin="2"
Click="HandleDeleteCategoryButtonClick"
Text="{x:Static properties:Resources.DeleteLink}"
ToolTip="{x:Static properties:Resources.DeleteCategoryButton}">
</controls:Link>
</StackPanel>
</Border>
</Grid>

View File

@@ -1,4 +1,5 @@
using Microsoft.Win32;
using FeedCenter.Data;
using Microsoft.Win32;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -6,458 +7,402 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class FeedsOptionsPanel
{
public partial class FeedsOptionsPanel
private CollectionViewSource _collectionViewSource;
public FeedsOptionsPanel(Window parentWindow) : base(parentWindow)
{
#region Constructor
InitializeComponent();
}
public FeedsOptionsPanel()
public override string CategoryName => Properties.Resources.optionCategoryFeeds;
public override void LoadPanel()
{
base.LoadPanel();
var collectionViewSource = new CollectionViewSource { Source = Database.Entities.Categories };
collectionViewSource.SortDescriptions.Add(new SortDescription("SortKey", ListSortDirection.Ascending));
collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
collectionViewSource.IsLiveSortingRequested = true;
CategoryDataGrid.ItemsSource = collectionViewSource.View;
CategoryDataGrid.SelectedIndex = 0;
}
private void SetFeedButtonStates()
{
AddFeedButton.IsEnabled = true;
EditFeedButton.IsEnabled = FeedDataGrid.SelectedItems.Count == 1;
DeleteFeedButton.IsEnabled = FeedDataGrid.SelectedItems.Count > 0;
}
private void AddFeed()
{
var feed = Feed.Create();
var category = (Category) CategoryDataGrid.SelectedItem;
feed.CategoryId = category.Id;
var feedWindow = new FeedWindow();
var result = feedWindow.Display(feed, Window.GetWindow(this));
if (!result.HasValue || !result.Value)
return;
Database.Entities.SaveChanges(() => Database.Entities.Feeds.Add(feed));
FeedDataGrid.SelectedItem = feed;
SetFeedButtonStates();
}
private void EditSelectedFeed()
{
if (FeedDataGrid.SelectedItem == null)
return;
var feed = (Feed) FeedDataGrid.SelectedItem;
var feedWindow = new FeedWindow();
feedWindow.Display(feed, Window.GetWindow(this));
}
private void DeleteSelectedFeeds()
{
if (MessageBox.Show(ParentWindow, Properties.Resources.ConfirmDeleteFeeds, Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
return;
var selectedItems = new Feed[FeedDataGrid.SelectedItems.Count];
FeedDataGrid.SelectedItems.CopyTo(selectedItems, 0);
foreach (var feed in selectedItems)
Database.Entities.SaveChanges(() => Database.Entities.Feeds.Remove(feed));
SetFeedButtonStates();
}
private void HandleAddFeedButtonClick(object sender, RoutedEventArgs e)
{
AddFeed();
}
private void HandleEditFeedButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedFeed();
}
private void HandleDeleteFeedButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedFeeds();
}
private void HandleImportButtonClick(object sender, RoutedEventArgs e)
{
ImportFeeds();
}
private void HandleExportButtonClick(object sender, RoutedEventArgs e)
{
ExportFeeds();
}
private static void ExportFeeds()
{
var saveFileDialog = new SaveFileDialog
{
InitializeComponent();
}
FileName = Properties.Resources.ApplicationName,
Filter = Properties.Resources.ImportExportFilter,
FilterIndex = 0,
OverwritePrompt = true
};
#endregion
var result = saveFileDialog.ShowDialog();
#region OptionsPanelBase overrides
if (!result.GetValueOrDefault(false))
return;
public override void LoadPanel(FeedCenterEntities database)
var writerSettings = new XmlWriterSettings
{
base.LoadPanel(database);
Indent = true,
CheckCharacters = true,
ConformanceLevel = ConformanceLevel.Document
};
var collectionViewSource = new CollectionViewSource { Source = Database.AllCategories };
collectionViewSource.SortDescriptions.Add(new SortDescription("SortKey", ListSortDirection.Ascending));
collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
var xmlWriter = XmlWriter.Create(saveFileDialog.FileName, writerSettings);
CategoryListBox.ItemsSource = collectionViewSource.View;
CategoryListBox.SelectedIndex = 0;
}
xmlWriter.WriteStartElement("opml");
xmlWriter.WriteStartElement("body");
public override bool ValidatePanel()
foreach (var feed in Database.Entities.Feeds.OrderBy(feed => feed.Name))
{
return true;
}
xmlWriter.WriteStartElement("outline");
public override void SavePanel()
{ }
xmlWriter.WriteAttributeString("title", feed.Title);
xmlWriter.WriteAttributeString("htmlUrl", feed.Link);
xmlWriter.WriteAttributeString("xmlUrl", feed.Source);
public override string CategoryName => Properties.Resources.optionCategoryFeeds;
#endregion
#region Feed list management
private void SetFeedButtonStates()
{
AddFeedButton.IsEnabled = true;
EditFeedButton.IsEnabled = (FeedListBox.SelectedItem != null);
DeleteFeedButton.IsEnabled = (FeedListBox.SelectedItem != null);
}
private void AddFeed()
{
var feed = Feed.Create(Database);
var category = (Category) CategoryListBox.SelectedItem;
feed.Category = category;
var feedWindow = new FeedWindow();
var result = feedWindow.Display(Database, feed, Window.GetWindow(this));
if (result.HasValue && result.Value)
{
Database.Feeds.Add(feed);
FeedListBox.SelectedItem = feed;
SetFeedButtonStates();
}
}
private void EditSelectedFeed()
{
if (FeedListBox.SelectedItem == null)
return;
var feed = (Feed) FeedListBox.SelectedItem;
var feedWindow = new FeedWindow();
feedWindow.Display(Database, feed, Window.GetWindow(this));
}
private void DeleteSelectedFeed()
{
var feed = (Feed) FeedListBox.SelectedItem;
Database.Feeds.Remove(feed);
SetFeedButtonStates();
}
#endregion
#region Feed event handlers
private void HandleAddFeedButtonClick(object sender, RoutedEventArgs e)
{
AddFeed();
}
private void HandleEditFeedButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedFeed();
}
private void HandleDeleteFeedButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedFeed();
}
private void HandleImportButtonClick(object sender, RoutedEventArgs e)
{
ImportFeeds();
}
private void HandleExportButtonClick(object sender, RoutedEventArgs e)
{
ExportFeeds();
}
#endregion
#region Feed import and export
private void ExportFeeds()
{
// Setup the save file dialog
var saveFileDialog = new SaveFileDialog
{
Filter = Properties.Resources.ImportExportFilter,
FilterIndex = 0,
OverwritePrompt = true
};
var result = saveFileDialog.ShowDialog();
if (!result.GetValueOrDefault(false))
return;
// Setup the writer settings
var writerSettings = new XmlWriterSettings
{
Indent = true,
CheckCharacters = true,
ConformanceLevel = ConformanceLevel.Document
};
// Create an XML writer for the file chosen
var xmlWriter = XmlWriter.Create(saveFileDialog.FileName, writerSettings);
// Start the opml element
xmlWriter.WriteStartElement("opml");
// Start the body element
xmlWriter.WriteStartElement("body");
// Loop over each feed
foreach (var feed in Database.Feeds.OrderBy(feed => feed.Name))
{
// Start the outline element
xmlWriter.WriteStartElement("outline");
// Write the title
xmlWriter.WriteAttributeString("title", feed.Title);
// Write the HTML link
xmlWriter.WriteAttributeString("htmlUrl", feed.Link);
// Write the XML link
xmlWriter.WriteAttributeString("xmlUrl", feed.Source);
// End the outline element
xmlWriter.WriteEndElement();
}
// End the body element
xmlWriter.WriteEndElement();
// End the opml element
xmlWriter.WriteEndElement();
// Flush and close the writer
xmlWriter.Flush();
xmlWriter.Close();
}
private void ImportFeeds()
xmlWriter.WriteEndElement();
xmlWriter.WriteEndElement();
xmlWriter.Flush();
xmlWriter.Close();
}
private static void ImportFeeds()
{
var openFileDialog = new OpenFileDialog
{
// Setup the open file dialog
var openFileDialog = new OpenFileDialog
Filter = Properties.Resources.ImportExportFilter,
FilterIndex = 0
};
var result = openFileDialog.ShowDialog();
if (!result.GetValueOrDefault(false))
return;
var xmlReaderSettings = new XmlReaderSettings { IgnoreWhitespace = true };
var xmlReader = XmlReader.Create(openFileDialog.FileName, xmlReaderSettings);
try
{
xmlReader.Read();
xmlReader.ReadStartElement("opml");
xmlReader.ReadStartElement("body");
while (xmlReader.NodeType != XmlNodeType.EndElement)
{
Filter = Properties.Resources.ImportExportFilter,
FilterIndex = 0
};
var feed = Feed.Create();
feed.CategoryId = Database.Entities.Categories.First(c => c.IsDefault).Id;
var result = openFileDialog.ShowDialog();
if (!result.GetValueOrDefault(false))
return;
// Setup the reader settings
var xmlReaderSettings = new XmlReaderSettings { IgnoreWhitespace = true };
// Create an XML reader for the file chosen
var xmlReader = XmlReader.Create(openFileDialog.FileName, xmlReaderSettings);
try
{
// Read the first node
xmlReader.Read();
// Read the OPML node
xmlReader.ReadStartElement("opml");
// Read the body node
xmlReader.ReadStartElement("body");
// Read all outline nodes
while (xmlReader.NodeType != XmlNodeType.EndElement)
while (xmlReader.MoveToNextAttribute())
{
// Create a new feed
var feed = Feed.Create(Database);
feed.Category = Database.Categories.First(c => c.IsDefault);
// Loop over all attributes
while (xmlReader.MoveToNextAttribute())
switch (xmlReader.Name.ToLower())
{
// Handle the attibute
switch (xmlReader.Name.ToLower())
{
case "title":
feed.Title = xmlReader.Value;
break;
case "title":
feed.Title = xmlReader.Value;
break;
case "htmlurl":
feed.Link = xmlReader.Value;
break;
// ReSharper disable once StringLiteralTypo
case "htmlurl":
feed.Link = xmlReader.Value;
break;
case "xmlurl":
feed.Source = xmlReader.Value;
break;
// ReSharper disable once StringLiteralTypo
case "xmlurl":
feed.Source = xmlReader.Value;
break;
case "text":
feed.Name = xmlReader.Value;
break;
}
case "text":
feed.Name = xmlReader.Value;
break;
}
// Fill in defaults for optional fields
if (string.IsNullOrEmpty(feed.Name))
feed.Name = feed.Title;
// Add the feed to the main list
Database.Feeds.Add(feed);
// Move back to the element node
xmlReader.MoveToElement();
// Skip to the next node
xmlReader.Skip();
}
// End the body node
xmlReader.ReadEndElement();
if (string.IsNullOrEmpty(feed.Name))
feed.Name = feed.Title;
// End the OPML node
xmlReader.ReadEndElement();
}
finally
{
xmlReader.Close();
}
}
Database.Entities.Feeds.Add(feed);
#endregion
xmlReader.MoveToElement();
#region Category list management
private void SetCategoryButtonStates()
{
AddCategoryButton.IsEnabled = true;
EditCategoryButton.IsEnabled = (CategoryListBox.SelectedItem != null && CategoryListBox.SelectedItem != Database.DefaultCategory);
DeleteCategoryButton.IsEnabled = (CategoryListBox.SelectedItem != null && CategoryListBox.SelectedItem != Database.DefaultCategory);
}
private void AddCategory()
{
var category = Category.Create();
var categoryWindow = new CategoryWindow();
var result = categoryWindow.Display(category, Window.GetWindow(this));
if (result.HasValue && result.Value)
{
Database.Categories.Add(category);
CategoryListBox.SelectedItem = category;
SetCategoryButtonStates();
}
}
private void EditSelectedCategory()
{
if (CategoryListBox.SelectedItem == null)
return;
var category = (Category) CategoryListBox.SelectedItem;
var categoryWindow = new CategoryWindow();
categoryWindow.Display(category, Window.GetWindow(this));
}
private void DeleteSelectedCategory()
{
var defaultCategory = Database.DefaultCategory;
var category = (Category) CategoryListBox.SelectedItem;
category.Feeds.ToList().ForEach(feed => feed.Category = defaultCategory);
var index = CategoryListBox.SelectedIndex;
if (index == CategoryListBox.Items.Count - 1)
CategoryListBox.SelectedIndex = index - 1;
else
CategoryListBox.SelectedIndex = index + 1;
Database.Categories.Remove(category);
SetCategoryButtonStates();
}
#endregion
#region Category event handlers
private void HandleAddCategoryButtonClick(object sender, RoutedEventArgs e)
{
AddCategory();
}
private void HandleEditCategoryButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedCategory();
}
private void HandleDeleteCategoryButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedCategory();
}
#endregion
private CollectionViewSource _collectionViewSource;
private void HandleCategoryListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_collectionViewSource == null)
{
_collectionViewSource = new CollectionViewSource { Source = Database.AllFeeds };
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
FeedListBox.ItemsSource = _collectionViewSource.View;
xmlReader.Skip();
}
_collectionViewSource.View.Refresh();
xmlReader.ReadEndElement();
if (FeedListBox.Items.Count > 0)
FeedListBox.SelectedIndex = 0;
SetFeedButtonStates();
SetCategoryButtonStates();
xmlReader.ReadEndElement();
}
private void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
finally
{
var selectedCategory = (Category) CategoryListBox.SelectedItem;
var feed = (Feed) e.Item;
e.Accepted = (feed.Category.ID == selectedCategory.ID);
}
private void HandleTextBlockDrop(object sender, DragEventArgs e)
{
var feedList = (List<Feed>) e.Data.GetData(typeof(List<Feed>));
var category = (Category) ((DataGridRow) sender).Item;
foreach (var feed in feedList)
feed.Category = category;
_collectionViewSource.View.Refresh();
var dataGridRow = (DataGridRow) sender;
dataGridRow.FontWeight = FontWeights.Normal;
}
private void HandleListBoxItemPreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var selectedItems = FeedListBox.SelectedItems.Cast<Feed>().ToList();
DragDrop.DoDragDrop(FeedListBox, selectedItems, DragDropEffects.Move);
}
}
private void HandleTextBlockDragEnter(object sender, DragEventArgs e)
{
var dataGridRow = (DataGridRow) sender;
dataGridRow.FontWeight = FontWeights.Bold;
}
private void HandleTextBlockDragLeave(object sender, DragEventArgs e)
{
var dataGridRow = (DataGridRow) sender;
dataGridRow.FontWeight = FontWeights.Normal;
}
private void HandleListBoxItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
EditSelectedFeed();
}
private void HandleMultipleEditClick(object sender, RoutedEventArgs e)
{
var bulkFeedWindow = new BulkFeedWindow();
bulkFeedWindow.Display(Window.GetWindow(this), Database);
}
private void HandleFeedListPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Get the object that was clicked on
var originalSource = (DependencyObject) e.OriginalSource;
// Look for a row that contains the object
var dataGridRow = (DataGridRow) FeedListBox.ContainerFromElement(originalSource);
// If the selection already contains this row then ignore it
if (dataGridRow != null && FeedListBox.SelectedItems.Contains(dataGridRow.Item))
e.Handled = true;
xmlReader.Close();
}
}
}
private void SetCategoryButtonStates()
{
AddCategoryButton.IsEnabled = true;
var selectedId = ((Category) CategoryDataGrid.SelectedItem).Id;
EditCategoryButton.IsEnabled = CategoryDataGrid.SelectedItem != null &&
selectedId != Database.Entities.DefaultCategory.Id;
DeleteCategoryButton.IsEnabled = CategoryDataGrid.SelectedItem != null &&
selectedId != Database.Entities.DefaultCategory.Id;
}
private void AddCategory()
{
var category = new Category();
var categoryWindow = new CategoryWindow();
var result = categoryWindow.Display(category, Window.GetWindow(this));
if (!result.HasValue || !result.Value)
return;
Database.Entities.SaveChanges(() => Database.Entities.Categories.Add(category));
CategoryDataGrid.SelectedItem = category;
SetCategoryButtonStates();
}
private void EditSelectedCategory()
{
if (CategoryDataGrid.SelectedItem == null)
return;
var category = (Category) CategoryDataGrid.SelectedItem;
var categoryWindow = new CategoryWindow();
categoryWindow.Display(category, Window.GetWindow(this));
}
private void DeleteSelectedCategory()
{
var category = (Category) CategoryDataGrid.SelectedItem;
if (MessageBox.Show(ParentWindow, string.Format(Properties.Resources.ConfirmDeleteCategory, category.Name), Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
return;
var defaultCategory = Database.Entities.DefaultCategory;
foreach (var feed in Database.Entities.Feeds.Where(f => f.CategoryId == category.Id))
Database.Entities.SaveChanges(() => feed.CategoryId = defaultCategory.Id);
var index = CategoryDataGrid.SelectedIndex;
if (index == CategoryDataGrid.Items.Count - 1)
CategoryDataGrid.SelectedIndex = index - 1;
else
CategoryDataGrid.SelectedIndex = index + 1;
Database.Entities.SaveChanges(() => Database.Entities.Categories.Remove(category));
SetCategoryButtonStates();
}
private void HandleAddCategoryButtonClick(object sender, RoutedEventArgs e)
{
AddCategory();
}
private void HandleEditCategoryButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedCategory();
}
private void HandleDeleteCategoryButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedCategory();
}
private void HandleCategoryDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_collectionViewSource == null)
{
_collectionViewSource = new CollectionViewSource { Source = Database.Entities.Feeds };
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
FeedDataGrid.ItemsSource = _collectionViewSource.View;
}
_collectionViewSource.View.Refresh();
if (FeedDataGrid.Items.Count > 0)
FeedDataGrid.SelectedIndex = 0;
SetFeedButtonStates();
SetCategoryButtonStates();
}
private void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
{
var selectedCategory = (Category) CategoryDataGrid.SelectedItem;
var feed = (Feed) e.Item;
e.Accepted = feed.CategoryId == selectedCategory.Id;
}
private void HandleCategoryDataGridRowDrop(object sender, DragEventArgs e)
{
var feedList = (List<Feed>) e.Data.GetData(typeof(List<Feed>));
var category = (Category) ((DataGridRow) sender).Item;
foreach (var feed in feedList!)
Database.Entities.SaveChanges(() => feed.CategoryId = category.Id);
_collectionViewSource.View.Refresh();
var dataGridRow = (DataGridRow) sender;
dataGridRow.FontWeight = FontWeights.Normal;
}
private void HandleCategoryDataGridRowDragEnter(object sender, DragEventArgs e)
{
var dataGridRow = (DataGridRow) sender;
dataGridRow.FontWeight = FontWeights.Bold;
}
private void HandleCategoryDataGridRowDragLeave(object sender, DragEventArgs e)
{
var dataGridRow = (DataGridRow) sender;
dataGridRow.FontWeight = FontWeights.Normal;
}
private void HandleFeedDataGridRowMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
EditSelectedFeed();
}
private void HandleMultipleEditClick(object sender, RoutedEventArgs e)
{
var bulkFeedWindow = new BulkFeedWindow();
bulkFeedWindow.Display(Window.GetWindow(this));
}
private void HandleFeedDataGridRowPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var selectedItems = FeedDataGrid.SelectedItems.Cast<Feed>().ToList();
DragDrop.DoDragDrop(FeedDataGrid, selectedItems, DragDropEffects.Move);
}
private void HandleCategoryDataGridRowMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!EditCategoryButton.IsEnabled)
return;
EditSelectedCategory();
}
private void HandleFeedDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SetFeedButtonStates();
}
}

View File

@@ -5,54 +5,33 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:options="clr-namespace:FeedCenter.Options"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:installedBrowsers="clr-namespace:ChrisKaczor.InstalledBrowsers;assembly=ChrisKaczor.InstalledBrowsers"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="15" />
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel options:Spacing.Vertical="10">
<CheckBox Content="{x:Static properties:Resources.startWithWindowsCheckBox}"
Name="StartWithWindowsCheckBox"
VerticalAlignment="Top"
VerticalContentAlignment="Center"
Margin="0,5"
Grid.ColumnSpan="2" />
<Label Content="{x:Static properties:Resources.defaultBrowserLabel}"
Target="{Binding ElementName=BrowserComboBox}"
Grid.Column="0"
Grid.Row="2"
Padding="0"
VerticalContentAlignment="Center"
Margin="0,0,5,0" />
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=StartWithWindows}"
Click="OnSaveSettings" />
<ComboBox Name="BrowserComboBox"
Grid.Row="2"
Grid.Column="1"
VerticalContentAlignment="Center">
<ComboBoxItem Content="{x:Static properties:Resources.DefaultBrowserCaption}"
Tag="" />
</ComboBox>
<Label Content="{x:Static properties:Resources.defaultUserAgentLabel}"
Target="{Binding ElementName=BrowserComboBox}"
Grid.Column="0"
Grid.Row="4"
Padding="0"
VerticalContentAlignment="Center"
Margin="0,0,5,0" />
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.defaultBrowserLabel}"
d:DataContext="{d:DesignInstance Type=installedBrowsers:InstalledBrowser}"
DisplayMemberPath="Name"
ItemsSource="{Binding Source={x:Static installedBrowsers:InstalledBrowser.InstalledBrowsers}}"
SelectedValuePath="Key"
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=Browser}"
SelectionChanged="OnSaveSettings" />
<ComboBox Name="UserAgentComboBox"
Grid.Row="4"
Grid.Column="1"
VerticalContentAlignment="Center">
</ComboBox>
</Grid>
</options:OptionsPanelBase>
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.defaultUserAgentLabel}"
d:DataContext="{d:DesignInstance Type=options:UserAgentItem}"
DisplayMemberPath="Caption"
ItemsSource="{Binding Source={x:Static options:UserAgentItem.UserAgents}}"
SelectedValuePath="UserAgent"
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=DefaultUserAgent}"
SelectionChanged="OnSaveSettings" />
</StackPanel>
</options:OptionsPanelBase>

View File

@@ -1,132 +1,36 @@
using System.Collections.Generic;
using ChrisKaczor.Wpf.Application;
using FeedCenter.Properties;
using System.Windows;
using Common.Wpf.Extensions;
using Common.Internet;
using System.Windows.Controls;
using System.Windows.Data;
internal class UserAgentItem
{
internal string Caption { get; set; }
internal string UserAgent { get; set; }
}
namespace FeedCenter.Options;
namespace FeedCenter.Options
public partial class GeneralOptionsPanel
{
public partial class GeneralOptionsPanel
public GeneralOptionsPanel(Window parentWindow) : base(parentWindow)
{
public GeneralOptionsPanel()
{
InitializeComponent();
}
public override void LoadPanel(FeedCenterEntities database)
{
base.LoadPanel(database);
var settings = Properties.Settings.Default;
StartWithWindowsCheckBox.IsChecked = settings.StartWithWindows;
LoadBrowserComboBox(BrowserComboBox, settings.Browser);
LoadUserAgentComboBox(UserAgentComboBox, settings.DefaultUserAgent);
}
public override bool ValidatePanel()
{
return true;
}
public override void SavePanel()
{
var settings = Properties.Settings.Default;
if (StartWithWindowsCheckBox.IsChecked.HasValue &&
settings.StartWithWindows != StartWithWindowsCheckBox.IsChecked.Value)
settings.StartWithWindows = StartWithWindowsCheckBox.IsChecked.Value;
Application.Current.SetStartWithWindows(settings.StartWithWindows);
settings.Browser = (string) ((ComboBoxItem) BrowserComboBox.SelectedItem).Tag;
settings.DefaultUserAgent = (string) ((ComboBoxItem) UserAgentComboBox.SelectedItem).Tag;
var expressions = this.GetBindingExpressions(new[] { UpdateSourceTrigger.Explicit });
this.UpdateAllSources(expressions);
}
public override string CategoryName => Properties.Resources.optionCategoryGeneral;
private static void LoadBrowserComboBox(ComboBox comboBox, string selected)
{
comboBox.SelectedIndex = 0;
ComboBoxItem selectedItem = null;
var browsers = Browser.DetectInstalledBrowsers();
foreach (var browser in browsers)
{
var item = new ComboBoxItem { Content = browser.Value.Name, Tag = browser.Key };
comboBox.Items.Add(item);
if (browser.Key == selected)
selectedItem = item;
}
if (selectedItem != null)
comboBox.SelectedItem = selectedItem;
}
private static void LoadUserAgentComboBox(ComboBox comboBox, string selected)
{
comboBox.SelectedIndex = 0;
ComboBoxItem selectedItem = null;
var userAgents = GetUserAgents();
foreach (var userAgent in userAgents)
{
var item = new ComboBoxItem { Content = userAgent.Caption, Tag = userAgent.UserAgent };
comboBox.Items.Add(item);
if (userAgent.UserAgent == selected)
selectedItem = item;
}
if (selectedItem != null)
comboBox.SelectedItem = selectedItem;
}
private static List<UserAgentItem> GetUserAgents()
{
var userAgents = new List<UserAgentItem>
{
new UserAgentItem
{
Caption = Properties.Resources.DefaultUserAgentCaption,
UserAgent = string.Empty
},
new UserAgentItem
{
Caption = "Windows RSS Platform 2.0",
UserAgent = "Windows-RSS-Platform/2.0 (MSIE 9.0; Windows NT 6.1)"
},
new UserAgentItem
{
Caption = "Feedly 1.0",
UserAgent = "Feedly/1.0"
},
new UserAgentItem
{
Caption = "curl",
UserAgent = "curl/7.47.0"
}
};
return userAgents;
}
InitializeComponent();
}
}
public override string CategoryName => Properties.Resources.optionCategoryGeneral;
public override void LoadPanel()
{
base.LoadPanel();
MarkLoaded();
}
private void OnSaveSettings(object sender, RoutedEventArgs e)
{
SaveSettings();
}
private void SaveSettings()
{
if (!HasLoaded) return;
Settings.Default.Save();
Application.Current.SetStartWithWindows(Settings.Default.StartWithWindows);
}
}

View File

@@ -0,0 +1,73 @@
using JetBrains.Annotations;
using System.Windows;
using System.Windows.Controls;
namespace FeedCenter.Options;
public class MarginSetter
{
private static Thickness GetLastItemMargin(DependencyObject obj)
{
return (Thickness) obj.GetValue(LastItemMarginProperty);
}
[UsedImplicitly]
public static Thickness GetMargin(DependencyObject obj)
{
return (Thickness) obj.GetValue(MarginProperty);
}
private static void MarginChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
// Make sure this is put on a panel
if (sender is not Panel panel)
return;
// Avoid duplicate registrations
panel.Loaded -= OnPanelLoaded;
panel.Loaded += OnPanelLoaded;
if (panel.IsLoaded)
{
OnPanelLoaded(panel, null);
}
}
private static void OnPanelLoaded(object sender, RoutedEventArgs e)
{
var panel = (Panel) sender;
// Go over the children and set margin for them:
for (var i = 0; i < panel.Children.Count; i++)
{
var child = panel.Children[i];
if (child is not FrameworkElement fe)
continue;
var isLastItem = i == panel.Children.Count - 1;
fe.Margin = isLastItem ? GetLastItemMargin(panel) : GetMargin(panel);
}
}
[UsedImplicitly]
public static void SetLastItemMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(LastItemMarginProperty, value);
}
[UsedImplicitly]
public static void SetMargin(DependencyObject obj, Thickness value)
{
obj.SetValue(MarginProperty, value);
}
// Using a DependencyProperty as the backing store for Margin. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MarginProperty =
DependencyProperty.RegisterAttached("Margin", typeof(Thickness), typeof(MarginSetter),
new UIPropertyMetadata(new Thickness(), MarginChangedCallback));
public static readonly DependencyProperty LastItemMarginProperty =
DependencyProperty.RegisterAttached("LastItemMargin", typeof(Thickness), typeof(MarginSetter),
new UIPropertyMetadata(new Thickness(), MarginChangedCallback));
}

View File

@@ -1,9 +1,8 @@
namespace FeedCenter.Options
namespace FeedCenter.Options;
public enum MultipleLineDisplay
{
public enum MultipleLineDisplay
{
Normal,
SingleLine,
FirstLine
}
}
Normal,
SingleLine,
FirstLine
}

View File

@@ -1,27 +1,27 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public class OptionsPanelBase : UserControl
{
public class OptionsPanelBase : UserControl
protected readonly Window ParentWindow;
protected OptionsPanelBase(Window parentWindow)
{
protected FeedCenterEntities Database { get; private set; }
public virtual void LoadPanel(FeedCenterEntities database)
{
Database = database;
}
public virtual bool ValidatePanel()
{
throw new NotImplementedException();
}
public virtual void SavePanel()
{
throw new NotImplementedException();
}
public virtual string CategoryName => null;
ParentWindow = parentWindow;
}
}
public virtual string CategoryName => null;
protected bool HasLoaded { get; private set; }
public virtual void LoadPanel()
{
}
protected void MarkLoaded()
{
HasLoaded = true;
}
}

View File

@@ -8,29 +8,44 @@
ResizeMode="CanResize"
WindowStartupLocation="CenterScreen"
Icon="/FeedCenter;component/Resources/Application.ico">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ListBox HorizontalAlignment="Left"
Name="CategoryListBox"
Width="126"
SelectionChanged="HandleSelectedCategoryChanged"
Margin="12,12,0,41" />
<ContentControl Margin="144,12,12,41"
Name="ContentControl"
IsTabStop="False" />
<Button Content="{x:Static properties:Resources.OkayButton}"
Height="23"
HorizontalAlignment="Right"
Margin="0,0,93,12"
VerticalAlignment="Bottom"
Width="75"
IsDefault="True"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static properties:Resources.CancelButton}"
Margin="0,0,12,12"
Height="23"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Width="75"
IsCancel="True" />
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
Grid.Row="0"
Name="CategoryListBox"
SelectionChanged="HandleSelectedCategoryChanged" />
<ContentControl
Grid.Column="2"
Grid.Row="0"
Margin="0,6,6,6"
Name="ContentControl"
IsTabStop="False" />
<Button
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0,6,6,6"
Content="{x:Static properties:Resources.CloseButton}"
HorizontalAlignment="Right"
IsCancel="True" />
</Grid>
</Window>
</Window>

View File

@@ -1,128 +1,75 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class OptionsWindow
{
public partial class OptionsWindow
private readonly List<OptionsPanelBase> _optionPanels = new();
public OptionsWindow()
{
#region Member variables
InitializeComponent();
private readonly List<OptionsPanelBase> _optionPanels = new List<OptionsPanelBase>();
// Add all the option categories
AddCategories();
private readonly FeedCenterEntities _database = new FeedCenterEntities();
// Load the category list
LoadCategories();
}
#endregion
private void AddCategories()
{
_optionPanels.Add(new GeneralOptionsPanel(this));
_optionPanels.Add(new DisplayOptionsPanel(this));
_optionPanels.Add(new FeedsOptionsPanel(this));
_optionPanels.Add(new UpdateOptionsPanel(this));
_optionPanels.Add(new AboutOptionsPanel(this));
}
#region Constructor
public OptionsWindow()
private void LoadCategories()
{
// Loop over each panel
foreach (var optionsPanel in _optionPanels)
{
InitializeComponent();
// Tell the panel to load itself
optionsPanel.LoadPanel();
// Add all the option categories
AddCategories();
// Add the panel to the category ist
CategoryListBox.Items.Add(new CategoryListItem(optionsPanel));
// Load the category list
LoadCategories();
// Set the panel into the right side
ContentControl.Content = optionsPanel;
}
#endregion
// Select the first item
CategoryListBox.SelectedItem = CategoryListBox.Items[0];
}
#region Category handling
private void SelectCategory(OptionsPanelBase panel)
{
// Set the content
ContentControl.Content = panel;
}
private void AddCategories()
private void HandleSelectedCategoryChanged(object sender, SelectionChangedEventArgs e)
{
// Select the right category
SelectCategory(((CategoryListItem) CategoryListBox.SelectedItem).Panel);
}
private class CategoryListItem
{
public CategoryListItem(OptionsPanelBase panel)
{
_optionPanels.Add(new GeneralOptionsPanel());
_optionPanels.Add(new DisplayOptionsPanel());
_optionPanels.Add(new FeedsOptionsPanel());
_optionPanels.Add(new UpdateOptionsPanel());
_optionPanels.Add(new AboutOptionsPanel());
Panel = panel;
}
private void LoadCategories()
public OptionsPanelBase Panel { get; }
public override string ToString()
{
// Loop over each panel
foreach (OptionsPanelBase optionsPanel in _optionPanels)
{
// Tell the panel to load itself
optionsPanel.LoadPanel(_database);
// Add the panel to the category ist
CategoryListBox.Items.Add(new CategoryListItem(optionsPanel));
// Set the panel into the right side
ContentControl.Content = optionsPanel;
}
// Select the first item
CategoryListBox.SelectedItem = CategoryListBox.Items[0];
}
private void SelectCategory(OptionsPanelBase panel)
{
// Set the content
ContentControl.Content = panel;
}
private void HandleSelectedCategoryChanged(object sender, SelectionChangedEventArgs e)
{
// Select the right category
SelectCategory(((CategoryListItem) CategoryListBox.SelectedItem).Panel);
}
#endregion
#region Category list item
private class CategoryListItem
{
public OptionsPanelBase Panel { get; }
public CategoryListItem(OptionsPanelBase panel)
{
Panel = panel;
}
public override string ToString()
{
return Panel.CategoryName;
}
}
#endregion
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
// Loop over each panel and ask them to validate
foreach (OptionsPanelBase optionsPanel in _optionPanels)
{
// If validation fails...
if (!optionsPanel.ValidatePanel())
{
// ...select the right category
SelectCategory(optionsPanel);
// Stop validation
return;
}
}
// Loop over each panel and ask them to save
foreach (OptionsPanelBase optionsPanel in _optionPanels)
{
// Save!
optionsPanel.SavePanel();
}
// Save the actual settings
_database.SaveChanges();
Properties.Settings.Default.Save();
DialogResult = true;
// Close the window
Close();
return Panel.CategoryName;
}
}
}
}

View File

@@ -0,0 +1,13 @@
using Realms;
namespace FeedCenter.Options;
public class Setting : RealmObject
{
[PrimaryKey]
public string Name { get; set; }
public string Value { get; set; }
[Ignored]
public string Version { get; set; }
}

View File

@@ -0,0 +1,54 @@
using JetBrains.Annotations;
using System.Windows;
namespace FeedCenter.Options;
public class Spacing
{
public static double GetHorizontal(DependencyObject obj)
{
return (double) obj.GetValue(HorizontalProperty);
}
public static double GetVertical(DependencyObject obj)
{
return (double) obj.GetValue(VerticalProperty);
}
private static void HorizontalChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
var space = (double) e.NewValue;
var obj = (DependencyObject) sender;
MarginSetter.SetMargin(obj, new Thickness(0, 0, space, 0));
MarginSetter.SetLastItemMargin(obj, new Thickness(0));
}
[UsedImplicitly]
public static void SetHorizontal(DependencyObject obj, double space)
{
obj.SetValue(HorizontalProperty, space);
}
[UsedImplicitly]
public static void SetVertical(DependencyObject obj, double value)
{
obj.SetValue(VerticalProperty, value);
}
private static void VerticalChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
{
var space = (double) e.NewValue;
var obj = (DependencyObject) sender;
MarginSetter.SetMargin(obj, new Thickness(0, 0, 0, space));
MarginSetter.SetLastItemMargin(obj, new Thickness(0));
}
public static readonly DependencyProperty VerticalProperty =
DependencyProperty.RegisterAttached("Vertical", typeof(double), typeof(Spacing),
new UIPropertyMetadata(0d, VerticalChangedCallback));
public static readonly DependencyProperty HorizontalProperty =
DependencyProperty.RegisterAttached("Horizontal", typeof(double), typeof(Spacing),
new UIPropertyMetadata(0d, HorizontalChangedCallback));
}

View File

@@ -6,18 +6,15 @@
xmlns:options="clr-namespace:FeedCenter.Options"
xmlns:properties="clr-namespace:FeedCenter.Properties"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<Grid>
d:DesignHeight="150"
d:DesignWidth="250">
<StackPanel options:Spacing.Vertical="10">
<CheckBox Content="{x:Static properties:Resources.checkVersionOnStartupCheckBox}"
Name="CheckVersionOnStartupCheckBox"
VerticalAlignment="Top" />
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=CheckVersionAtStartup}"
Click="OnSaveSettings" />
<Button Content="{x:Static properties:Resources.checkVersionNowButton}"
Height="23"
HorizontalAlignment="Left"
Margin="0,22,0,0"
VerticalAlignment="Top"
Width="75"
Click="HandleCheckVersionNowButtonClick" />
</Grid>
</options:OptionsPanelBase>
</StackPanel>
</options:OptionsPanelBase>

View File

@@ -1,37 +1,32 @@
using Common.Update;
using ChrisKaczor.ApplicationUpdate;
using FeedCenter.Properties;
using System.Windows;
namespace FeedCenter.Options
namespace FeedCenter.Options;
public partial class UpdateOptionsPanel
{
public partial class UpdateOptionsPanel
public UpdateOptionsPanel(Window parentWindow) : base(parentWindow)
{
public UpdateOptionsPanel()
{
InitializeComponent();
}
public override void LoadPanel(FeedCenterEntities database)
{
base.LoadPanel(database);
CheckVersionOnStartupCheckBox.IsChecked = Properties.Settings.Default.CheckVersionAtStartup;
}
public override bool ValidatePanel()
{
return true;
}
public override void SavePanel()
{
if (CheckVersionOnStartupCheckBox.IsChecked.HasValue && Properties.Settings.Default.CheckVersionAtStartup != CheckVersionOnStartupCheckBox.IsChecked.Value)
Properties.Settings.Default.CheckVersionAtStartup = CheckVersionOnStartupCheckBox.IsChecked.Value;
}
public override string CategoryName => Properties.Resources.optionCategoryUpdate;
private void HandleCheckVersionNowButtonClick(object sender, System.Windows.RoutedEventArgs e)
{
UpdateCheck.DisplayUpdateInformation(true);
}
InitializeComponent();
}
}
public override string CategoryName => Properties.Resources.optionCategoryUpdate;
private void HandleCheckVersionNowButtonClick(object sender, RoutedEventArgs e)
{
UpdateCheck.DisplayUpdateInformation(true);
}
private void OnSaveSettings(object sender, RoutedEventArgs e)
{
SaveSettings();
}
private void SaveSettings()
{
if (!HasLoaded) return;
Settings.Default.Save();
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
namespace FeedCenter.Options;
public class UserAgentItem
{
public string Caption { get; set; }
public string UserAgent { get; set; }
public static List<UserAgentItem> UserAgents => new()
{
new UserAgentItem
{
Caption = Properties.Resources.DefaultUserAgentCaption,
UserAgent = string.Empty
},
new UserAgentItem
{
Caption = "Windows RSS Platform 2.0",
UserAgent = "Windows-RSS-Platform/2.0 (MSIE 9.0; Windows NT 6.1)"
},
new UserAgentItem
{
Caption = "Feedly 1.0",
UserAgent = "Feedly/1.0"
},
new UserAgentItem
{
Caption = "curl",
UserAgent = "curl/7.47.0"
}
};
}

View File

@@ -1,16 +1,19 @@
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows;
[assembly: AssemblyTitle("Feed Center")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("Chris Kaczor")]
[assembly: AssemblyProduct("Feed Center")]
[assembly: AssemblyCopyright("Copyright © Chris Kaczor 2010")]
[assembly: AssemblyCopyright("Copyright © Chris Kaczor 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: SupportedOSPlatform("windows")]
#if DEBUG
[assembly: AssemblyConfiguration("Debug build")]
#else
@@ -21,5 +24,5 @@ using System.Windows;
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="FeedCenterEntities" Identifier="FeedCenter.FeedCenterEntities" ProviderType="Microsoft.VisualStudio.DataDesign.DataSourceProviders.EntityDataModel.EdmDataSourceProvider" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>FeedCenter.FeedCenterEntities, Model.Designer.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

View File

@@ -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", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
@@ -134,7 +134,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to _Password:.
/// Looks up a localized string similar to Password.
/// </summary>
public static string authenticationPasswordLabel {
get {
@@ -152,7 +152,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to _User name:.
/// Looks up a localized string similar to User name.
/// </summary>
public static string authenticationUserNameLabel {
get {
@@ -214,6 +214,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string categoryNameLabel {
get {
return ResourceManager.GetString("categoryNameLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add Category.
/// </summary>
@@ -259,12 +268,41 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete this category?
///
///All feeds currently in category &quot;{0}&quot; will be moved to the default category..
/// </summary>
public static string ConfirmDeleteCategory {
get {
return ResourceManager.GetString("ConfirmDeleteCategory", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete this feed?.
/// </summary>
public static string ConfirmDelete {
public static string ConfirmDeleteFeed {
get {
return ResourceManager.GetString("ConfirmDelete", resourceCulture);
return ResourceManager.GetString("ConfirmDeleteFeed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete the selected feeds?.
/// </summary>
public static string ConfirmDeleteFeeds {
get {
return ResourceManager.GetString("ConfirmDeleteFeeds", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Confirm Delete.
/// </summary>
public static string ConfirmDeleteTitle {
get {
return ResourceManager.GetString("ConfirmDeleteTitle", resourceCulture);
}
}
@@ -470,7 +508,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Default _browser:.
/// Looks up a localized string similar to Default browser.
/// </summary>
public static string defaultBrowserLabel {
get {
@@ -488,7 +526,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Default _user agent:.
/// Looks up a localized string similar to Default user agent.
/// </summary>
public static string defaultUserAgentLabel {
get {
@@ -641,7 +679,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to _Category:.
/// Looks up a localized string similar to Category.
/// </summary>
public static string feedCategoryLabel {
get {
@@ -695,7 +733,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Feed _link contains:.
/// Looks up a localized string similar to Feed link contains.
/// </summary>
public static string FeedLinkFilterLabel {
get {
@@ -722,7 +760,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Feed _name:.
/// Looks up a localized string similar to Name.
/// </summary>
public static string feedNameLabel {
get {
@@ -830,7 +868,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Feed _URL:.
/// Looks up a localized string similar to URL.
/// </summary>
public static string feedUrlLabel {
get {
@@ -956,7 +994,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to _Multi-line items:.
/// Looks up a localized string similar to Multiline titles.
/// </summary>
public static string multipleLineDisplayLabel {
get {
@@ -1064,7 +1102,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to &quot;Open all&quot; _action:.
/// Looks up a localized string similar to &quot;Open all&quot; action.
/// </summary>
public static string openLabel {
get {
@@ -1298,11 +1336,11 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Checking database existence....
/// Looks up a localized string similar to Checking for legacy database....
/// </summary>
public static string SplashCheckingForDatabase {
public static string SplashCheckingForLegacyDatabase {
get {
return ResourceManager.GetString("SplashCheckingForDatabase", resourceCulture);
return ResourceManager.GetString("SplashCheckingForLegacyDatabase", resourceCulture);
}
}
@@ -1343,11 +1381,29 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Maintaining database....
/// Looks up a localized string similar to Loading database....
/// </summary>
public static string SplashMaintainingDatabase {
public static string SplashLoadingDatabase {
get {
return ResourceManager.GetString("SplashMaintainingDatabase", resourceCulture);
return ResourceManager.GetString("SplashLoadingDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Maintaining legacy database....
/// </summary>
public static string SplashMaintainingLegacyDatabase {
get {
return ResourceManager.GetString("SplashMaintainingLegacyDatabase", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Migrating legacy database....
/// </summary>
public static string SplashMigratingLegacyDatabase {
get {
return ResourceManager.GetString("SplashMigratingLegacyDatabase", resourceCulture);
}
}
@@ -1370,11 +1426,11 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Updating database....
/// Looks up a localized string similar to Updating legacy database....
/// </summary>
public static string SplashUpdatingDatabase {
public static string SplashUpdatingLegacyDatabase {
get {
return ResourceManager.GetString("SplashUpdatingDatabase", resourceCulture);
return ResourceManager.GetString("SplashUpdatingLegacyDatabase", resourceCulture);
}
}
@@ -1388,7 +1444,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to _Tool bar location:.
/// Looks up a localized string similar to Toolbar location.
/// </summary>
public static string toolbarLocationLabel {
get {
@@ -1406,7 +1462,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Version: {0}.
/// Looks up a localized string similar to Version {0}.
/// </summary>
public static string Version {
get {

View File

@@ -149,13 +149,13 @@
<value>&lt; no title &gt;</value>
</data>
<data name="Version" xml:space="preserve">
<value>Version: {0}</value>
<value>Version {0}</value>
</data>
<data name="SplashCheckingForUpdate" xml:space="preserve">
<value>Checking for update...</value>
</data>
<data name="SplashCheckingForDatabase" xml:space="preserve">
<value>Checking database existence...</value>
<data name="SplashCheckingForLegacyDatabase" xml:space="preserve">
<value>Checking for legacy database...</value>
</data>
<data name="SplashCreatingDatabase" xml:space="preserve">
<value>Creating database...</value>
@@ -169,11 +169,11 @@
<data name="SplashDownloadingUpdate" xml:space="preserve">
<value>Downloading update...</value>
</data>
<data name="SplashMaintainingDatabase" xml:space="preserve">
<value>Maintaining database...</value>
<data name="SplashMaintainingLegacyDatabase" xml:space="preserve">
<value>Maintaining legacy database...</value>
</data>
<data name="SplashUpdatingDatabase" xml:space="preserve">
<value>Updating database...</value>
<data name="SplashUpdatingLegacyDatabase" xml:space="preserve">
<value>Updating legacy database...</value>
</data>
<data name="SplashStarting" xml:space="preserve">
<value>Starting...</value>
@@ -266,7 +266,7 @@
<value>Authentication</value>
</data>
<data name="feedNameLabel" xml:space="preserve">
<value>Feed _name:</value>
<value>Name</value>
</data>
<data name="feedReadIntervalPrefix" xml:space="preserve">
<value>_Refresh feed every</value>
@@ -275,7 +275,7 @@
<value>minutes</value>
</data>
<data name="feedUrlLabel" xml:space="preserve">
<value>Feed _URL:</value>
<value>URL</value>
</data>
<data name="FeedWindowAdd" xml:space="preserve">
<value>Add Feed</value>
@@ -293,10 +293,10 @@
<value>_Check for a new version on startup</value>
</data>
<data name="authenticationPasswordLabel" xml:space="preserve">
<value>_Password:</value>
<value>Password</value>
</data>
<data name="authenticationUserNameLabel" xml:space="preserve">
<value>_User name:</value>
<value>User name</value>
</data>
<data name="requiresAuthenticationCheckBox" xml:space="preserve">
<value>_Feed requires authentication</value>
@@ -320,7 +320,7 @@
<value>Right</value>
</data>
<data name="toolbarLocationLabel" xml:space="preserve">
<value>_Tool bar location:</value>
<value>Toolbar location</value>
</data>
<data name="Top" xml:space="preserve">
<value>Top</value>
@@ -347,13 +347,13 @@
<value>_Register as default feed reader</value>
</data>
<data name="defaultBrowserLabel" xml:space="preserve">
<value>Default _browser:</value>
<value>Default browser</value>
</data>
<data name="DefaultBrowserCaption" xml:space="preserve">
<value>&lt; Windows Default &gt;</value>
</data>
<data name="feedCategoryLabel" xml:space="preserve">
<value>_Category:</value>
<value>Category</value>
</data>
<data name="optionCategoryCategories" xml:space="preserve">
<value>Categories</value>
@@ -365,7 +365,7 @@
<value>Edit Category</value>
</data>
<data name="multipleLineDisplayLabel" xml:space="preserve">
<value>_Multi-line items:</value>
<value>Multiline titles</value>
</data>
<data name="multipleLineDisplayFirstLine" xml:space="preserve">
<value>Show only first line</value>
@@ -400,14 +400,14 @@
<data name="EditMenu" xml:space="preserve">
<value>Edit...</value>
</data>
<data name="ConfirmDelete" xml:space="preserve">
<data name="ConfirmDeleteFeed" xml:space="preserve">
<value>Are you sure you want to delete this feed?</value>
</data>
<data name="optionCategoryReading" xml:space="preserve">
<value>Reading</value>
</data>
<data name="openLabel" xml:space="preserve">
<value>"Open all" _action:</value>
<value>"Open all" action</value>
</data>
<data name="DatabaseUpdate_5" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Scripts\DatabaseUpdate_5.sqlce;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
@@ -419,7 +419,7 @@
<value>Edit Multiple Feeds</value>
</data>
<data name="FeedLinkFilterLabel" xml:space="preserve">
<value>Feed _link contains:</value>
<value>Feed link contains</value>
</data>
<data name="SelectAllLabel" xml:space="preserve">
<value>All</value>
@@ -527,6 +527,26 @@
<value>Feed Center</value>
</data>
<data name="defaultUserAgentLabel" xml:space="preserve">
<value>Default _user agent:</value>
<value>Default user agent</value>
</data>
<data name="SplashLoadingDatabase" xml:space="preserve">
<value>Loading database...</value>
</data>
<data name="SplashMigratingLegacyDatabase" xml:space="preserve">
<value>Migrating legacy database...</value>
</data>
<data name="categoryNameLabel" xml:space="preserve">
<value>Name</value>
</data>
<data name="ConfirmDeleteCategory" xml:space="preserve">
<value>Are you sure you want to delete this category?
All feeds currently in category "{0}" will be moved to the default category.</value>
</data>
<data name="ConfirmDeleteTitle" xml:space="preserve">
<value>Confirm Delete</value>
</data>
<data name="ConfirmDeleteFeeds" xml:space="preserve">
<value>Are you sure you want to delete the selected feeds?</value>
</data>
</root>

View File

@@ -12,7 +12,7 @@ namespace FeedCenter.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -24,7 +24,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool WindowLocked {
@@ -37,7 +37,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0,0")]
public global::System.Windows.Size WindowSize {
@@ -50,7 +50,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0,0")]
public global::System.Windows.Point WindowLocation {
@@ -74,9 +74,9 @@ namespace FeedCenter.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("FeedCenter.sdf")]
public string DatabaseFile {
public string DatabaseFile_Legacy {
get {
return ((string)(this["DatabaseFile"]));
return ((string)(this["DatabaseFile_Legacy"]));
}
}
@@ -111,7 +111,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("00:01:00")]
public global::System.TimeSpan FeedScrollInterval {
@@ -124,7 +124,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("00:30:00")]
public global::System.TimeSpan FeedCheckInterval {
@@ -137,7 +137,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool DisplayEmptyFeeds {
@@ -150,7 +150,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.DateTime LastVersionCheck {
get {
@@ -171,7 +171,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool StartWithWindows {
@@ -196,7 +196,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("Bottom")]
public global::System.Windows.Controls.Dock ToolbarLocation {
@@ -209,7 +209,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("500")]
public int OpenAllSleepInterval {
@@ -222,7 +222,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string Browser {
@@ -235,7 +235,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("1500")]
public int OpenAllSleepIntervalFirst {
@@ -248,7 +248,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("Normal")]
public global::FeedCenter.Options.MultipleLineDisplay MultipleLineDisplay {
@@ -262,16 +262,7 @@ namespace FeedCenter.Properties {
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("FeedCenterSetup.xml")]
public string VersionFile {
get {
return ((string)(this["VersionFile"]));
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("http://server/FeedCenter/")]
[global::System.Configuration.DefaultSettingValueAttribute("https://api.github.com/repos/ckaczor/FeedCenter/releases/latest")]
public string VersionLocation {
get {
return ((string)(this["VersionLocation"]));
@@ -291,7 +282,7 @@ namespace FeedCenter.Properties {
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Configuration.SettingsProviderAttribute(typeof(Common.Settings.GenericSettingsProvider))]
[global::System.Configuration.SettingsProviderAttribute(typeof(ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider))]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string DefaultUserAgent {
@@ -302,5 +293,14 @@ namespace FeedCenter.Properties {
this["DefaultUserAgent"] = value;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("FeedCenter.realm")]
public string DatabaseFile {
get {
return ((string)(this["DatabaseFile"]));
}
}
}
}

View File

@@ -2,19 +2,19 @@
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="FeedCenter.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="WindowLocked" Provider="Common.Settings.GenericSettingsProvider" Type="System.Boolean" Scope="User">
<Setting Name="WindowLocked" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="WindowSize" Provider="Common.Settings.GenericSettingsProvider" Type="System.Windows.Size" Scope="User">
<Setting Name="WindowSize" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Windows.Size" Scope="User">
<Value Profile="(Default)">0,0</Value>
</Setting>
<Setting Name="WindowLocation" Provider="Common.Settings.GenericSettingsProvider" Type="System.Windows.Point" Scope="User">
<Setting Name="WindowLocation" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Windows.Point" Scope="User">
<Value Profile="(Default)">0,0</Value>
</Setting>
<Setting Name="LogDatabase" Type="System.Boolean" Scope="Application">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="DatabaseFile" Type="System.String" Scope="Application">
<Setting Name="DatabaseFile_Legacy" Type="System.String" Scope="Application">
<Value Profile="(Default)">FeedCenter.sdf</Value>
</Setting>
<Setting Name="ProgressSleepInterval" Type="System.Int32" Scope="Application">
@@ -26,53 +26,53 @@
<Setting Name="BalloonTipTimeout" Type="System.Int32" Scope="Application">
<Value Profile="(Default)">5000</Value>
</Setting>
<Setting Name="FeedScrollInterval" Provider="Common.Settings.GenericSettingsProvider" Type="System.TimeSpan" Scope="User">
<Setting Name="FeedScrollInterval" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.TimeSpan" Scope="User">
<Value Profile="(Default)">00:01:00</Value>
</Setting>
<Setting Name="FeedCheckInterval" Provider="Common.Settings.GenericSettingsProvider" Type="System.TimeSpan" Scope="User">
<Setting Name="FeedCheckInterval" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.TimeSpan" Scope="User">
<Value Profile="(Default)">00:30:00</Value>
</Setting>
<Setting Name="DisplayEmptyFeeds" Provider="Common.Settings.GenericSettingsProvider" Type="System.Boolean" Scope="User">
<Setting Name="DisplayEmptyFeeds" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="LastVersionCheck" Provider="Common.Settings.GenericSettingsProvider" Type="System.DateTime" Scope="User">
<Setting Name="LastVersionCheck" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.DateTime" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="VersionCheckInterval" Type="System.TimeSpan" Scope="Application">
<Value Profile="(Default)">01:00:00</Value>
</Setting>
<Setting Name="StartWithWindows" Provider="Common.Settings.GenericSettingsProvider" Type="System.Boolean" Scope="User">
<Setting Name="StartWithWindows" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="FirstRun" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="ToolbarLocation" Provider="Common.Settings.GenericSettingsProvider" Type="System.Windows.Controls.Dock" Scope="User">
<Setting Name="ToolbarLocation" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Windows.Controls.Dock" Scope="User">
<Value Profile="(Default)">Bottom</Value>
</Setting>
<Setting Name="OpenAllSleepInterval" Provider="Common.Settings.GenericSettingsProvider" Type="System.Int32" Scope="User">
<Setting Name="OpenAllSleepInterval" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Int32" Scope="User">
<Value Profile="(Default)">500</Value>
</Setting>
<Setting Name="Browser" Provider="Common.Settings.GenericSettingsProvider" Type="System.String" Scope="User">
<Setting Name="Browser" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="OpenAllSleepIntervalFirst" Provider="Common.Settings.GenericSettingsProvider" Type="System.Int32" Scope="User">
<Setting Name="OpenAllSleepIntervalFirst" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.Int32" Scope="User">
<Value Profile="(Default)">1500</Value>
</Setting>
<Setting Name="MultipleLineDisplay" Provider="Common.Settings.GenericSettingsProvider" Type="FeedCenter.Options.MultipleLineDisplay" Scope="User">
<Setting Name="MultipleLineDisplay" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="FeedCenter.Options.MultipleLineDisplay" Scope="User">
<Value Profile="(Default)">Normal</Value>
</Setting>
<Setting Name="VersionFile" Type="System.String" Scope="Application">
<Value Profile="(Default)">FeedCenterSetup.xml</Value>
</Setting>
<Setting Name="VersionLocation" Type="System.String" Scope="Application">
<Value Profile="(Default)">http://server/FeedCenter/</Value>
<Value Profile="(Default)">https://api.github.com/repos/ckaczor/FeedCenter/releases/latest</Value>
</Setting>
<Setting Name="LastCategoryID" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DefaultUserAgent" Provider="Common.Settings.GenericSettingsProvider" Type="System.String" Scope="User">
<Setting Name="DefaultUserAgent" Provider="ChrisKaczor.GenericSettingsProvider.GenericSettingsProvider" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="DatabaseFile" Type="System.String" Scope="Application">
<Value Profile="(Default)">FeedCenter.realm</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -32,7 +32,7 @@
</application>
</compatibility>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!-- <dependency>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
@@ -43,5 +43,5 @@
language="*"
/>
</dependentAssembly>
</dependency>-->
</dependency>
</asmv1:assembly>

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"FeedCenter": {
"commandName": "Project",
"commandLineArgs": "/debugpath"
}
}
}

View File

@@ -1,21 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FeedCenter
{
using System;
using System.Collections.Generic;
public partial class Setting
{
public string Name { get; set; }
public string Value { get; set; }
public string Version { get; set; }
}
}

View File

@@ -1,61 +1,48 @@
using System;
using System.Collections.Generic;
using FeedCenter.Data;
using FeedCenter.Options;
using System;
using System.Linq;
namespace FeedCenter
namespace FeedCenter;
public static class SettingsStore
{
public static class SettingsStore
public static object OpenDataStore()
{
public static object OpenDataStore()
if (!Database.Exists)
return null;
Database.Load();
return Database.Entities;
}
public static string GetSettingValue(object dataStore, string name, Version _)
{
var entities = (FeedCenterEntities) dataStore;
var setting = entities?.Settings.FirstOrDefault(s => s.Name == name);
return setting?.Value;
}
public static void SetSettingValue(object dataStore, string name, Version _, string value)
{
var entities = (FeedCenterEntities) dataStore;
if (entities == null)
return;
// Try to get the setting from the database that matches the name and version
var setting = entities.Settings.FirstOrDefault(s => s.Name == name);
entities.SaveChanges(() =>
{
var entities = new FeedCenterEntities();
return entities.Database.Exists() ? entities : null;
}
public static void CloseDataStore(object dataStore)
{
var entities = (FeedCenterEntities) dataStore;
if (entities == null)
return;
entities.SaveChanges();
entities.Dispose();
}
public static string GetSettingValue(object dataStore, string name, Version version)
{
var entities = (FeedCenterEntities) dataStore;
if (entities == null)
return null;
var versionString = version.ToString();
var setting = entities.Settings.FirstOrDefault(s => s.Name == name && s.Version == versionString);
return setting?.Value;
}
public static void SetSettingValue(object dataStore, string name, Version version, string value)
{
var entities = (FeedCenterEntities) dataStore;
if (entities == null)
return;
var versionString = version.ToString();
// Try to get the setting from the database that matches the name and version
var setting = entities.Settings.FirstOrDefault(s => s.Name == name && s.Version == versionString);
// If there was no setting we need to create it
if (setting == null)
{
// Create the new setting
setting = new Setting { Name = name, Version = version.ToString() };
setting = new Setting { Name = name };
// Add the setting to the database
entities.Settings.Add(setting);
@@ -63,34 +50,6 @@ namespace FeedCenter
// Set the value into the setting
setting.Value = value;
}
public static List<Version> GetVersionList(object dataStore)
{
var entities = (FeedCenterEntities) dataStore;
// Get a distinct list of version strings
var versions = entities?.Settings.Select(s => s.Version).Distinct().ToList();
// Create a version object for each string and return the list
return versions?.Select(s => new Version(s)).ToList();
}
public static void DeleteSettingsForVersion(object dataStore, Version version)
{
var entities = (FeedCenterEntities) dataStore;
if (entities == null)
return;
var versionString = version.ToString();
// Get all the settings for the current version number
var settings = entities.Settings.Where(setting => setting.Version == versionString);
// Delete each setting
foreach (var setting in settings)
entities.Settings.Remove(setting);
}
});
}
}
}

View File

@@ -1,4 +1,4 @@
using Common.Update;
using ChrisKaczor.ApplicationUpdate;
using FeedCenter.Data;
using FeedCenter.Properties;
using System;
@@ -7,223 +7,193 @@ using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
namespace FeedCenter
namespace FeedCenter;
public partial class SplashWindow : IDisposable
{
public partial class SplashWindow
private class ProgressStep
{
#region Progress step
public delegate bool ProgressCallback();
private class ProgressStep
public readonly string Key;
public readonly string Caption;
public readonly ProgressCallback Callback;
public ProgressStep(string key, string caption, ProgressCallback callback)
{
public delegate bool ProgressCallback();
public readonly string Key;
public readonly string Caption;
public readonly ProgressCallback Callback;
public ProgressStep(string key, string caption, ProgressCallback callback)
{
Key = key;
Caption = caption;
Callback = callback;
}
Key = key;
Caption = caption;
Callback = callback;
}
#endregion
#region Member variables
private readonly List<ProgressStep> _progressSteps = new List<ProgressStep>();
private readonly Dispatcher _dispatcher;
private readonly BackgroundWorker _backgroundWorker;
#endregion
#region Constructor
public SplashWindow()
{
InitializeComponent();
// Store the dispatcher - the background worker has trouble getting the right thread when called from Main
_dispatcher = Dispatcher.CurrentDispatcher;
// Get the version to display
string version = UpdateCheck.LocalVersion.ToString();
// Show the version
VersionLabel.Content = string.Format(Properties.Resources.Version, version);
// Set the starting caption
StatusLabel.Content = Properties.Resources.SplashStarting;
// Build the progress steps
LoadProgressSteps();
// Set the progress bar to the number of steps
ProgressBar.Maximum = _progressSteps.Count;
// Create the worker with progress and cancel
_backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
// Setup the events
_backgroundWorker.DoWork += HandleBackgroundWorkerDoWork;
_backgroundWorker.ProgressChanged += HandleBackgroundWorkerProgressChanged;
_backgroundWorker.RunWorkerCompleted += HandleBackgroundWorkerCompleted;
}
#endregion
#region Form overrides
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
// Start the worker
_backgroundWorker.RunWorkerAsync();
}
#endregion
#region Background worker
private void HandleBackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!_dispatcher.CheckAccess())
{
_dispatcher.Invoke(new EventHandler<ProgressChangedEventArgs>(HandleBackgroundWorkerProgressChanged), sender, e);
return;
}
// Update the progress bar
ProgressBar.Value += e.ProgressPercentage;
// Get the message
var message = (string) e.UserState;
// Update the status label if one was supplied
if (!string.IsNullOrEmpty(message))
StatusLabel.Content = message;
}
private void HandleBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
// Wait just a little bit to make sure the window is up
Thread.Sleep(100);
// Initialize the skip key
string skipKey = string.Empty;
// Loop over all progress steps and execute
foreach (ProgressStep progressStep in _progressSteps)
{
if (progressStep.Key == skipKey)
{
// Update progress with an empty step
UpdateProgress(_backgroundWorker, string.Empty);
}
else
{
// Update progress
UpdateProgress(_backgroundWorker, progressStep.Caption);
// Execute the step and get the result
bool result = progressStep.Callback();
// If the step indicated a skip then set the skip key, otherwise clear it
skipKey = (result ? string.Empty : progressStep.Key);
}
// Stop if cancelled
if (_backgroundWorker.CancellationPending)
return;
}
}
private static void UpdateProgress(BackgroundWorker worker, string progressMessage)
{
// Update the worker
worker.ReportProgress(1, progressMessage);
// Sleep a bit if we actually updated
if (!string.IsNullOrEmpty(progressMessage))
Thread.Sleep(Settings.Default.ProgressSleepInterval);
}
private void HandleBackgroundWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!_dispatcher.CheckAccess())
{
_dispatcher.Invoke(new EventHandler<RunWorkerCompletedEventArgs>(HandleBackgroundWorkerCompleted), sender, e);
return;
}
// Move the progress bar to the max just in case
ProgressBar.Value = ProgressBar.Maximum;
// Close the window
Close();
}
#endregion
#region Progress steps
private static class ProgressKey
{
public const string DatabaseCreate = "CreateDatabase";
public const string DatabaseUpdate = "UpdateDatabase";
public const string DatabaseMaintenance = "MaintainDatabase";
}
private void LoadProgressSteps()
{
// Load the progress steps
_progressSteps.Add(new ProgressStep(ProgressKey.DatabaseCreate, Properties.Resources.SplashCheckingForDatabase, CheckDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.DatabaseCreate, Properties.Resources.SplashCreatingDatabase, CreateDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.DatabaseUpdate, Properties.Resources.SplashUpdatingDatabase, UpdateDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.DatabaseMaintenance, Properties.Resources.SplashMaintainingDatabase, MaintainDatabase));
}
private static bool CheckDatabase()
{
// Get the data directory
string path = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
// Set the path
Database.DatabasePath = System.IO.Path.Combine(path, Settings.Default.DatabaseFile);
// If the database exists then we're done
return !Database.DatabaseExists;
}
private static bool CreateDatabase()
{
// Create the database
Database.CreateDatabase();
return true;
}
private static bool UpdateDatabase()
{
// Update the database
Database.UpdateDatabase();
return true;
}
private static bool MaintainDatabase()
{
// Maintain the database
Database.MaintainDatabase();
return true;
}
#endregion
}
}
private readonly List<ProgressStep> _progressSteps = new();
private readonly Dispatcher _dispatcher;
private readonly BackgroundWorker _backgroundWorker;
public SplashWindow()
{
InitializeComponent();
// Store the dispatcher - the background worker has trouble getting the right thread when called from Main
_dispatcher = Dispatcher.CurrentDispatcher;
VersionLabel.Content = string.Format(Properties.Resources.Version, UpdateCheck.LocalVersion.ToString());
StatusLabel.Content = Properties.Resources.SplashStarting;
LoadProgressSteps();
ProgressBar.Maximum = _progressSteps.Count;
_backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
_backgroundWorker.DoWork += HandleBackgroundWorkerDoWork;
_backgroundWorker.ProgressChanged += HandleBackgroundWorkerProgressChanged;
_backgroundWorker.RunWorkerCompleted += HandleBackgroundWorkerCompleted;
}
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
_backgroundWorker.RunWorkerAsync();
}
private void HandleBackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!_dispatcher.CheckAccess())
{
_dispatcher.Invoke(new EventHandler<ProgressChangedEventArgs>(HandleBackgroundWorkerProgressChanged), sender, e);
return;
}
ProgressBar.Value += e.ProgressPercentage;
// Get the message
var message = (string) e.UserState;
// Update the status label if one was supplied
if (!string.IsNullOrEmpty(message))
StatusLabel.Content = message;
}
private void HandleBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
// Wait just a little bit to make sure the window is up
Thread.Sleep(100);
// Initialize the skip key
var skipKey = string.Empty;
// Loop over all progress steps and execute
foreach (var progressStep in _progressSteps)
{
if (progressStep.Key == skipKey)
{
// Update progress with an empty step
UpdateProgress(_backgroundWorker, string.Empty);
}
else
{
// Update progress
UpdateProgress(_backgroundWorker, progressStep.Caption);
// Execute the step and get the result
var result = progressStep.Callback();
// If the step indicated a skip then set the skip key, otherwise clear it
skipKey = result ? string.Empty : progressStep.Key;
}
// Stop if cancelled
if (_backgroundWorker.CancellationPending)
return;
}
}
private static void UpdateProgress(BackgroundWorker worker, string progressMessage)
{
// Update the worker
worker.ReportProgress(1, progressMessage);
// Sleep a bit if we actually updated
if (!string.IsNullOrEmpty(progressMessage))
Thread.Sleep(Settings.Default.ProgressSleepInterval);
}
private void HandleBackgroundWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!_dispatcher.CheckAccess())
{
_dispatcher.Invoke(new EventHandler<RunWorkerCompletedEventArgs>(HandleBackgroundWorkerCompleted), sender, e);
return;
}
// Move the progress bar to the max just in case
ProgressBar.Value = ProgressBar.Maximum;
Close();
}
private static class ProgressKey
{
public const string ManageLegacyDatabase = "ManageLegacyDatabase";
public const string ManageDatabase = "ManageDatabase";
}
private void LoadProgressSteps()
{
_progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashCheckingForLegacyDatabase, CheckDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashUpdatingLegacyDatabase, UpdateDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashMaintainingLegacyDatabase, MaintainDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashMigratingLegacyDatabase, MigrateDatabase));
_progressSteps.Add(new ProgressStep(ProgressKey.ManageDatabase, Properties.Resources.SplashLoadingDatabase, LoadDatabase));
}
private static bool CheckDatabase()
{
return LegacyDatabase.Exists;
}
private static bool UpdateDatabase()
{
LegacyDatabase.UpdateDatabase();
return true;
}
private static bool MaintainDatabase()
{
LegacyDatabase.MaintainDatabase();
return true;
}
private static bool MigrateDatabase()
{
LegacyDatabase.MigrateDatabase();
return true;
}
private bool LoadDatabase()
{
_dispatcher.Invoke(() =>
{
Database.Load();
Settings.Default.Reload();
});
return true;
}
public void Dispose()
{
_backgroundWorker?.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -1,116 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Windows.Forms;
using System.Collections.Specialized;
using Microsoft.Win32;
namespace FeedCenter
{
class RegistrySettingsProvider : SettingsProvider
{
public RegistrySettingsProvider()
{
}
public override string ApplicationName
{
get { return Application.ProductName; }
set { }
}
public override void Initialize(string name, NameValueCollection col)
{
base.Initialize(this.ApplicationName, col);
}
// SetPropertyValue is invoked when ApplicationSettingsBase.Save is called
// ASB makes sure to pass each provider only the values marked for that provider -
// though in this sample, since the entire settings class was marked with a SettingsProvider
// attribute, all settings in that class map to this provider
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection propvals)
{
// Iterate through the settings to be stored
// Only IsDirty=true properties should be included in propvals
foreach (SettingsPropertyValue propval in propvals)
{
// NOTE: this provider allows setting to both user- and application-scoped
// settings. The default provider for ApplicationSettingsBase -
// LocalFileSettingsProvider - is read-only for application-scoped setting. This
// is an example of a policy that a provider may need to enforce for implementation,
// security or other reasons.
GetRegKey(propval.Property).SetValue(propval.Name, propval.SerializedValue);
}
}
public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection props)
{
// Create new collection of values
SettingsPropertyValueCollection values = new SettingsPropertyValueCollection();
// Iterate through the settings to be retrieved
foreach (SettingsProperty setting in props)
{
SettingsPropertyValue value = new SettingsPropertyValue(setting);
value.IsDirty = false;
value.SerializedValue = GetRegKey(setting).GetValue(setting.Name);
values.Add(value);
}
return values;
}
// Helper method: fetches correct registry subkey.
// HKLM is used for settings marked as application-scoped.
// HKLU is used for settings marked as user-scoped.
private RegistryKey GetRegKey(SettingsProperty prop)
{
RegistryKey regKey;
if (IsUserScoped(prop))
{
regKey = Registry.CurrentUser;
}
else
{
regKey = Registry.LocalMachine;
}
regKey = regKey.CreateSubKey(GetSubKeyPath());
return regKey;
}
// Helper method: walks the "attribute bag" for a given property
// to determine if it is user-scoped or not.
// Note that this provider does not enforce other rules, such as
// - unknown attributes
// - improper attribute combinations (e.g. both user and app - this implementation
// would say true for user-scoped regardless of existence of app-scoped)
private bool IsUserScoped(SettingsProperty prop)
{
foreach (DictionaryEntry d in prop.Attributes)
{
Attribute a = (Attribute) d.Value;
if (a.GetType() == typeof(UserScopedSettingAttribute))
return true;
}
return false;
}
// Builds a key path based on the CompanyName, ProductName, and ProductVersion attributes in
// the AssemblyInfo file (editable directly or within the Project Properties UI)
private string GetSubKeyPath()
{
return "Software\\" + Application.CompanyName + "\\" + Application.ProductName + "\\" + Application.ProductVersion;
}
}
}

View File

@@ -1,11 +1,30 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder x:Name="placeholder" />
<TextBlock FontSize="14"
Margin="2,0"
Foreground="White"
Background="Red"
TextAlignment="Center"
Width="16"
Text="!"
ToolTip="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=placeholder}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
<Setter Property="Margin"
Value="0,0,18,0" />
</Trigger>
</Style.Triggers>
</Style>

View File

@@ -1,35 +1,34 @@
using System;
using FeedCenter.Properties;
using System;
using System.IO;
using System.Reflection;
using FeedCenter.Properties;
namespace FeedCenter
namespace FeedCenter;
public static class SystemConfiguration
{
public static class SystemConfiguration
private static bool UseDebugPath => Environment.CommandLine.IndexOf("/debugPath", StringComparison.InvariantCultureIgnoreCase) != -1;
public static string DataDirectory => UseDebugPath ? Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) : UserSettingsPath;
public static string UserSettingsPath
{
private static bool UseDebugPath => Environment.CommandLine.IndexOf("/debugPath", StringComparison.InvariantCultureIgnoreCase) != -1;
public static string DataDirectory => UseDebugPath ? Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) : UserSettingsPath;
public static string UserSettingsPath
get
{
get
{
// If we're running in debug mode then use a local path for the database and logs
if (UseDebugPath)
return Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// If we're running in debug mode then use a local path for the database and logs
if (UseDebugPath)
return Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// Get the path to the local application data directory
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Resources.ApplicationName);
// Get the path to the local application data directory
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Resources.ApplicationName);
// Make sure it exists - create it if needed
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
// Make sure it exists - create it if needed
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
return path;
}
return path;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More