mirror of
https://github.com/ckaczor/FeedCenter.git
synced 2026-01-14 09:58:53 -05:00
Compare commits
58 Commits
1.0.0.12
...
1.2.0.73-p
| Author | SHA1 | Date | |
|---|---|---|---|
| cdd22c6632 | |||
| b68f794e7d | |||
| 4291d301cc | |||
| af88692aab | |||
| 3b1303c7d4 | |||
| ce1fed6636 | |||
| 40f7dd0fa4 | |||
| 00c2cb7227 | |||
| 4d7d7c041a | |||
| 636c7f9b2a | |||
| 41029671dc | |||
| 4e721efa55 | |||
| f3145d8811 | |||
| 9e2e7aabe8 | |||
| 7ee84f079b | |||
| 8f70003bef | |||
| 38f093dc5c | |||
| e5bdc80d10 | |||
| 260268194a | |||
| 8ecde89be0 | |||
| 845e80577c | |||
| 31a04b13e6 | |||
| 64d893ae0f | |||
| 0ddd38e3f2 | |||
| f5f78c8825 | |||
| 477185341e | |||
| 1db13fbe2e | |||
| c64ec21bd9 | |||
| 45009a31b9 | |||
| 2a7ef356bd | |||
| 105b6e7d30 | |||
| 965f753489 | |||
| 019ebd46be | |||
| 33a3aac48c | |||
| 93a839f7f3 | |||
| 4fc28ed0c1 | |||
| 9e10ac0ab9 | |||
| ac7c20e6bf | |||
| 7638d9c2c7 | |||
| edd01cc052 | |||
| fb777f4837 | |||
| fcc6cc05e9 | |||
| 504cc80470 | |||
| 586a0497d6 | |||
| d6a2fd5a46 | |||
| 5c0c84a068 | |||
| ace251fd4f | |||
| 68aec56824 | |||
| 242663c3e5 | |||
| a81cf5e69f | |||
| 64d0f770ca | |||
| 96d327270f | |||
| 49842a1663 | |||
| e130be35a4 | |||
| eac0313f23 | |||
| 6514f23329 | |||
| b5f570688d | |||
| f480a6c373 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,223 +1,11 @@
|
||||
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
|
||||
{
|
||||
#region Static database settings
|
||||
public static string DatabaseFile { get; set; }
|
||||
public static string DatabasePath { get; set; }
|
||||
|
||||
public static string DatabasePath;
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static bool Exists => File.Exists(DatabaseFile);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
234
Application/Data/LegacyDatabase.cs
Normal file
234
Application/Data/LegacyDatabase.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Application/Data/RealmObservableCollection.cs
Normal file
21
Application/Data/RealmObservableCollection.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Realms;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace FeedCenter.Data;
|
||||
|
||||
public class RealmObservableCollection<T>(Realm realm) : ObservableCollection<T>(realm.All<T>()) where T : IRealmObject
|
||||
{
|
||||
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.
@@ -1,139 +1,118 @@
|
||||
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 FeedCenterEntities()
|
||||
{
|
||||
#region Dispose
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}")
|
||||
{
|
||||
if (disposing)
|
||||
SchemaVersion = 2,
|
||||
MigrationCallback = (migration, oldSchemaVersion) =>
|
||||
{
|
||||
var manager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
|
||||
manager.ObjectStateManagerChanged -= HandleObjectStateManagerObjectStateManagerChanged;
|
||||
_hookedStateManager = false;
|
||||
}
|
||||
Account localAccount;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _hookedStateManager;
|
||||
|
||||
#region All categories
|
||||
|
||||
private ObservableCollection<Category> _allCategories;
|
||||
|
||||
public ObservableCollection<Category> AllCategories
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_allCategories == null)
|
||||
if (oldSchemaVersion == 1)
|
||||
{
|
||||
_allCategories = new ObservableCollection<Category>(Categories);
|
||||
localAccount = Account.CreateDefault();
|
||||
migration.NewRealm.Add(localAccount);
|
||||
}
|
||||
else
|
||||
{
|
||||
localAccount = migration.NewRealm.All<Account>().First(a => a.Type == AccountType.Local);
|
||||
}
|
||||
|
||||
if (!_hookedStateManager)
|
||||
var newVersionCategories = migration.NewRealm.All<Category>();
|
||||
|
||||
foreach (var newVersionCategory in newVersionCategories)
|
||||
{
|
||||
switch (oldSchemaVersion)
|
||||
{
|
||||
var manager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
|
||||
manager.ObjectStateManagerChanged += HandleObjectStateManagerObjectStateManagerChanged;
|
||||
_hookedStateManager = true;
|
||||
case 1:
|
||||
newVersionCategory.Account = localAccount;
|
||||
newVersionCategory.RemoteId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _allCategories;
|
||||
}
|
||||
}
|
||||
var newVersionFeeds = migration.NewRealm.All<Feed>();
|
||||
|
||||
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)
|
||||
foreach (var newVersionFeed in newVersionFeeds)
|
||||
{
|
||||
_allFeeds = new ObservableCollection<Feed>(Feeds);
|
||||
|
||||
if (!_hookedStateManager)
|
||||
switch (oldSchemaVersion)
|
||||
{
|
||||
var manager = ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager;
|
||||
manager.ObjectStateManagerChanged += HandleObjectStateManagerObjectStateManagerChanged;
|
||||
_hookedStateManager = true;
|
||||
case 0:
|
||||
newVersionFeed.UserAgent = null;
|
||||
break;
|
||||
case 1:
|
||||
newVersionFeed.Account = localAccount;
|
||||
newVersionFeed.RemoteId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _allFeeds;
|
||||
var newVersionFeedItems = migration.NewRealm.All<FeedItem>();
|
||||
|
||||
foreach (var newVersionFeedItem in newVersionFeedItems)
|
||||
{
|
||||
switch (oldSchemaVersion)
|
||||
{
|
||||
case 1:
|
||||
newVersionFeedItem.RemoteId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endregion
|
||||
RealmInstance = Realm.GetInstance(realmConfiguration);
|
||||
|
||||
#region Object state manager
|
||||
Accounts = new RealmObservableCollection<Account>(RealmInstance);
|
||||
Settings = new RealmObservableCollection<Setting>(RealmInstance);
|
||||
Feeds = new RealmObservableCollection<Feed>(RealmInstance);
|
||||
Categories = new RealmObservableCollection<Category>(RealmInstance);
|
||||
|
||||
void HandleObjectStateManagerObjectStateManagerChanged(object sender, CollectionChangeEventArgs e)
|
||||
if (!Accounts.Any())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
RealmInstance.Write(() => Accounts.Add(Account.CreateDefault()));
|
||||
}
|
||||
|
||||
#endregion
|
||||
if (!Categories.Any())
|
||||
{
|
||||
var localAccount = Accounts.First(a => a.Type == AccountType.Local);
|
||||
RealmInstance.Write(() => Categories.Add(Category.CreateDefault(localAccount)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RealmObservableCollection<Category> Categories { get; }
|
||||
|
||||
public Category DefaultCategory
|
||||
{
|
||||
get { return Categories.First(c => c.IsDefault); }
|
||||
}
|
||||
|
||||
public Account LocalAccount
|
||||
{
|
||||
get { return Accounts.First(a => a.Type == AccountType.Local); }
|
||||
}
|
||||
|
||||
public RealmObservableCollection<Feed> Feeds { get; }
|
||||
public RealmObservableCollection<Account> Accounts { get; }
|
||||
private Realm RealmInstance { get; }
|
||||
public RealmObservableCollection<Setting> Settings { get; }
|
||||
|
||||
public void SaveChanges(Action action)
|
||||
{
|
||||
RealmInstance.Write(action);
|
||||
}
|
||||
|
||||
public Transaction BeginTransaction()
|
||||
{
|
||||
return RealmInstance.BeginWrite();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,473 +1,107 @@
|
||||
<?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>net9.0-windows8.0</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.7" />
|
||||
<PackageReference Include="ChrisKaczor.FeverClient" Version="1.0.1" />
|
||||
<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.6" />
|
||||
<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.4" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="DebounceThrottle" Version="3.0.1" />
|
||||
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.3" />
|
||||
<PackageReference Include="HtmlTextWriter" Version="3.0.2" />
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.11" />
|
||||
<PackageReference Include="Microsoft.SqlServer.Compact" Version="4.0.8876.1" GeneratePathProperty="true">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.Compatibility" Version="9.0.9" />
|
||||
<PackageReference Include="NameBasedGrid" Version="1.0.0">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.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 "$(PkgMicrosoft_SqlServer_Compact)\NativeBinaries\x86\*.*" "$(PublishDir)x86"" />
|
||||
</Target>
|
||||
<Target Name="AddSqlServerCompact_x64" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)' == 'win-x64'">
|
||||
<Exec Command="xcopy /s /y /i "$(PkgMicrosoft_SqlServer_Compact)\NativeBinaries\amd64\*.*" "$(PublishDir)amd64"" />
|
||||
</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>
|
||||
49
Application/FeedCenter.sln
Normal file
49
Application/FeedCenter.sln
Normal file
@@ -0,0 +1,49 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11018.127
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeedCenter", "FeedCenter.csproj", "{BD3D12F2-DE23-4466-83B1-1EB617A877A4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{1462AAAD-B01B-4FF6-9B9F-595D239C9D1E}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
..\.gitignore = ..\.gitignore
|
||||
..\appveyor.yml = ..\appveyor.yml
|
||||
..\LICENSE.md = ..\LICENSE.md
|
||||
..\README.md = ..\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|Mixed Platforms = Debug|Mixed Platforms
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|Mixed Platforms = Release|Mixed Platforms
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x64.Build.0 = Debug|x64
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x86.Build.0 = Debug|x86
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x64.ActiveCfg = Release|x86
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B1D182A0-263B-4AB8-8413-56303DBD4CCC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
7
Application/FeedCenter.sln.DotSettings
Normal file
7
Application/FeedCenter.sln.DotSettings
Normal file
@@ -0,0 +1,7 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kaczor/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,131 +1,140 @@
|
||||
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.Options;
|
||||
using FeedCenter.Properties;
|
||||
|
||||
namespace FeedCenter
|
||||
namespace FeedCenter;
|
||||
|
||||
public partial class FeedErrorWindow
|
||||
{
|
||||
public partial class FeedErrorWindow
|
||||
private CollectionViewSource _collectionViewSource;
|
||||
private readonly FeedCenterEntities _entities = new();
|
||||
|
||||
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 = _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(_entities);
|
||||
|
||||
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;
|
||||
|
||||
_entities.SaveChanges(() => _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));
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,143 +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 = GetAttribute(node, "rel");
|
||||
|
||||
if (relNode != null)
|
||||
rel = relNode.InnerText;
|
||||
|
||||
if (string.IsNullOrEmpty(rel) || rel == "alternate")
|
||||
Feed.Link = GetAttribute(node, "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 = GetAttribute(childNode, "rel");
|
||||
XmlNode relNode = GetAttribute(node, "rel");
|
||||
|
||||
if (relNode != null)
|
||||
rel = relNode.InnerText.Trim();
|
||||
rel = relNode.InnerText;
|
||||
|
||||
if (string.IsNullOrEmpty(rel) || rel == "alternate")
|
||||
{
|
||||
var link = GetAttribute(childNode, "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 feedItem;
|
||||
return FeedReadResult.Success;
|
||||
}
|
||||
|
||||
private static XmlAttribute GetAttribute(XmlNode node, string attributeName)
|
||||
catch (XmlException xmlException)
|
||||
{
|
||||
if (node?.Attributes == null)
|
||||
return null;
|
||||
Log.Logger.Error(xmlException, "Exception: {0}", feedText);
|
||||
|
||||
return node.Attributes[attributeName, node.NamespaceURI] ?? node.Attributes[attributeName];
|
||||
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];
|
||||
}
|
||||
}
|
||||
7
Application/FeedParsers/FeedParseError.cs
Normal file
7
Application/FeedParsers/FeedParseError.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FeedCenter.FeedParsers;
|
||||
|
||||
internal enum FeedParseError
|
||||
{
|
||||
Unknown = 0,
|
||||
InvalidXml = 1
|
||||
}
|
||||
13
Application/FeedParsers/FeedParseException.cs
Normal file
13
Application/FeedParsers/FeedParseException.cs
Normal 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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Application/FeedParsers/InvalidFeedFormatException.cs
Normal file
12
Application/FeedParsers/InvalidFeedFormatException.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace FeedCenter.FeedParsers;
|
||||
|
||||
[Serializable]
|
||||
internal class InvalidFeedFormatException : ApplicationException
|
||||
{
|
||||
internal InvalidFeedFormatException(Exception exception)
|
||||
: base(string.Empty, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
203
Application/Feeds/Account.cs
Normal file
203
Application/Feeds/Account.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using Realms;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
public class Account : RealmObject, INotifyDataErrorInfo
|
||||
{
|
||||
public const string DefaultName = "< Local >";
|
||||
|
||||
private readonly DataErrorDictionary _dataErrorDictionary;
|
||||
|
||||
public Account() : this(AccountType.Local)
|
||||
{
|
||||
}
|
||||
|
||||
public Account(AccountType type)
|
||||
{
|
||||
Type = type;
|
||||
|
||||
_dataErrorDictionary = new DataErrorDictionary();
|
||||
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
|
||||
}
|
||||
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
public AccountType Type
|
||||
{
|
||||
get => Enum.TryParse(TypeRaw, out AccountType result) ? result : AccountType.Local;
|
||||
set => TypeRaw = value.ToString();
|
||||
}
|
||||
|
||||
public bool SupportsFeedEdit => Type switch
|
||||
{
|
||||
AccountType.Fever => false,
|
||||
AccountType.GoogleReader => false,
|
||||
AccountType.Local => true,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
public bool SupportsFeedDelete => Type switch
|
||||
{
|
||||
AccountType.Fever => false,
|
||||
AccountType.GoogleReader => false,
|
||||
AccountType.Local => true,
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
private string TypeRaw { get; set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => RawName;
|
||||
set
|
||||
{
|
||||
RawName = value;
|
||||
|
||||
ValidateString(nameof(Name), RawName);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[MapTo("Name")]
|
||||
private string RawName { get; set; } = string.Empty;
|
||||
|
||||
public string Url
|
||||
{
|
||||
get => RawUrl;
|
||||
set
|
||||
{
|
||||
RawUrl = value;
|
||||
|
||||
ValidateString(nameof(Url), RawUrl);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[MapTo("Url")]
|
||||
public string RawUrl { get; set; }
|
||||
|
||||
public string Username
|
||||
{
|
||||
get => RawUsername;
|
||||
set
|
||||
{
|
||||
RawUsername = value;
|
||||
|
||||
if (!Authenticate)
|
||||
{
|
||||
_dataErrorDictionary.ClearErrors(nameof(Username));
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateString(nameof(Username), RawUsername);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[MapTo("Username")]
|
||||
public string RawUsername { get; set; }
|
||||
|
||||
public string Password
|
||||
{
|
||||
get => RawPassword;
|
||||
set
|
||||
{
|
||||
RawPassword = value;
|
||||
|
||||
if (!Authenticate)
|
||||
{
|
||||
_dataErrorDictionary.ClearErrors(nameof(Password));
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateString(nameof(Password), RawPassword);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[MapTo("Password")]
|
||||
public string RawPassword { get; set; }
|
||||
|
||||
public bool Authenticate { get; set; }
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public int CheckInterval { get; set; } = 60;
|
||||
|
||||
public DateTimeOffset LastChecked { get; set; }
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private void ValidateString(string propertyName, string value)
|
||||
{
|
||||
_dataErrorDictionary.ClearErrors(propertyName);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
_dataErrorDictionary.AddError(propertyName, $"{propertyName} cannot be empty");
|
||||
}
|
||||
|
||||
public static Account CreateDefault()
|
||||
{
|
||||
return new Account { Name = DefaultName, Type = AccountType.Local };
|
||||
}
|
||||
|
||||
public int GetProgressSteps(FeedCenterEntities entities)
|
||||
{
|
||||
var progressSteps = Type switch
|
||||
{
|
||||
// Delegate to the right reader based on the account type
|
||||
AccountType.Fever => new FeverReader().GetProgressSteps(entities),
|
||||
AccountType.GoogleReader => new GoogleReaderReader().GetProgressSteps(entities),
|
||||
AccountType.Local => new LocalReader().GetProgressSteps(entities),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
return progressSteps;
|
||||
}
|
||||
|
||||
public AccountReadResult Read(AccountReadInput accountReadInput)
|
||||
{
|
||||
// If not enabled then do nothing
|
||||
if (!Enabled)
|
||||
return AccountReadResult.NotEnabled;
|
||||
|
||||
// Check if we're forcing a read
|
||||
if (!accountReadInput.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 AccountReadResult.NotDue;
|
||||
}
|
||||
|
||||
var accountReadResult = Type switch
|
||||
{
|
||||
// Delegate to the right reader based on the account type
|
||||
AccountType.Fever => new FeverReader().Read(this, accountReadInput),
|
||||
AccountType.GoogleReader => new GoogleReaderReader().Read(this, accountReadInput),
|
||||
AccountType.Local => new LocalReader().Read(this, accountReadInput),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
return accountReadResult;
|
||||
}
|
||||
}
|
||||
11
Application/Feeds/AccountReadInput.cs
Normal file
11
Application/Feeds/AccountReadInput.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
public class AccountReadInput(FeedCenterEntities entities, Guid? feedId, bool forceRead, Action incrementProgress)
|
||||
{
|
||||
public FeedCenterEntities Entities { get; set; } = entities;
|
||||
public Guid? FeedId { get; set; } = feedId;
|
||||
public bool ForceRead { get; set; } = forceRead;
|
||||
public Action IncrementProgress { get; private set; } = incrementProgress ?? throw new ArgumentNullException(nameof(incrementProgress));
|
||||
}
|
||||
8
Application/Feeds/AccountReadResult.cs
Normal file
8
Application/Feeds/AccountReadResult.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public enum AccountReadResult
|
||||
{
|
||||
Success,
|
||||
NotDue,
|
||||
NotEnabled
|
||||
}
|
||||
8
Application/Feeds/AccountType.cs
Normal file
8
Application/Feeds/AccountType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public enum AccountType
|
||||
{
|
||||
Local,
|
||||
Fever,
|
||||
GoogleReader
|
||||
}
|
||||
@@ -1,17 +1,75 @@
|
||||
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 string RemoteId { get; set; }
|
||||
|
||||
public Account Account { get; set; }
|
||||
|
||||
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(Account account)
|
||||
{
|
||||
return new Category { Name = DefaultName, IsDefault = true, Account = account };
|
||||
}
|
||||
|
||||
private void ValidateName()
|
||||
{
|
||||
_dataErrorDictionary.ClearErrors(nameof(Name));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
_dataErrorDictionary.AddError(nameof(Name), "Name cannot be empty");
|
||||
}
|
||||
}
|
||||
42
Application/Feeds/DataErrorDictionary.cs
Normal file
42
Application/Feeds/DataErrorDictionary.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,369 +1,476 @@
|
||||
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.Debug;
|
||||
using Common.Update;
|
||||
using Common.Xml;
|
||||
using FeedCenter.Data;
|
||||
using ChrisKaczor.ApplicationUpdate;
|
||||
using FeedCenter.FeedParsers;
|
||||
using FeedCenter.Properties;
|
||||
using FeedCenter.Xml;
|
||||
using JetBrains.Annotations;
|
||||
using Realms;
|
||||
using Serilog;
|
||||
|
||||
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
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string RemoteId { get; set; }
|
||||
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;
|
||||
public Account Account { 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
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public string LastReadResultDescription
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
// Cast the last read result to the proper enum
|
||||
var lastReadResult = LastReadResult;
|
||||
// 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}";
|
||||
// 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);
|
||||
// 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();
|
||||
}
|
||||
// Return the value or just the enum value if not found
|
||||
return resourceValue ?? lastReadResult.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Feed Create(FeedCenterEntities database)
|
||||
private string LastReadResultRaw { get; set; }
|
||||
|
||||
public DateTimeOffset LastUpdated { get; set; }
|
||||
public string Link { get; set; }
|
||||
|
||||
public MultipleOpenAction MultipleOpenAction
|
||||
{
|
||||
get => Enum.TryParse(MultipleOpenActionRaw, out MultipleOpenAction result) ? result : MultipleOpenAction.IndividualPages;
|
||||
set => MultipleOpenActionRaw = value.ToString();
|
||||
}
|
||||
|
||||
private string MultipleOpenActionRaw { get; set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => RawName;
|
||||
set
|
||||
{
|
||||
return new Feed { ID = Guid.NewGuid(), CategoryID = database.DefaultCategory.ID };
|
||||
RawName = value;
|
||||
|
||||
ValidateString(nameof(Name), RawName);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#region Reading
|
||||
|
||||
public FeedReadResult Read(FeedCenterEntities database, bool forceRead = false)
|
||||
public string Password
|
||||
{
|
||||
get => RawPassword;
|
||||
set
|
||||
{
|
||||
Tracer.WriteLine("Reading feed: {0}", Source);
|
||||
Tracer.IncrementIndentLevel();
|
||||
RawPassword = value;
|
||||
|
||||
var result = ReadFeed(database, forceRead);
|
||||
|
||||
// Handle the result
|
||||
switch (result)
|
||||
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 == 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("Password")]
|
||||
public string RawPassword { get; set; }
|
||||
|
||||
[MapTo("Source")]
|
||||
private string RawSource { get; set; } = string.Empty;
|
||||
|
||||
[MapTo("Username")]
|
||||
public string RawUsername { get; set; }
|
||||
|
||||
public string Source
|
||||
{
|
||||
get => RawSource;
|
||||
set
|
||||
{
|
||||
RawSource = value;
|
||||
|
||||
ValidateString(nameof(Source), RawSource);
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string UserAgent { 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(FeedCenterEntities entities)
|
||||
{
|
||||
return new Feed { Id = Guid.NewGuid(), CategoryId = entities.DefaultCategory.Id, Account = entities.LocalAccount };
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
// Reset status to success
|
||||
LastReadResult = FeedReadResult.Success;
|
||||
|
||||
// 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 string GetUserAgent()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(UserAgent))
|
||||
return UserAgent;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Settings.Default.DefaultUserAgent))
|
||||
return Settings.Default.DefaultUserAgent;
|
||||
|
||||
return "FeedCenter/" + UpdateCheck.LocalVersion;
|
||||
}
|
||||
|
||||
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 | DecompressionMethods.Brotli,
|
||||
AllowAutoRedirect = true
|
||||
};
|
||||
|
||||
_httpClient = new HttpClient(clientHandler)
|
||||
{
|
||||
// Set a timeout
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
_httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br");
|
||||
_httpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.9");
|
||||
_httpClient.DefaultRequestHeaders.CacheControl = CacheControlHeaderValue.Parse("max-age=0");
|
||||
}
|
||||
|
||||
// Set a user agent string
|
||||
var userAgent = GetUserAgent();
|
||||
_httpClient.DefaultRequestHeaders.UserAgent.Clear();
|
||||
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
|
||||
|
||||
// If we need to authenticate then set the credentials
|
||||
_httpClient.DefaultRequestHeaders.Authorization = Authenticate ? new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}"))) : null;
|
||||
|
||||
_httpClient.DefaultRequestHeaders.IfModifiedSince = LastChecked;
|
||||
|
||||
// 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(" ", " ");
|
||||
|
||||
// Find ampersands that aren't properly escaped and replace them with escaped versions
|
||||
var r = UnescapedAmpersandRegex();
|
||||
feedText = r.Replace(feedText, "&");
|
||||
|
||||
return Tuple.Create(FeedReadResult.Success, feedText);
|
||||
}
|
||||
catch (HttpRequestException httpRequestException)
|
||||
{
|
||||
if (httpRequestException.StatusCode == HttpStatusCode.NotModified)
|
||||
{
|
||||
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
|
||||
}
|
||||
|
||||
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.TooManyRequests:
|
||||
return Tuple.Create(FeedReadResult.TooManyRequests, string.Empty);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Read the feed text
|
||||
var retrieveResult = RetrieveFeed();
|
||||
|
||||
if (retrieveResult.Item1 != FeedReadResult.Success)
|
||||
// We're checking it now so update the time
|
||||
LastChecked = DateTimeOffset.Now;
|
||||
|
||||
// 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 request = WebRequest.Create(new Uri(Source));
|
||||
|
||||
// If this is an http request set some special properties
|
||||
if (request is HttpWebRequest webRequest)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// Make sure the service point closes the connection right away
|
||||
webRequest.ServicePoint.ConnectionLeaseTimeout = 0;
|
||||
|
||||
// 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(Settings.Default.DefaultUserAgent))
|
||||
webRequest.UserAgent = "FeedCenter/" + UpdateCheck.LocalVersion;
|
||||
else
|
||||
webRequest.UserAgent = Settings.Default.DefaultUserAgent;
|
||||
}
|
||||
|
||||
// Set the default encoding
|
||||
var encoding = Encoding.UTF8;
|
||||
|
||||
// Attempt to get the response
|
||||
using (var response = (HttpWebResponse) request.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
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
{
|
||||
if (responseStream == null)
|
||||
return Tuple.Create(FeedReadResult.NoResponse, string.Empty);
|
||||
|
||||
// Create the text reader
|
||||
using (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(" ", " ");
|
||||
|
||||
// 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, "&");
|
||||
|
||||
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;
|
||||
|
||||
if (webException.Response is HttpWebResponse errorResponse)
|
||||
{
|
||||
switch (errorResponse.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.InternalServerError:
|
||||
|
||||
return Tuple.Create(FeedReadResult.ServerError, string.Empty);
|
||||
|
||||
case HttpStatusCode.NotModified:
|
||||
|
||||
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
|
||||
|
||||
case HttpStatusCode.NotFound:
|
||||
|
||||
return Tuple.Create(FeedReadResult.NotFound, string.Empty);
|
||||
|
||||
case HttpStatusCode.Unauthorized:
|
||||
case HttpStatusCode.Forbidden:
|
||||
|
||||
return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
switch (webException.Status)
|
||||
{
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
return FeedReadResult.UnknownError;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[GeneratedRegex("&(?!(?:[a-z]+|#[0-9]+|#x[0-9a-f]+);)")]
|
||||
private static partial Regex UnescapedAmpersandRegex();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,112 @@
|
||||
using System;
|
||||
using FeedCenter.Options;
|
||||
using Realms;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FeedCenter
|
||||
namespace FeedCenter;
|
||||
|
||||
public partial class FeedItem : RealmObject
|
||||
{
|
||||
public partial class FeedItem
|
||||
[PrimaryKey]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public bool BeenRead { get; set; }
|
||||
public string Description { get; set; }
|
||||
public Guid FeedId { get; set; }
|
||||
public string Guid { 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 string RemoteId { 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;
|
||||
}
|
||||
|
||||
public async Task MarkAsRead(FeedCenterEntities entities)
|
||||
{
|
||||
var feed = entities.Feeds.FirstOrDefault(f => f.Id == FeedId);
|
||||
|
||||
entities.SaveChanges(() =>
|
||||
{
|
||||
BeenRead = true;
|
||||
New = false;
|
||||
});
|
||||
|
||||
if (feed == null || feed.Account.Type == AccountType.Local)
|
||||
return;
|
||||
|
||||
switch (feed.Account.Type)
|
||||
{
|
||||
case AccountType.Fever:
|
||||
// Delegate to the right reader based on the account type
|
||||
await FeverReader.MarkFeedItemRead(feed.Account, RemoteId);
|
||||
|
||||
break;
|
||||
case AccountType.Local:
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex("\\n")]
|
||||
private static partial Regex NewlineRegex();
|
||||
|
||||
[GeneratedRegex("[ ]{2,}")]
|
||||
private static partial Regex MultipleSpaceRegex();
|
||||
|
||||
[GeneratedRegex("\\t")]
|
||||
private static partial Regex TabRegex();
|
||||
}
|
||||
20
Application/Feeds/FeedReadResult.cs
Normal file
20
Application/Feeds/FeedReadResult.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public enum FeedReadResult
|
||||
{
|
||||
Success,
|
||||
NotModified,
|
||||
NotDue,
|
||||
UnknownError,
|
||||
InvalidXml,
|
||||
NotEnabled,
|
||||
Unauthorized,
|
||||
NoResponse,
|
||||
NotFound,
|
||||
Timeout,
|
||||
ConnectionFailed,
|
||||
ServerError,
|
||||
Moved,
|
||||
TemporarilyUnavailable,
|
||||
TooManyRequests
|
||||
}
|
||||
9
Application/Feeds/FeedType.cs
Normal file
9
Application/Feeds/FeedType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public enum FeedType
|
||||
{
|
||||
Unknown,
|
||||
Rss,
|
||||
Rdf,
|
||||
Atom
|
||||
}
|
||||
145
Application/Feeds/FeverReader.cs
Normal file
145
Application/Feeds/FeverReader.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ChrisKaczor.FeverClient;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
internal class FeverReader : IAccountReader
|
||||
{
|
||||
public int GetProgressSteps(FeedCenterEntities entities)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
|
||||
{
|
||||
var checkTime = DateTimeOffset.UtcNow;
|
||||
|
||||
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
|
||||
|
||||
var feverClient = new FeverClient(account.Url, apiKey);
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var feverFeeds = feverClient.GetFeeds().Result.ToList();
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var allFeverFeedItems = feverClient.GetAllFeedItems().Result.ToList();
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var transaction = accountReadInput.Entities.BeginTransaction();
|
||||
|
||||
foreach (var feverFeed in feverFeeds)
|
||||
{
|
||||
var feed = accountReadInput.Entities.Feeds.FirstOrDefault(f => f.RemoteId == feverFeed.Id.ToString() && f.Account.Id == account.Id);
|
||||
|
||||
if (feed == null)
|
||||
{
|
||||
feed = new Feed
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RemoteId = feverFeed.Id.ToString(),
|
||||
Title = feverFeed.Title,
|
||||
Source = feverFeed.Url,
|
||||
Link = feverFeed.SiteUrl,
|
||||
Account = account,
|
||||
Name = feverFeed.Title,
|
||||
CategoryId = accountReadInput.Entities.DefaultCategory.Id,
|
||||
Enabled = true,
|
||||
CheckInterval = 0,
|
||||
};
|
||||
|
||||
accountReadInput.Entities.Feeds.Add(feed);
|
||||
}
|
||||
|
||||
feed.Name = feverFeed.Title;
|
||||
feed.Title = feverFeed.Title;
|
||||
feed.Link = feverFeed.SiteUrl;
|
||||
feed.Source = feverFeed.Url;
|
||||
feed.LastReadResult = FeedReadResult.Success;
|
||||
feed.LastChecked = checkTime;
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var feverFeedItems = allFeverFeedItems
|
||||
.Where(f => f.FeedId == feverFeed.Id)
|
||||
.OrderByDescending(fi => fi.CreatedOnTime).ToList();
|
||||
|
||||
var sequence = 1;
|
||||
|
||||
foreach (var feverFeedItem in feverFeedItems)
|
||||
{
|
||||
var feedItem = feed.Items.FirstOrDefault(f => f.RemoteId == feverFeedItem.Id.ToString());
|
||||
|
||||
if (feedItem == null)
|
||||
{
|
||||
feedItem = new FeedItem
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RemoteId = feverFeedItem.Id.ToString(),
|
||||
Title = feverFeedItem.Title,
|
||||
Link = feverFeedItem.Url,
|
||||
Description = feverFeedItem.Html,
|
||||
BeenRead = feverFeedItem.IsRead,
|
||||
FeedId = feed.Id,
|
||||
Guid = Guid.NewGuid().ToString(),
|
||||
Sequence = sequence++,
|
||||
};
|
||||
|
||||
feed.Items.Add(feedItem);
|
||||
}
|
||||
|
||||
feedItem.LastFound = checkTime;
|
||||
feedItem.BeenRead = feverFeedItem.IsRead;
|
||||
feedItem.Sequence = sequence++;
|
||||
}
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var feedItemsNotSeen = feed.Items.Where(fi => fi.LastFound != checkTime).ToList();
|
||||
|
||||
foreach (var feedItemNotSeen in feedItemsNotSeen)
|
||||
{
|
||||
feed.Items.Remove(feedItemNotSeen);
|
||||
}
|
||||
}
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
var feedsNotSeen = accountReadInput.Entities.Feeds.Where(f => f.Account.Id == account.Id && f.LastChecked != checkTime).ToList();
|
||||
|
||||
foreach (var feedNotSeen in feedsNotSeen)
|
||||
{
|
||||
accountReadInput.Entities.Feeds.Remove(feedNotSeen);
|
||||
}
|
||||
|
||||
account.LastChecked = checkTime;
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
return AccountReadResult.Success;
|
||||
}
|
||||
|
||||
public static async Task MarkFeedItemRead(Account account, string feedItemId)
|
||||
{
|
||||
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
|
||||
|
||||
var feverClient = new FeverClient(account.Url, apiKey);
|
||||
|
||||
await feverClient.MarkFeedItemAsRead(int.Parse(feedItemId));
|
||||
}
|
||||
|
||||
private static string GetApiKey(Account account)
|
||||
{
|
||||
var input = $"{account.Username}:{account.Password}";
|
||||
var hash = MD5.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
144
Application/Feeds/GoogleReaderReader.cs
Normal file
144
Application/Feeds/GoogleReaderReader.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
internal class GoogleReaderReader : IAccountReader
|
||||
{
|
||||
public int GetProgressSteps(FeedCenterEntities entities)
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
|
||||
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
|
||||
{
|
||||
var checkTime = DateTimeOffset.UtcNow;
|
||||
|
||||
//var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
|
||||
|
||||
//var feverClient = new FeverClient.FeverClient(account.Url, apiKey);
|
||||
|
||||
//accountReadInput.IncrementProgress();
|
||||
|
||||
//var feverFeeds = feverClient.GetFeeds().Result.ToList();
|
||||
|
||||
//accountReadInput.IncrementProgress();
|
||||
|
||||
//var allFeverFeedItems = feverClient.GetAllFeedItems().Result.ToList();
|
||||
|
||||
//accountReadInput.IncrementProgress();
|
||||
|
||||
var transaction = accountReadInput.Entities.BeginTransaction();
|
||||
|
||||
//foreach (var feverFeed in feverFeeds)
|
||||
//{
|
||||
// var feed = accountReadInput.Entities.Feeds.FirstOrDefault(f => f.RemoteId == feverFeed.Id.ToString() && f.Account.Id == account.Id);
|
||||
|
||||
// if (feed == null)
|
||||
// {
|
||||
// feed = new Feed
|
||||
// {
|
||||
// Id = Guid.NewGuid(),
|
||||
// RemoteId = feverFeed.Id.ToString(),
|
||||
// Title = feverFeed.Title,
|
||||
// Source = feverFeed.Url,
|
||||
// Link = feverFeed.SiteUrl,
|
||||
// Account = account,
|
||||
// Name = feverFeed.Title,
|
||||
// CategoryId = accountReadInput.Entities.DefaultCategory.Id,
|
||||
// Enabled = true,
|
||||
// CheckInterval = 0,
|
||||
// };
|
||||
|
||||
// accountReadInput.Entities.Feeds.Add(feed);
|
||||
// }
|
||||
|
||||
// feed.Name = feverFeed.Title;
|
||||
// feed.Title = feverFeed.Title;
|
||||
// feed.Link = feverFeed.SiteUrl;
|
||||
// feed.Source = feverFeed.Url;
|
||||
// feed.LastReadResult = FeedReadResult.Success;
|
||||
// feed.LastChecked = checkTime;
|
||||
|
||||
// accountReadInput.IncrementProgress();
|
||||
|
||||
// var feverFeedItems = allFeverFeedItems
|
||||
// .Where(f => f.FeedId == feverFeed.Id)
|
||||
// .OrderByDescending(fi => fi.CreatedOnTime).ToList();
|
||||
|
||||
// var sequence = 1;
|
||||
|
||||
// foreach (var feverFeedItem in feverFeedItems)
|
||||
// {
|
||||
// var feedItem = feed.Items.FirstOrDefault(f => f.RemoteId == feverFeedItem.Id.ToString());
|
||||
|
||||
// if (feedItem == null)
|
||||
// {
|
||||
// feedItem = new FeedItem
|
||||
// {
|
||||
// Id = Guid.NewGuid(),
|
||||
// RemoteId = feverFeedItem.Id.ToString(),
|
||||
// Title = feverFeedItem.Title,
|
||||
// Link = feverFeedItem.Url,
|
||||
// Description = feverFeedItem.Html,
|
||||
// BeenRead = feverFeedItem.IsRead,
|
||||
// FeedId = feed.Id,
|
||||
// Guid = Guid.NewGuid().ToString(),
|
||||
// Sequence = sequence++,
|
||||
// };
|
||||
|
||||
// feed.Items.Add(feedItem);
|
||||
// }
|
||||
|
||||
// feedItem.LastFound = checkTime;
|
||||
// feedItem.BeenRead = feverFeedItem.IsRead;
|
||||
// feedItem.Sequence = sequence++;
|
||||
// }
|
||||
|
||||
// accountReadInput.IncrementProgress();
|
||||
|
||||
// var feedItemsNotSeen = feed.Items.Where(fi => fi.LastFound != checkTime).ToList();
|
||||
|
||||
// foreach (var feedItemNotSeen in feedItemsNotSeen)
|
||||
// {
|
||||
// feed.Items.Remove(feedItemNotSeen);
|
||||
// }
|
||||
//}
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
//var feedsNotSeen = accountReadInput.Entities.Feeds.Where(f => f.Account.Id == account.Id && f.LastChecked != checkTime).ToList();
|
||||
|
||||
//foreach (var feedNotSeen in feedsNotSeen)
|
||||
//{
|
||||
// accountReadInput.Entities.Feeds.Remove(feedNotSeen);
|
||||
//}
|
||||
|
||||
account.LastChecked = checkTime;
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
|
||||
return AccountReadResult.Success;
|
||||
}
|
||||
|
||||
public static async Task MarkFeedItemRead(Account account, string feedItemId)
|
||||
{
|
||||
//var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
|
||||
|
||||
//var feverClient = new FeverClient.FeverClient(account.Url, apiKey);
|
||||
|
||||
//await feverClient.MarkFeedItemAsRead(int.Parse(feedItemId));
|
||||
}
|
||||
|
||||
//private static string GetApiKey(Account account)
|
||||
//{
|
||||
// var input = $"{account.Username}:{account.Password}";
|
||||
// var hash = MD5.HashData(Encoding.UTF8.GetBytes(input));
|
||||
// return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
//}
|
||||
}
|
||||
7
Application/Feeds/IAccountReader.cs
Normal file
7
Application/Feeds/IAccountReader.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public interface IAccountReader
|
||||
{
|
||||
public int GetProgressSteps(FeedCenterEntities entities);
|
||||
public AccountReadResult Read(Account account, AccountReadInput accountReadInput);
|
||||
}
|
||||
42
Application/Feeds/LocalReader.cs
Normal file
42
Application/Feeds/LocalReader.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FeedCenter;
|
||||
|
||||
public class LocalReader : IAccountReader
|
||||
{
|
||||
public int GetProgressSteps(FeedCenterEntities entities)
|
||||
{
|
||||
var enabledFeedCount = entities.Feeds.Count(f => f.Account.Type == AccountType.Local && f.Enabled);
|
||||
|
||||
return enabledFeedCount;
|
||||
}
|
||||
|
||||
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
|
||||
{
|
||||
var checkTime = DateTimeOffset.UtcNow;
|
||||
|
||||
// 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 (accountReadInput.FeedId != null)
|
||||
feedsToRead.Add(accountReadInput.Entities.Feeds.First(feed => feed.Id == accountReadInput.FeedId));
|
||||
else
|
||||
feedsToRead.AddRange(accountReadInput.Entities.Feeds.Where(f => f.Account.Type == AccountType.Local));
|
||||
|
||||
// Loop over each feed and read it
|
||||
foreach (var feed in feedsToRead)
|
||||
{
|
||||
// Read the feed
|
||||
accountReadInput.Entities.SaveChanges(() => feed.Read(accountReadInput.ForceRead));
|
||||
|
||||
accountReadInput.IncrementProgress();
|
||||
}
|
||||
|
||||
accountReadInput.Entities.SaveChanges(() => account.LastChecked = checkTime);
|
||||
|
||||
return AccountReadResult.Success;
|
||||
}
|
||||
}
|
||||
7
Application/Feeds/MultipleOpenAction.cs
Normal file
7
Application/Feeds/MultipleOpenAction.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FeedCenter;
|
||||
|
||||
public enum MultipleOpenAction
|
||||
{
|
||||
IndividualPages,
|
||||
SinglePage
|
||||
}
|
||||
3
Application/FodyWeavers.xml
Normal file
3
Application/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Realm />
|
||||
</Weavers>
|
||||
28
Application/FodyWeavers.xsd
Normal file
28
Application/FodyWeavers.xsd
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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: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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +1,113 @@
|
||||
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(_database);
|
||||
feed.Source = feedUrl;
|
||||
|
||||
// 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 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Feed read failed - create a new feed window
|
||||
var feedForm = new FeedWindow(_database);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +1,131 @@
|
||||
using System;
|
||||
using ChrisKaczor.InstalledBrowsers;
|
||||
using FeedCenter.Properties;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
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 async 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;
|
||||
|
||||
// Remove the item from the list
|
||||
LinkTextList.Items.Remove(feedItem);
|
||||
|
||||
// The feed item has been read and is no longer new
|
||||
await feedItem.MarkAsRead(_database);
|
||||
}
|
||||
|
||||
private async 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;
|
||||
|
||||
// Remove the item from the list
|
||||
LinkTextList.Items.Remove(feedItem);
|
||||
|
||||
// The feed item has been read and is no longer new
|
||||
await feedItem.MarkAsRead(_database);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Common.Update;
|
||||
using ChrisKaczor.ApplicationUpdate;
|
||||
using FeedCenter.Properties;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -7,179 +7,225 @@ 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 showProgress, int maximum)
|
||||
{
|
||||
// Refresh the progress bar if we need it
|
||||
if (showProgress)
|
||||
{
|
||||
FeedReadProgress.Value = 0;
|
||||
FeedReadProgress.Maximum = maximum + 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.Accounts.Any())
|
||||
return;
|
||||
|
||||
var progressSteps = _database.Accounts.Sum(a => a.GetProgressSteps(_database));
|
||||
|
||||
// Switch to progress mode
|
||||
SetProgressMode(true, progressSteps);
|
||||
|
||||
// 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;
|
||||
|
||||
var accountsToRead = new List<Account>();
|
||||
|
||||
// If we have a single feed then get the account for that feed
|
||||
if (workerInput.FeedId != null)
|
||||
{
|
||||
var feed = database.Feeds.FirstOrDefault(f => f.Id == workerInput.FeedId);
|
||||
|
||||
if (feed != null)
|
||||
accountsToRead.Add(feed.Account);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise get all accounts
|
||||
accountsToRead.AddRange(database.Accounts);
|
||||
}
|
||||
|
||||
var incrementProgress = () =>
|
||||
{
|
||||
// Increment progress
|
||||
currentProgress += 1;
|
||||
|
||||
// Report progress
|
||||
worker.ReportProgress(currentProgress);
|
||||
};
|
||||
|
||||
// Loop over each account and read it
|
||||
foreach (var account in accountsToRead)
|
||||
{
|
||||
var accountReadInput = new AccountReadInput(database, workerInput.FeedId, workerInput.ForceRead, incrementProgress);
|
||||
|
||||
account.Read(accountReadInput);
|
||||
}
|
||||
|
||||
//// 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(Settings.Default.IncludePrerelease).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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
@@ -283,11 +285,13 @@
|
||||
<MenuItem Header="{x:Static properties:Resources.lockWindowCheckBox}"
|
||||
IsCheckable="True"
|
||||
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=WindowLocked}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{x:Static properties:Resources.CurrentFeed}">
|
||||
<MenuItem Header="{x:Static properties:Resources.EditMenu}"
|
||||
<Separator Name="SettingsMenuSeparator" />
|
||||
<MenuItem Name="CurrentFeedMenu" Header="{x:Static properties:Resources.CurrentFeed}">
|
||||
<MenuItem Name="EditCurrentFeedMenuItem"
|
||||
Header="{x:Static properties:Resources.EditMenu}"
|
||||
Click="HandleEditCurrentFeedMenuItemClick" />
|
||||
<MenuItem Header="{x:Static properties:Resources.DeleteMenu}"
|
||||
<MenuItem Name="DeleteCurrentFeedMenuItem"
|
||||
Header="{x:Static properties:Resources.DeleteMenu}"
|
||||
Click="HandleDeleteCurrentFeedMenuItemClick" />
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
@@ -295,17 +299,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>
|
||||
@@ -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.Properties;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
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().ContinueWith(_ => { }, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
|
||||
SingleInstance.Stop().ContinueWith(_ => { }, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
{
|
||||
// Set up 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;
|
||||
|
||||
// Set up the database
|
||||
_database = new FeedCenterEntities();
|
||||
|
||||
// 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(Settings.Default.IncludePrerelease);
|
||||
|
||||
// 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,289 +135,293 @@ namespace FeedCenter
|
||||
NameBasedGrid.NameBasedGrid.SetRow(NavigationToolbarTray, "BottomToolbarRow");
|
||||
|
||||
break;
|
||||
case Dock.Left:
|
||||
case Dock.Right:
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Feed display
|
||||
|
||||
private void UpdateToolbarButtonState()
|
||||
{
|
||||
// Cache the feed count to save (a little) time
|
||||
var feedCount = _feedList?.Count() ?? 0;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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.Category.ID == _currentCategory.ID);
|
||||
|
||||
UpdateToolbarButtonState();
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// Increment the index and adjust if we've gone around the end
|
||||
_feedIndex = (_feedIndex + 1) % feedCount;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
case MultipleOpenAction.IndividualPages:
|
||||
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
|
||||
break;
|
||||
case MultipleOpenAction.SinglePage:
|
||||
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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 = new FeedCenterEntities();
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
EditCurrentFeedMenuItem.Visibility = _currentFeed?.Account.SupportsFeedEdit ?? false ? Visibility.Visible : Visibility.Collapsed;
|
||||
DeleteCurrentFeedMenuItem.Visibility = _currentFeed?.Account.SupportsFeedDelete ?? false ? Visibility.Visible : Visibility.Collapsed;
|
||||
CurrentFeedMenu.Visibility = EditCurrentFeedMenuItem.IsVisible || DeleteCurrentFeedMenuItem.IsVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||
SettingsMenuSeparator.Visibility = CurrentFeedMenu.Visibility;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// Increment the index and adjust if we've gone around the end
|
||||
_feedIndex = (_feedIndex + 1) % feedCount;
|
||||
|
||||
// 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.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);
|
||||
|
||||
// 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()
|
||||
{
|
||||
var feedCount = _feedList.Count();
|
||||
|
||||
if (feedCount == 0)
|
||||
return;
|
||||
|
||||
if (Settings.Default.DisplayEmptyFeeds)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
{
|
||||
// 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.Any(item => !item.BeenRead))
|
||||
{
|
||||
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)
|
||||
{
|
||||
case MultipleOpenAction.IndividualPages:
|
||||
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllMultipleToolbarButton;
|
||||
break;
|
||||
case MultipleOpenAction.SinglePage:
|
||||
OpenAllToolbarButton.ToolTip = Properties.Resources.openAllSingleToolbarButton;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
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();
|
||||
UpdateToolbarButtonState();
|
||||
}
|
||||
|
||||
private async Task MarkAllItemsAsRead()
|
||||
{
|
||||
// Loop over all items and mark them as read
|
||||
foreach (FeedItem feedItem in LinkTextList.Items)
|
||||
await feedItem.MarkAsRead(_database);
|
||||
|
||||
// Clear the list
|
||||
LinkTextList.Items.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,46 @@
|
||||
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();
|
||||
_mainTimer = null;
|
||||
}
|
||||
|
||||
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 +61,6 @@ namespace FeedCenter
|
||||
|
||||
// Get the timer going again
|
||||
StartTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +1,271 @@
|
||||
using FeedCenter.Options;
|
||||
using ChrisKaczor.InstalledBrowsers;
|
||||
using FeedCenter.Options;
|
||||
using FeedCenter.Properties;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.UI;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
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 async Task 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
|
||||
await feedItem.MarkAsRead(_database);
|
||||
|
||||
// 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);
|
||||
|
||||
// Switch to the normal sleep interval
|
||||
sleepInterval = settings.OpenAllSleepInterval;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOptionsToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
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 static void HandleException(Exception exception)
|
||||
{
|
||||
Log.Logger.Write(LogEventLevel.Debug, exception, "");
|
||||
}
|
||||
|
||||
private async void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
await MarkAllItemsAsRead();
|
||||
}
|
||||
|
||||
private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
catch (Exception exception)
|
||||
{
|
||||
MarkAllItemsAsRead();
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Create the feed error window
|
||||
var feedErrorWindow = new FeedErrorWindow();
|
||||
private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Create the feed error window
|
||||
var feedErrorWindow = new FeedErrorWindow();
|
||||
|
||||
// Display the window
|
||||
var result = feedErrorWindow.Display(this);
|
||||
// Display the window
|
||||
feedErrorWindow.Display(this);
|
||||
|
||||
// If okay was selected
|
||||
if (result.GetValueOrDefault())
|
||||
{
|
||||
// Reset the database to current settings
|
||||
ResetDatabase();
|
||||
// Refresh the database to current settings
|
||||
ResetDatabase();
|
||||
|
||||
// Re-initialize the feed display
|
||||
DisplayFeed();
|
||||
// Re-initialize the feed display
|
||||
DisplayFeed();
|
||||
|
||||
UpdateErrorLink();
|
||||
}
|
||||
}
|
||||
UpdateErrorLink();
|
||||
}
|
||||
|
||||
private void HandleRefreshMenuItemClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuItem = (MenuItem) e.Source;
|
||||
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)
|
||||
{
|
||||
if (Equals(menuItem, MenuRefresh))
|
||||
ReadCurrentFeed(true);
|
||||
else if (Equals(menuItem, MenuRefreshAll))
|
||||
ReadFeeds(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
|
||||
private void HandleRefreshToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ReadFeeds(true);
|
||||
}
|
||||
|
||||
private async void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var menuItem = (MenuItem) e.Source;
|
||||
|
||||
if (Equals(menuItem, MenuOpenAllSinglePage))
|
||||
OpenAllFeedItemsOnSinglePage();
|
||||
await OpenAllFeedItemsOnSinglePage();
|
||||
else if (Equals(menuItem, MenuOpenAllMultiplePages))
|
||||
OpenAllFeedItemsIndividually();
|
||||
await OpenAllFeedItemsIndividually();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
private async void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var multipleOpenAction = _currentFeed.MultipleOpenAction;
|
||||
|
||||
switch (multipleOpenAction)
|
||||
{
|
||||
case MultipleOpenAction.IndividualPages:
|
||||
OpenAllFeedItemsIndividually();
|
||||
await OpenAllFeedItemsIndividually();
|
||||
break;
|
||||
case MultipleOpenAction.SinglePage:
|
||||
OpenAllFeedItemsOnSinglePage();
|
||||
await OpenAllFeedItemsOnSinglePage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
|
||||
catch (Exception exception)
|
||||
{
|
||||
// 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();
|
||||
HandleException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Create a new feed window
|
||||
var feedWindow = new FeedWindow(_database);
|
||||
|
||||
// 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));
|
||||
|
||||
// Refresh the database to current settings
|
||||
ResetDatabase();
|
||||
|
||||
// Re-initialize the feed display
|
||||
DisplayFeed();
|
||||
}
|
||||
|
||||
private async Task OpenAllFeedItemsOnSinglePage()
|
||||
{
|
||||
var fileName = Path.GetTempFileName() + ".html";
|
||||
TextWriter textWriter = new StreamWriter(fileName);
|
||||
|
||||
await using (var htmlTextWriter = new HtmlTextWriter(textWriter))
|
||||
{
|
||||
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);
|
||||
|
||||
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);
|
||||
|
||||
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
|
||||
await htmlTextWriter.WriteAsync(_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);
|
||||
await htmlTextWriter.WriteAsync(item.Title.Length == 0 ? item.Link : item.Title);
|
||||
htmlTextWriter.RenderEndTag();
|
||||
|
||||
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
|
||||
htmlTextWriter.RenderEndTag();
|
||||
|
||||
await htmlTextWriter.WriteAsync(item.Description);
|
||||
|
||||
htmlTextWriter.RenderEndTag();
|
||||
|
||||
firstItem = false;
|
||||
}
|
||||
|
||||
htmlTextWriter.RenderEndTag();
|
||||
htmlTextWriter.RenderEndTag();
|
||||
}
|
||||
|
||||
await textWriter.FlushAsync();
|
||||
textWriter.Close();
|
||||
|
||||
InstalledBrowser.OpenLink(Settings.Default.Browser, fileName);
|
||||
|
||||
await MarkAllItemsAsRead();
|
||||
}
|
||||
}
|
||||
@@ -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.UpdateServerType = ServerType.GitHub;
|
||||
UpdateCheck.UpdateServer = Settings.Default.VersionLocation;
|
||||
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, Settings.Default.IncludePrerelease);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
Settings.Default.Save();
|
||||
|
||||
// Set the last window size
|
||||
Settings.Default.WindowSize = new Size(Width, Height);
|
||||
Settings.Default.Save();
|
||||
|
||||
// Save the dock on the navigation tray
|
||||
Settings.Default.ToolbarLocation = NameBasedGrid.NameBasedGrid.GetRow(NavigationToolbarTray) == "TopToolbarRow" ? Dock.Top : Dock.Bottom;
|
||||
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(TimeSpan.FromMilliseconds(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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
#>
|
||||
10
Application/Model.Designer.cs
generated
10
Application/Model.Designer.cs
generated
@@ -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...'.
|
||||
@@ -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>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
#>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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, FeedCenterEntities entities) : base(parentWindow, entities)
|
||||
{
|
||||
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;
|
||||
}
|
||||
24
Application/Options/AccountTypeItem.cs
Normal file
24
Application/Options/AccountTypeItem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FeedCenter.Options
|
||||
{
|
||||
public class AccountTypeItem
|
||||
{
|
||||
public AccountType AccountType { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
public static List<AccountTypeItem> AccountTypes =>
|
||||
[
|
||||
new()
|
||||
{
|
||||
Name = Properties.Resources.AccountTypeFever,
|
||||
AccountType = AccountType.Fever
|
||||
},
|
||||
//new()
|
||||
//{
|
||||
// Name = Properties.Resources.AccountTypeGoogleReader,
|
||||
// AccountType = AccountType.GoogleReader
|
||||
//}
|
||||
];
|
||||
}
|
||||
}
|
||||
22
Application/Options/AccountTypeToNameConverter.cs
Normal file
22
Application/Options/AccountTypeToNameConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public class AccountTypeToNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is AccountType accountType)
|
||||
return AccountTypeItem.AccountTypes.First(at => at.AccountType == accountType).Name;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
139
Application/Options/AccountWindow.xaml
Normal file
139
Application/Options/AccountWindow.xaml
Normal file
@@ -0,0 +1,139 @@
|
||||
<Window x:Class="FeedCenter.Options.AccountWindow"
|
||||
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
d:DataContext="{d:DesignInstance Type=feedCenter:Account}"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:options="clr-namespace:FeedCenter.Options"
|
||||
mc:Ignorable="d"
|
||||
Title="AccountWindow"
|
||||
Height="350"
|
||||
Width="450"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="/FeedCenter;component/Resources/Application.ico"
|
||||
FocusManager.FocusedElement="{Binding ElementName=NameTextBox}">
|
||||
<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"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
mah:HeaderedControlHelper.HeaderFontSize="16"
|
||||
mah:TabControlHelper.Underlined="SelectedTabItem">
|
||||
<TabItem Header="{x:Static properties:Resources.generalTab}">
|
||||
<StackPanel Margin="0,4"
|
||||
options:Spacing.Vertical="8">
|
||||
<ComboBox Name="AccountTypeComboBox"
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.accountTypeLabel}"
|
||||
DisplayMemberPath="Name"
|
||||
ItemsSource="{Binding Source={x:Static options:AccountTypeItem.AccountTypes}}"
|
||||
SelectedValuePath="AccountType"
|
||||
SelectedValue="{Binding Path=Type, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=true}" />
|
||||
<TextBox Name="NameTextBox"
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.accountNameLabel}"
|
||||
mah:TextBoxHelper.SelectAllOnFocus="True"
|
||||
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
|
||||
<TextBox Name="UrlTextBox"
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.accountUrlLabel}"
|
||||
mah:TextBoxHelper.SelectAllOnFocus="True"
|
||||
Text="{Binding Path=Url, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
|
||||
<CheckBox Name="ReadIntervalCheckBox"
|
||||
VerticalContentAlignment="Center"
|
||||
IsChecked="{Binding Path=Enabled, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="{x:Static properties:Resources.accountReadIntervalPrefix}"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Padding="0" />
|
||||
<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.accountReadIntervalSuffix}"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Padding="0" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="{x:Static properties:Resources.authenticationTab}">
|
||||
<StackPanel Margin="0,4">
|
||||
<CheckBox Content="{x:Static properties:Resources.accountRequiresAuthenticationCheckBox}"
|
||||
Margin="0,0,0,4"
|
||||
Name="RequiresAuthenticationCheckBox"
|
||||
IsChecked="{Binding Path=Authenticate, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
|
||||
<TextBox Name="AuthenticationUserNameTextBox"
|
||||
Margin="25,0,0,4"
|
||||
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.authenticationUserNameLabel}"
|
||||
Text="{Binding Path=Username, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
|
||||
<PasswordBox Name="AuthenticationPasswordTextBox"
|
||||
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>
|
||||
<Grid Name="AccountReadProgress"
|
||||
Grid.Row="1"
|
||||
Height="20"
|
||||
Visibility="Collapsed">
|
||||
<ProgressBar Name="AccountReadProgressBar" />
|
||||
<TextBlock Text="Loading account..."
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
<StackPanel
|
||||
Name="ButtonPanel"
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
Visibility="Visible"
|
||||
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>
|
||||
78
Application/Options/AccountWindow.xaml.cs
Normal file
78
Application/Options/AccountWindow.xaml.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using ChrisKaczor.Wpf.Validation;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public partial class AccountWindow
|
||||
{
|
||||
private Account _account;
|
||||
private bool _isNew;
|
||||
private readonly FeedCenterEntities _entities;
|
||||
|
||||
public AccountWindow(FeedCenterEntities entities)
|
||||
{
|
||||
_entities = entities;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public bool? Display(Account account, Window owner, bool isNew)
|
||||
{
|
||||
_account = account;
|
||||
_isNew = isNew;
|
||||
|
||||
DataContext = account;
|
||||
|
||||
Title = isNew ? Properties.Resources.AccountWindowAdd : Properties.Resources.AccountWindowEdit;
|
||||
|
||||
Owner = owner;
|
||||
|
||||
return ShowDialog();
|
||||
}
|
||||
|
||||
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var transaction = _entities.BeginTransaction();
|
||||
|
||||
if (!this.IsValid(OptionsTabControl))
|
||||
{
|
||||
transaction.Rollback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isNew)
|
||||
{
|
||||
_entities.Accounts.Add(_account);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
var accountId = _account.Id;
|
||||
|
||||
AccountReadProgressBar.Value = 0;
|
||||
AccountReadProgressBar.Maximum = _account.GetProgressSteps(_entities) + 1;
|
||||
|
||||
AccountReadProgress.Visibility = Visibility.Visible;
|
||||
ButtonPanel.Visibility = Visibility.Collapsed;
|
||||
|
||||
var dispatcher = Dispatcher.CurrentDispatcher;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
var entities = new FeedCenterEntities();
|
||||
var account = entities.Accounts.First(a => a.Id == accountId);
|
||||
var accountReadInput = new AccountReadInput(entities, null, true, () => dispatcher.Invoke(() => AccountReadProgressBar.Value++));
|
||||
account.Read(accountReadInput);
|
||||
|
||||
dispatcher.Invoke(() =>
|
||||
{
|
||||
DialogResult = true;
|
||||
|
||||
Close();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
98
Application/Options/AccountsOptionsPanel.xaml
Normal file
98
Application/Options/AccountsOptionsPanel.xaml
Normal file
@@ -0,0 +1,98 @@
|
||||
<options:OptionsPanelBase x:Class="FeedCenter.Options.AccountsOptionsPanel"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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: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>
|
||||
<options:AccountTypeToNameConverter x:Key="AccountTypeToNameConverter" />
|
||||
<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="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<DataGrid Name="AccountDataGrid"
|
||||
SelectionChanged="HandleAccountDataGridSelectionChanged"
|
||||
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:Account }">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Name}"
|
||||
Header="{x:Static properties:Resources.AccountNameColumnHeader}"
|
||||
SortDirection="Ascending"
|
||||
Width="*" />
|
||||
<DataGridTextColumn Binding="{Binding Type, Converter={StaticResource AccountTypeToNameConverter}}"
|
||||
Header="{x:Static properties:Resources.AccountTypeColumnHeader}"
|
||||
Width="*" />
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="DataGridRow"
|
||||
BasedOn="{StaticResource MahApps.Styles.DataGridRow}">
|
||||
<EventSetter Event="MouseDoubleClick"
|
||||
Handler="HandleAccountDataGridRowMouseDoubleClick" />
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
</DataGrid>
|
||||
<Border Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
BorderThickness="1,0,1,1"
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" />
|
||||
<Border Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
BorderThickness="1,0,1,1"
|
||||
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
|
||||
<controls:Link Name="AddAccountButton"
|
||||
Margin="2"
|
||||
Click="HandleAddAccountButtonClick"
|
||||
Text="{x:Static properties:Resources.AddLink}"
|
||||
ToolTip="{x:Static properties:Resources.AddAccountButton}">
|
||||
</controls:Link>
|
||||
<controls:Link Name="EditAccountButton"
|
||||
Margin="2"
|
||||
Click="HandleEditAccountButtonClick"
|
||||
Text="{x:Static properties:Resources.EditLink}"
|
||||
ToolTip="{x:Static properties:Resources.EditAccountButton}">
|
||||
</controls:Link>
|
||||
<controls:Link Name="DeleteAccountButton"
|
||||
Margin="2"
|
||||
Click="HandleDeleteAccountButtonClick"
|
||||
Text="{x:Static properties:Resources.DeleteLink}"
|
||||
ToolTip="{x:Static properties:Resources.DeleteAccountButton}">
|
||||
</controls:Link>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</options:OptionsPanelBase>
|
||||
129
Application/Options/AccountsOptionsPanel.xaml.cs
Normal file
129
Application/Options/AccountsOptionsPanel.xaml.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public partial class AccountsOptionsPanel
|
||||
{
|
||||
private readonly FeedCenterEntities _entities;
|
||||
|
||||
public AccountsOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
|
||||
{
|
||||
_entities = entities;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public override string CategoryName => Properties.Resources.optionCategoryAccounts;
|
||||
|
||||
public override void LoadPanel()
|
||||
{
|
||||
base.LoadPanel();
|
||||
|
||||
var collectionViewSource = new CollectionViewSource { Source = _entities.Accounts };
|
||||
collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
|
||||
collectionViewSource.IsLiveSortingRequested = true;
|
||||
collectionViewSource.View.Filter = item =>
|
||||
{
|
||||
if (item is not Account account)
|
||||
return false;
|
||||
|
||||
// Filter out local accounts
|
||||
return account.Type != AccountType.Local;
|
||||
};
|
||||
|
||||
AccountDataGrid.ItemsSource = collectionViewSource.View;
|
||||
AccountDataGrid.SelectedIndex = 0;
|
||||
|
||||
SetAccountButtonStates();
|
||||
}
|
||||
|
||||
private void SetAccountButtonStates()
|
||||
{
|
||||
AddAccountButton.IsEnabled = true;
|
||||
|
||||
EditAccountButton.IsEnabled = AccountDataGrid.SelectedItem != null;
|
||||
DeleteAccountButton.IsEnabled = AccountDataGrid.SelectedItem != null;
|
||||
}
|
||||
|
||||
private void AddAccount()
|
||||
{
|
||||
var account = new Account(AccountType.Fever);
|
||||
|
||||
var accountWindow = new AccountWindow(_entities);
|
||||
|
||||
var result = accountWindow.Display(account, Window.GetWindow(this), true);
|
||||
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
AccountDataGrid.SelectedItem = account;
|
||||
|
||||
SetAccountButtonStates();
|
||||
}
|
||||
|
||||
private void EditSelectedAccount()
|
||||
{
|
||||
if (AccountDataGrid.SelectedItem == null)
|
||||
return;
|
||||
|
||||
var account = (Account) AccountDataGrid.SelectedItem;
|
||||
|
||||
var accountWindow = new AccountWindow(_entities);
|
||||
|
||||
accountWindow.Display(account, Window.GetWindow(this), false);
|
||||
}
|
||||
|
||||
private void DeleteSelectedAccount()
|
||||
{
|
||||
var account = (Account) AccountDataGrid.SelectedItem;
|
||||
|
||||
if (MessageBox.Show(ParentWindow, string.Format(Properties.Resources.ConfirmDeleteAccount, account.Name),
|
||||
Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question,
|
||||
MessageBoxResult.No) == MessageBoxResult.No)
|
||||
return;
|
||||
|
||||
var index = AccountDataGrid.SelectedIndex;
|
||||
|
||||
if (index == AccountDataGrid.Items.Count - 1)
|
||||
AccountDataGrid.SelectedIndex = index - 1;
|
||||
else
|
||||
AccountDataGrid.SelectedIndex = index + 1;
|
||||
|
||||
_entities.SaveChanges(() => _entities.Accounts.Remove(account));
|
||||
|
||||
SetAccountButtonStates();
|
||||
}
|
||||
|
||||
private void HandleAddAccountButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AddAccount();
|
||||
}
|
||||
|
||||
private void HandleEditAccountButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditSelectedAccount();
|
||||
}
|
||||
|
||||
private void HandleDeleteAccountButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DeleteSelectedAccount();
|
||||
}
|
||||
|
||||
private void HandleAccountDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SetAccountButtonStates();
|
||||
}
|
||||
|
||||
private void HandleAccountDataGridRowMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!EditAccountButton.IsEnabled)
|
||||
return;
|
||||
|
||||
EditSelectedAccount();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,101 +1,99 @@
|
||||
using Common.Wpf;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
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;
|
||||
private readonly FeedCenterEntities _entities;
|
||||
|
||||
public BulkFeedWindow(FeedCenterEntities entities)
|
||||
{
|
||||
private List<CheckedListItem<Feed>> _checkedListBoxItems;
|
||||
private CollectionViewSource _collectionViewSource;
|
||||
_entities = entities;
|
||||
|
||||
public BulkFeedWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void Display(Window window, FeedCenterEntities database)
|
||||
{
|
||||
_checkedListBoxItems = new List<CheckedListItem<Feed>>();
|
||||
public void Display(Window window)
|
||||
{
|
||||
_checkedListBoxItems = new List<CheckedListItem<Feed>>();
|
||||
|
||||
foreach (var feed in database.AllFeeds)
|
||||
_checkedListBoxItems.Add(new CheckedListItem<Feed> { Item = feed });
|
||||
foreach (var feed in _entities.Feeds)
|
||||
_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();
|
||||
}
|
||||
|
||||
void HandleCollectionViewSourceFilter(object sender, FilterEventArgs e)
|
||||
{
|
||||
var checkedListBoxItem = (CheckedListItem<Feed>) e.Item;
|
||||
private 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)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,53 @@
|
||||
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 Name="NameTextBox"
|
||||
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>
|
||||
@@ -1,64 +1,52 @@
|
||||
using Common.Wpf.Extensions;
|
||||
using System.Linq;
|
||||
using ChrisKaczor.Wpf.Validation;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FeedCenter.Options
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public partial class CategoryWindow
|
||||
{
|
||||
public partial class CategoryWindow
|
||||
private readonly FeedCenterEntities _entities;
|
||||
|
||||
public CategoryWindow(FeedCenterEntities entities)
|
||||
{
|
||||
public CategoryWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
_entities = entities;
|
||||
|
||||
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 = _entities.BeginTransaction();
|
||||
|
||||
if (!this.IsValid())
|
||||
{
|
||||
transaction.Rollback();
|
||||
return;
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
// Dialog is good
|
||||
DialogResult = true;
|
||||
|
||||
// Close the dialog
|
||||
Close();
|
||||
}
|
||||
}
|
||||
6
Application/Options/CheckedFeedListItem.cs
Normal file
6
Application/Options/CheckedFeedListItem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace FeedCenter.Options
|
||||
{
|
||||
public class CheckedFeedListItem : CheckedListItem<Feed>
|
||||
{
|
||||
}
|
||||
}
|
||||
41
Application/Options/CheckedListItem.cs
Normal file
41
Application/Options/CheckedListItem.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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, FeedCenterEntities entities) : base(parentWindow, entities)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -3,183 +3,146 @@
|
||||
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>
|
||||
<ComboBox Name="UserAgentComboBox"
|
||||
mah:TextBoxHelper.UseFloatingWatermark="True"
|
||||
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.userAgentLabel}"
|
||||
DisplayMemberPath="Caption"
|
||||
ItemsSource="{Binding Source={x:Static options:UserAgentItem.UserAgents}}"
|
||||
SelectedValuePath="UserAgent"
|
||||
SelectedValue="{Binding Path=UserAgent, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=true}" />
|
||||
</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>
|
||||
@@ -1,91 +1,46 @@
|
||||
using System.Data.Entity;
|
||||
using Common.Wpf.Extensions;
|
||||
using System.Linq;
|
||||
using ChrisKaczor.Wpf.Validation;
|
||||
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
|
||||
private readonly FeedCenterEntities _entities;
|
||||
|
||||
public FeedWindow(FeedCenterEntities entities)
|
||||
{
|
||||
public FeedWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
_entities = entities;
|
||||
|
||||
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 = _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 = _entities.BeginTransaction();
|
||||
|
||||
if (!this.IsValid(OptionsTabControl))
|
||||
{
|
||||
transaction.Rollback();
|
||||
return;
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
DialogResult = true;
|
||||
|
||||
Close();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -6,458 +6,404 @@ 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;
|
||||
private readonly FeedCenterEntities _entities;
|
||||
|
||||
public FeedsOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
|
||||
{
|
||||
#region Constructor
|
||||
_entities = entities;
|
||||
|
||||
public FeedsOptionsPanel()
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public override string CategoryName => Properties.Resources.optionCategoryFeeds;
|
||||
|
||||
public override void LoadPanel()
|
||||
{
|
||||
base.LoadPanel();
|
||||
|
||||
var collectionViewSource = new CollectionViewSource { Source = _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(_entities);
|
||||
|
||||
var category = (Category) CategoryDataGrid.SelectedItem;
|
||||
|
||||
feed.CategoryId = category.Id;
|
||||
|
||||
var feedWindow = new FeedWindow(_entities);
|
||||
|
||||
var result = feedWindow.Display(feed, Window.GetWindow(this));
|
||||
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
_entities.SaveChanges(() => _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(_entities);
|
||||
|
||||
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)
|
||||
_entities.SaveChanges(() => _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 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 _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 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(_entities);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
_entities.SaveChanges(() => _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 != _entities.DefaultCategory.Id;
|
||||
DeleteCategoryButton.IsEnabled = CategoryDataGrid.SelectedItem != null &&
|
||||
selectedId != _entities.DefaultCategory.Id;
|
||||
}
|
||||
|
||||
private void AddCategory()
|
||||
{
|
||||
var category = new Category();
|
||||
|
||||
var categoryWindow = new CategoryWindow(_entities);
|
||||
|
||||
var result = categoryWindow.Display(category, Window.GetWindow(this));
|
||||
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
_entities.SaveChanges(() => _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(_entities);
|
||||
|
||||
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 = _entities.DefaultCategory;
|
||||
|
||||
foreach (var feed in _entities.Feeds.Where(f => f.CategoryId == category.Id))
|
||||
_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;
|
||||
|
||||
_entities.SaveChanges(() => _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 = _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 && feed.Account.Type == AccountType.Local;
|
||||
}
|
||||
|
||||
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!)
|
||||
_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(_entities);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -5,54 +5,30 @@
|
||||
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" />
|
||||
<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" />
|
||||
<ComboBox Name="UserAgentComboBox"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
VerticalContentAlignment="Center">
|
||||
</ComboBox>
|
||||
|
||||
</Grid>
|
||||
</options:OptionsPanelBase>
|
||||
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=StartWithWindows}"
|
||||
Click="OnSaveSettings" />
|
||||
<ComboBox 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 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.DefaultUserAgents}}"
|
||||
SelectedValuePath="UserAgent"
|
||||
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=DefaultUserAgent}"
|
||||
SelectionChanged="OnSaveSettings" />
|
||||
</StackPanel>
|
||||
</options:OptionsPanelBase>
|
||||
@@ -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, FeedCenterEntities entities) : base(parentWindow, entities)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
73
Application/Options/MarginSetter.cs
Normal file
73
Application/Options/MarginSetter.cs
Normal 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));
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace FeedCenter.Options
|
||||
namespace FeedCenter.Options;
|
||||
|
||||
public enum MultipleLineDisplay
|
||||
{
|
||||
public enum MultipleLineDisplay
|
||||
{
|
||||
Normal,
|
||||
SingleLine,
|
||||
FirstLine
|
||||
}
|
||||
}
|
||||
Normal,
|
||||
SingleLine,
|
||||
FirstLine
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
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 readonly FeedCenterEntities Entities;
|
||||
|
||||
protected OptionsPanelBase(Window parentWindow, FeedCenterEntities entities)
|
||||
{
|
||||
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;
|
||||
Entities = entities;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string CategoryName => null;
|
||||
|
||||
protected bool HasLoaded { get; private set; }
|
||||
|
||||
public virtual void LoadPanel()
|
||||
{
|
||||
}
|
||||
|
||||
protected void MarkLoaded()
|
||||
{
|
||||
HasLoaded = true;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -1,128 +1,77 @@
|
||||
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();
|
||||
private readonly FeedCenterEntities _entities = 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, _entities));
|
||||
_optionPanels.Add(new DisplayOptionsPanel(this, _entities));
|
||||
_optionPanels.Add(new FeedsOptionsPanel(this, _entities));
|
||||
_optionPanels.Add(new AccountsOptionsPanel(this, _entities));
|
||||
_optionPanels.Add(new UpdateOptionsPanel(this, _entities));
|
||||
_optionPanels.Add(new AboutOptionsPanel(this, _entities));
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Application/Options/Setting.cs
Normal file
13
Application/Options/Setting.cs
Normal 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; }
|
||||
}
|
||||
54
Application/Options/Spacing.cs
Normal file
54
Application/Options/Spacing.cs
Normal 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));
|
||||
}
|
||||
@@ -6,18 +6,19 @@
|
||||
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" />
|
||||
<CheckBox Content="{x:Static properties:Resources.includePrereleaseCheckBox}"
|
||||
Name="IncludePrereleaseCheckBox"
|
||||
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=IncludePrerelease}"
|
||||
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>
|
||||
@@ -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, FeedCenterEntities entities) : base(parentWindow, entities)
|
||||
{
|
||||
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, Settings.Default.IncludePrerelease);
|
||||
}
|
||||
|
||||
private void OnSaveSettings(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
if (!HasLoaded) return;
|
||||
|
||||
Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user