From 6514f233295d7caacaa98fb12211cc0dba1cd933 Mon Sep 17 00:00:00 2001 From: Chris Kaczor Date: Thu, 6 Apr 2023 17:20:38 -0400 Subject: [PATCH] Rework database loading/migration --- Application/App.xaml.cs | 4 +- Application/Data/Database.cs | 213 +--------- Application/Data/Extensions.cs | 13 - Application/Data/LegacyDatabase.cs | 234 +++++++++++ Application/Entities.cs | 62 +-- Application/FeedCenter.csproj | 11 + Application/FeedErrorWindow.xaml.cs | 5 +- Application/Feeds/Feed.cs | 11 +- Application/MainWindow/FeedCreation.cs | 1 - Application/MainWindow/FeedReading.cs | 29 +- Application/Options/CategoryWindow.xaml.cs | 2 +- .../Options/GeneralOptionsPanel.xaml.cs | 10 +- Application/Options/OptionsWindow.xaml.cs | 2 +- .../DataSources/FeedCenterEntities.datasource | 10 - Application/Properties/Resources.Designer.cs | 36 +- Application/Properties/Resources.resx | 18 +- Application/Properties/Settings.Designer.cs | 13 +- Application/Properties/Settings.settings | 33 +- Application/SettingsStore.cs | 5 + Application/SplashWindow.xaml.cs | 393 ++++++++---------- Application/app.config | 25 +- 21 files changed, 561 insertions(+), 569 deletions(-) delete mode 100644 Application/Data/Extensions.cs create mode 100644 Application/Data/LegacyDatabase.cs delete mode 100644 Application/Properties/DataSources/FeedCenterEntities.datasource diff --git a/Application/App.xaml.cs b/Application/App.xaml.cs index 08f0b36..e916c1c 100644 --- a/Application/App.xaml.cs +++ b/Application/App.xaml.cs @@ -44,9 +44,11 @@ namespace FeedCenter 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); - Database.Load(); // Get the generic provider var genericProvider = (GenericSettingsProvider) Settings.Default.Providers[nameof(GenericSettingsProvider)]; diff --git a/Application/Data/Database.cs b/Application/Data/Database.cs index 6c70743..f7917ae 100644 --- a/Application/Data/Database.cs +++ b/Application/Data/Database.cs @@ -1,218 +1,25 @@ -using FeedCenter.Properties; -using Serilog; -using System; -using System.Collections.Generic; -using System.Data.SqlServerCe; -using System.IO; -using System.Linq; +using System.IO; namespace FeedCenter.Data { public static class Database { - #region Static database settings + public static string DatabaseFile { get; set; } + public static string DatabasePath { get; set; } - public static string DatabaseFile; - public static string DatabasePath; + public static FeedCenterEntities Entities { get; set; } - #endregion + public static bool Exists => File.Exists(DatabaseFile); - #region File version - - public static FeedCenterEntities Entities; - - private enum SqlServerCeFileVersion - { - Unknown, - Version20, - Version30, - Version35, - Version40, - } + public static bool Loaded { get; set; } public static void Load() { + if (Loaded) return; + Entities = new FeedCenterEntities(); - } - private static SqlServerCeFileVersion GetFileVersion(string databasePath) - { - // Create a mapping of version numbers to the version enumeration - var versionMapping = new Dictionary - { - { 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.ContainsKey(signature) ? versionMapping[signature] : SqlServerCeFileVersion.Unknown; - } - - #endregion - - public static bool DatabaseExists => File.Exists(DatabaseFile); - - public static void CreateDatabase() - { - Log.Logger.Information("Creating database engine"); - - // Create the database engine - using var engine = new SqlCeEngine($"Data Source={DatabaseFile}"); - Log.Logger.Information("Creating database"); - - // Create the database itself - engine.CreateDatabase(); - - Log.Logger.Information("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"; - - 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(); - - // 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() - { - 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(); - } - - 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(); - } + Loaded = true; } } -} +} \ No newline at end of file diff --git a/Application/Data/Extensions.cs b/Application/Data/Extensions.cs deleted file mode 100644 index 5ad949f..0000000 --- a/Application/Data/Extensions.cs +++ /dev/null @@ -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 - } -} diff --git a/Application/Data/LegacyDatabase.cs b/Application/Data/LegacyDatabase.cs new file mode 100644 index 0000000..e19d9c8 --- /dev/null +++ b/Application/Data/LegacyDatabase.cs @@ -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 + { + { 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(); + + // 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("SELECT * FROM Setting").OrderBy(s => s.Version).ToList(); + var categories = connection.Query("SELECT * FROM Category").ToList(); + var feeds = connection.Query("SELECT * FROM Feed").ToList(); + var feedItems = connection.Query("SELECT * FROM FeedItem").ToList(); + + realm.Write(() => + { + foreach (var category in categories) + { + category.Feeds = feeds.Where(f => f.CategoryId == category.Id).ToList(); + } + + foreach (var feed in feeds) + { + feed.Category = categories.FirstOrDefault(c => c.Id == feed.CategoryId); + } + + 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(); + } + } +} \ No newline at end of file diff --git a/Application/Entities.cs b/Application/Entities.cs index 6278bfd..39f0005 100644 --- a/Application/Entities.cs +++ b/Application/Entities.cs @@ -1,10 +1,7 @@ -using Dapper; -using FeedCenter.Data; +using FeedCenter.Data; using FeedCenter.Options; using Realms; using System; -using System.Data.SqlServerCe; -using System.IO; using System.Linq; namespace FeedCenter @@ -19,60 +16,10 @@ namespace FeedCenter public FeedCenterEntities() { - Load(); - } - - public void Refresh() - { - Realm.Refresh(); - } - - public void Load() - { - var realmConfiguration = new RealmConfiguration($"{Database.DatabasePath}/FeedCenter.realm"); + var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}"); Realm = Realm.GetInstance(realmConfiguration); - if (File.Exists(Database.DatabaseFile)) - { - using var connection = new SqlCeConnection($"Data Source={Database.DatabaseFile}"); - - connection.Open(); - - var settings = connection.Query("SELECT * FROM Setting").OrderBy(s => s.Version).ToList(); - var categories = connection.Query("SELECT * FROM Category").ToList(); - var feeds = connection.Query("SELECT * FROM Feed").ToList(); - var feedItems = connection.Query("SELECT * FROM FeedItem").ToList(); - - Realm.Write(() => - { - foreach (var category in categories) - { - category.Feeds = feeds.Where(f => f.CategoryId == category.Id).ToList(); - } - - foreach (var feed in feeds) - { - feed.Category = categories.FirstOrDefault(c => c.Id == feed.CategoryId); - } - - 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(Database.DatabaseFile, Database.DatabaseFile + "_bak"); - } - Settings = new RealmObservableCollection(Realm); Feeds = new RealmObservableCollection(Realm); Categories = new RealmObservableCollection(Realm); @@ -83,6 +30,11 @@ namespace FeedCenter } } + public void Refresh() + { + Realm.Refresh(); + } + public void SaveChanges(Action action) { Realm.Write(action); diff --git a/Application/FeedCenter.csproj b/Application/FeedCenter.csproj index 85e7b59..1940fc3 100644 --- a/Application/FeedCenter.csproj +++ b/Application/FeedCenter.csproj @@ -157,12 +157,23 @@ + + True + True + Resources.resx + True True Settings.settings + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + SettingsSingleFileGenerator diff --git a/Application/FeedErrorWindow.xaml.cs b/Application/FeedErrorWindow.xaml.cs index f595897..08db8ec 100644 --- a/Application/FeedErrorWindow.xaml.cs +++ b/Application/FeedErrorWindow.xaml.cs @@ -108,13 +108,14 @@ namespace FeedCenter Close(); } - private async void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e) + private void HandleRefreshCurrentButtonClick(object sender, RoutedEventArgs e) { IsEnabled = false; Mouse.OverrideCursor = Cursors.Wait; var feed = (Feed) FeedDataGrid.SelectedItem; - await feed.ReadAsync(_database, true); + + _database.SaveChanges(() => feed.Read(_database, true)); var selectedIndex = FeedDataGrid.SelectedIndex; diff --git a/Application/Feeds/Feed.cs b/Application/Feeds/Feed.cs index 8c7642d..f9e3db9 100644 --- a/Application/Feeds/Feed.cs +++ b/Application/Feeds/Feed.cs @@ -72,8 +72,8 @@ namespace FeedCenter public string Link { get; set; } public string Description { get; set; } public DateTimeOffset LastChecked { get; set; } - public int CheckInterval { get; set; } - public bool Enabled { get; set; } + public int CheckInterval { get; set; } = 60; + public bool Enabled { get; set; } = true; public bool Authenticate { get; set; } public string Username { get; set; } public string Password { get; set; } @@ -164,7 +164,7 @@ namespace FeedCenter } // 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) + if (result == FeedReadResult.Success && LastUpdated == default) LastUpdated = DateTimeOffset.Now; Log.Logger.Information("Done reading feed: {0}", result); @@ -172,11 +172,6 @@ namespace FeedCenter return result; } - public async Task ReadAsync(FeedCenterEntities database, bool forceRead = false) - { - return await Task.Run(() => Read(database, forceRead)); - } - public Tuple DetectFeedType() { var retrieveResult = RetrieveFeed(); diff --git a/Application/MainWindow/FeedCreation.cs b/Application/MainWindow/FeedCreation.cs index 0f70142..9c59b86 100644 --- a/Application/MainWindow/FeedCreation.cs +++ b/Application/MainWindow/FeedCreation.cs @@ -23,7 +23,6 @@ namespace FeedCenter var feed = Feed.Create(_database); feed.Source = feedUrl; feed.Category = _database.DefaultCategory; - feed.Enabled = true; // Try to detect the feed type var feedTypeResult = feed.DetectFeedType(); diff --git a/Application/MainWindow/FeedReading.cs b/Application/MainWindow/FeedReading.cs index e8dd651..f179fe5 100644 --- a/Application/MainWindow/FeedReading.cs +++ b/Application/MainWindow/FeedReading.cs @@ -15,8 +15,23 @@ namespace FeedCenter private class FeedReadWorkerInput { - public bool ForceRead; - public Feed Feed; + public bool ForceRead { get; } + public Guid? FeedId { get; } + + public FeedReadWorkerInput() + { + } + + public FeedReadWorkerInput(bool forceRead) + { + ForceRead = forceRead; + } + + public FeedReadWorkerInput(bool forceRead, Guid? feedId) + { + ForceRead = forceRead; + FeedId = feedId; + } } private void SetProgressMode(bool value, int feedCount) @@ -48,7 +63,7 @@ namespace FeedCenter SetProgressMode(true, 1); // Create the input class - var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = _currentFeed }; + var workerInput = new FeedReadWorkerInput(forceRead, _currentFeed.Id); // Start the worker _feedReadWorker.RunWorkerAsync(workerInput); @@ -68,7 +83,7 @@ namespace FeedCenter SetProgressMode(true, _database.Feeds.Count); // Create the input class - var workerInput = new FeedReadWorkerInput { ForceRead = forceRead, Feed = null }; + var workerInput = new FeedReadWorkerInput(forceRead); // Start the worker _feedReadWorker.RunWorkerAsync(workerInput); @@ -126,7 +141,7 @@ namespace FeedCenter var worker = (BackgroundWorker) sender; // Get the input information - var workerInput = (FeedReadWorkerInput) e.Argument ?? new FeedReadWorkerInput { Feed = null, ForceRead = false }; + var workerInput = (FeedReadWorkerInput) e.Argument ?? new FeedReadWorkerInput(); // Setup for progress var currentProgress = 0; @@ -135,8 +150,8 @@ namespace FeedCenter var feedsToRead = new List(); // 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)); + if (workerInput.FeedId != null) + feedsToRead.Add(database.Feeds.First(feed => feed.Id == workerInput.FeedId)); else feedsToRead.AddRange(database.Feeds); diff --git a/Application/Options/CategoryWindow.xaml.cs b/Application/Options/CategoryWindow.xaml.cs index 33063d5..ec1c42b 100644 --- a/Application/Options/CategoryWindow.xaml.cs +++ b/Application/Options/CategoryWindow.xaml.cs @@ -29,7 +29,7 @@ namespace FeedCenter.Options private void HandleOkayButtonClick(object sender, RoutedEventArgs e) { - if (!this.Validate()) + if (!this.IsValid()) return; // Dialog is good diff --git a/Application/Options/GeneralOptionsPanel.xaml.cs b/Application/Options/GeneralOptionsPanel.xaml.cs index 6bbd56d..a86ef9e 100644 --- a/Application/Options/GeneralOptionsPanel.xaml.cs +++ b/Application/Options/GeneralOptionsPanel.xaml.cs @@ -54,8 +54,6 @@ namespace FeedCenter.Options var expressions = this.GetBindingExpressions(new[] { UpdateSourceTrigger.Explicit }); this.UpdateAllSources(expressions); - - this.Validate(); } public override string CategoryName => Properties.Resources.optionCategoryGeneral; @@ -106,22 +104,22 @@ namespace FeedCenter.Options { var userAgents = new List { - new UserAgentItem + new() { Caption = Properties.Resources.DefaultUserAgentCaption, UserAgent = string.Empty }, - new UserAgentItem + new() { Caption = "Windows RSS Platform 2.0", UserAgent = "Windows-RSS-Platform/2.0 (MSIE 9.0; Windows NT 6.1)" }, - new UserAgentItem + new() { Caption = "Feedly 1.0", UserAgent = "Feedly/1.0" }, - new UserAgentItem + new() { Caption = "curl", UserAgent = "curl/7.47.0" diff --git a/Application/Options/OptionsWindow.xaml.cs b/Application/Options/OptionsWindow.xaml.cs index 8e63700..a94500c 100644 --- a/Application/Options/OptionsWindow.xaml.cs +++ b/Application/Options/OptionsWindow.xaml.cs @@ -9,7 +9,7 @@ namespace FeedCenter.Options { #region Member variables - private readonly List _optionPanels = new List(); + private readonly List _optionPanels = new(); private readonly FeedCenterEntities _database = Database.Entities; diff --git a/Application/Properties/DataSources/FeedCenterEntities.datasource b/Application/Properties/DataSources/FeedCenterEntities.datasource deleted file mode 100644 index c919ef6..0000000 --- a/Application/Properties/DataSources/FeedCenterEntities.datasource +++ /dev/null @@ -1,10 +0,0 @@ - - - - FeedCenter.FeedCenterEntities, Model.Designer.cs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null - \ No newline at end of file diff --git a/Application/Properties/Resources.Designer.cs b/Application/Properties/Resources.Designer.cs index 9009133..dfb1d4a 100644 --- a/Application/Properties/Resources.Designer.cs +++ b/Application/Properties/Resources.Designer.cs @@ -1298,11 +1298,11 @@ namespace FeedCenter.Properties { } /// - /// Looks up a localized string similar to Checking database existence.... + /// Looks up a localized string similar to Checking for legacy database.... /// - public static string SplashCheckingForDatabase { + public static string SplashCheckingForLegacyDatabase { get { - return ResourceManager.GetString("SplashCheckingForDatabase", resourceCulture); + return ResourceManager.GetString("SplashCheckingForLegacyDatabase", resourceCulture); } } @@ -1343,11 +1343,29 @@ namespace FeedCenter.Properties { } /// - /// Looks up a localized string similar to Maintaining database.... + /// Looks up a localized string similar to Loading database.... /// - public static string SplashMaintainingDatabase { + public static string SplashLoadingDatabase { get { - return ResourceManager.GetString("SplashMaintainingDatabase", resourceCulture); + return ResourceManager.GetString("SplashLoadingDatabase", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maintaining legacy database.... + /// + public static string SplashMaintainingLegacyDatabase { + get { + return ResourceManager.GetString("SplashMaintainingLegacyDatabase", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Migrating legacy database.... + /// + public static string SplashMigratingLegacyDatabase { + get { + return ResourceManager.GetString("SplashMigratingLegacyDatabase", resourceCulture); } } @@ -1370,11 +1388,11 @@ namespace FeedCenter.Properties { } /// - /// Looks up a localized string similar to Updating database.... + /// Looks up a localized string similar to Updating legacy database.... /// - public static string SplashUpdatingDatabase { + public static string SplashUpdatingLegacyDatabase { get { - return ResourceManager.GetString("SplashUpdatingDatabase", resourceCulture); + return ResourceManager.GetString("SplashUpdatingLegacyDatabase", resourceCulture); } } diff --git a/Application/Properties/Resources.resx b/Application/Properties/Resources.resx index bc79932..7021dfc 100644 --- a/Application/Properties/Resources.resx +++ b/Application/Properties/Resources.resx @@ -154,8 +154,8 @@ Checking for update... - - Checking database existence... + + Checking for legacy database... Creating database... @@ -169,11 +169,11 @@ Downloading update... - - Maintaining database... + + Maintaining legacy database... - - Updating database... + + Updating legacy database... Starting... @@ -529,4 +529,10 @@ Default _user agent: + + Loading database... + + + Migrating legacy database... + \ No newline at end of file diff --git a/Application/Properties/Settings.Designer.cs b/Application/Properties/Settings.Designer.cs index 09f4ddd..0f48d7f 100644 --- a/Application/Properties/Settings.Designer.cs +++ b/Application/Properties/Settings.Designer.cs @@ -74,9 +74,9 @@ namespace FeedCenter.Properties { [global::System.Configuration.ApplicationScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("FeedCenter.sdf")] - public string DatabaseFile { + public string DatabaseFile_Legacy { get { - return ((string)(this["DatabaseFile"])); + return ((string)(this["DatabaseFile_Legacy"])); } } @@ -293,5 +293,14 @@ namespace FeedCenter.Properties { this["DefaultUserAgent"] = value; } } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("FeedCenter.realm")] + public string DatabaseFile { + get { + return ((string)(this["DatabaseFile"])); + } + } } } diff --git a/Application/Properties/Settings.settings b/Application/Properties/Settings.settings index 62bdabb..e662fcb 100644 --- a/Application/Properties/Settings.settings +++ b/Application/Properties/Settings.settings @@ -2,19 +2,19 @@ - + False - + 0,0 - + 0,0 True - + FeedCenter.sdf @@ -26,40 +26,40 @@ 5000 - + 00:01:00 - + 00:30:00 - + True - + 01:00:00 - + False True - + Bottom - + 500 - + - + 1500 - + Normal @@ -68,8 +68,11 @@ - + + + FeedCenter.realm + \ No newline at end of file diff --git a/Application/SettingsStore.cs b/Application/SettingsStore.cs index 45f655b..56e75c7 100644 --- a/Application/SettingsStore.cs +++ b/Application/SettingsStore.cs @@ -10,6 +10,11 @@ namespace FeedCenter { public static object OpenDataStore() { + if (!Database.Exists) + return null; + + Database.Load(); + return Database.Entities; } diff --git a/Application/SplashWindow.xaml.cs b/Application/SplashWindow.xaml.cs index 75742b2..eee972e 100644 --- a/Application/SplashWindow.xaml.cs +++ b/Application/SplashWindow.xaml.cs @@ -7,222 +7,191 @@ using System.ComponentModel; using System.Threading; using System.Windows.Threading; -namespace FeedCenter +namespace FeedCenter; + +public partial class SplashWindow : IDisposable { - public partial class SplashWindow : IDisposable + private class ProgressStep { - #region Progress step + public delegate bool ProgressCallback(); - private class ProgressStep + public readonly string Key; + public readonly string Caption; + public readonly ProgressCallback Callback; + + public ProgressStep(string key, string caption, ProgressCallback callback) { - public delegate bool ProgressCallback(); - - public readonly string Key; - public readonly string Caption; - public readonly ProgressCallback Callback; - - public ProgressStep(string key, string caption, ProgressCallback callback) - { - Key = key; - Caption = caption; - Callback = callback; - } - } - - #endregion - - #region Member variables - - private readonly List _progressSteps = new(); - private readonly Dispatcher _dispatcher; - private readonly BackgroundWorker _backgroundWorker; - - #endregion - - #region Constructor - - public SplashWindow() - { - InitializeComponent(); - - // Store the dispatcher - the background worker has trouble getting the right thread when called from Main - _dispatcher = Dispatcher.CurrentDispatcher; - - // Get the version to display - var version = UpdateCheck.LocalVersion.ToString(); - - // Show the version - VersionLabel.Content = string.Format(Properties.Resources.Version, version); - - // Set the starting caption - StatusLabel.Content = Properties.Resources.SplashStarting; - - // Build the progress steps - LoadProgressSteps(); - - // Set the progress bar to the number of steps - ProgressBar.Maximum = _progressSteps.Count; - - // Create the worker with progress and cancel - _backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; - - // Setup the events - _backgroundWorker.DoWork += HandleBackgroundWorkerDoWork; - _backgroundWorker.ProgressChanged += HandleBackgroundWorkerProgressChanged; - _backgroundWorker.RunWorkerCompleted += HandleBackgroundWorkerCompleted; - } - - #endregion - - #region Form overrides - - protected override void OnContentRendered(EventArgs e) - { - base.OnContentRendered(e); - - // Start the worker - _backgroundWorker.RunWorkerAsync(); - } - - #endregion - - #region Background worker - - private void HandleBackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e) - { - if (!_dispatcher.CheckAccess()) - { - _dispatcher.Invoke(new EventHandler(HandleBackgroundWorkerProgressChanged), sender, e); - return; - } - - // Update the progress bar - ProgressBar.Value += e.ProgressPercentage; - - // Get the message - var message = (string) e.UserState; - - // Update the status label if one was supplied - if (!string.IsNullOrEmpty(message)) - StatusLabel.Content = message; - } - - private void HandleBackgroundWorkerDoWork(object sender, DoWorkEventArgs e) - { - // Wait just a little bit to make sure the window is up - Thread.Sleep(100); - - // Initialize the skip key - var skipKey = string.Empty; - - // Loop over all progress steps and execute - foreach (var progressStep in _progressSteps) - { - if (progressStep.Key == skipKey) - { - // Update progress with an empty step - UpdateProgress(_backgroundWorker, string.Empty); - } - else - { - // Update progress - UpdateProgress(_backgroundWorker, progressStep.Caption); - - // Execute the step and get the result - var result = progressStep.Callback(); - - // If the step indicated a skip then set the skip key, otherwise clear it - skipKey = (result ? string.Empty : progressStep.Key); - } - - // Stop if cancelled - if (_backgroundWorker.CancellationPending) - return; - } - } - - private static void UpdateProgress(BackgroundWorker worker, string progressMessage) - { - // Update the worker - worker.ReportProgress(1, progressMessage); - - // Sleep a bit if we actually updated - if (!string.IsNullOrEmpty(progressMessage)) - Thread.Sleep(Settings.Default.ProgressSleepInterval); - } - - private void HandleBackgroundWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - if (!_dispatcher.CheckAccess()) - { - _dispatcher.Invoke(new EventHandler(HandleBackgroundWorkerCompleted), sender, e); - return; - } - - // Move the progress bar to the max just in case - ProgressBar.Value = ProgressBar.Maximum; - - // Close the window - Close(); - } - - #endregion - - #region Progress steps - - private static class ProgressKey - { - public const string DatabaseCreate = "CreateDatabase"; - public const string DatabaseUpdate = "UpdateDatabase"; - public const string DatabaseMaintenance = "MaintainDatabase"; - } - - private void LoadProgressSteps() - { - // Load the progress steps - _progressSteps.Add(new ProgressStep(ProgressKey.DatabaseCreate, Properties.Resources.SplashCheckingForDatabase, CheckDatabase)); - _progressSteps.Add(new ProgressStep(ProgressKey.DatabaseCreate, Properties.Resources.SplashCreatingDatabase, CreateDatabase)); - - _progressSteps.Add(new ProgressStep(ProgressKey.DatabaseUpdate, Properties.Resources.SplashUpdatingDatabase, UpdateDatabase)); - - _progressSteps.Add(new ProgressStep(ProgressKey.DatabaseMaintenance, Properties.Resources.SplashMaintainingDatabase, MaintainDatabase)); - } - - private static bool CheckDatabase() - { - // If the database exists then we're done - return !Database.DatabaseExists; - } - - private static bool CreateDatabase() - { - // Create the database - //Database.CreateDatabase(); - - return true; - } - - private static bool UpdateDatabase() - { - // Update the database - // Database.UpdateDatabase(); - - return true; - } - - private static bool MaintainDatabase() - { - // Maintain the database - //Database.MaintainDatabase(); - - return true; - } - - #endregion - - public void Dispose() - { - _backgroundWorker?.Dispose(); + Key = key; + Caption = caption; + Callback = callback; } } + + private readonly List _progressSteps = new(); + private readonly Dispatcher _dispatcher; + private readonly BackgroundWorker _backgroundWorker; + + public SplashWindow() + { + InitializeComponent(); + + // Store the dispatcher - the background worker has trouble getting the right thread when called from Main + _dispatcher = Dispatcher.CurrentDispatcher; + + VersionLabel.Content = string.Format(Properties.Resources.Version, UpdateCheck.LocalVersion.ToString()); + + StatusLabel.Content = Properties.Resources.SplashStarting; + + LoadProgressSteps(); + + ProgressBar.Maximum = _progressSteps.Count; + + _backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; + + _backgroundWorker.DoWork += HandleBackgroundWorkerDoWork; + _backgroundWorker.ProgressChanged += HandleBackgroundWorkerProgressChanged; + _backgroundWorker.RunWorkerCompleted += HandleBackgroundWorkerCompleted; + } + + protected override void OnContentRendered(EventArgs e) + { + base.OnContentRendered(e); + + _backgroundWorker.RunWorkerAsync(); + } + + private void HandleBackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e) + { + if (!_dispatcher.CheckAccess()) + { + _dispatcher.Invoke(new EventHandler(HandleBackgroundWorkerProgressChanged), sender, e); + return; + } + + ProgressBar.Value += e.ProgressPercentage; + + // Get the message + var message = (string) e.UserState; + + // Update the status label if one was supplied + if (!string.IsNullOrEmpty(message)) + StatusLabel.Content = message; + } + + private void HandleBackgroundWorkerDoWork(object sender, DoWorkEventArgs e) + { + // Wait just a little bit to make sure the window is up + Thread.Sleep(100); + + // Initialize the skip key + var skipKey = string.Empty; + + // Loop over all progress steps and execute + foreach (var progressStep in _progressSteps) + { + if (progressStep.Key == skipKey) + { + // Update progress with an empty step + UpdateProgress(_backgroundWorker, string.Empty); + } + else + { + // Update progress + UpdateProgress(_backgroundWorker, progressStep.Caption); + + // Execute the step and get the result + var result = progressStep.Callback(); + + // If the step indicated a skip then set the skip key, otherwise clear it + skipKey = result ? string.Empty : progressStep.Key; + } + + // Stop if cancelled + if (_backgroundWorker.CancellationPending) + return; + } + } + + private static void UpdateProgress(BackgroundWorker worker, string progressMessage) + { + // Update the worker + worker.ReportProgress(1, progressMessage); + + // Sleep a bit if we actually updated + if (!string.IsNullOrEmpty(progressMessage)) + Thread.Sleep(Settings.Default.ProgressSleepInterval); + } + + private void HandleBackgroundWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (!_dispatcher.CheckAccess()) + { + _dispatcher.Invoke(new EventHandler(HandleBackgroundWorkerCompleted), sender, e); + return; + } + + // Move the progress bar to the max just in case + ProgressBar.Value = ProgressBar.Maximum; + + Close(); + } + + private static class ProgressKey + { + public const string ManageLegacyDatabase = "ManageLegacyDatabase"; + public const string ManageDatabase = "ManageDatabase"; + } + + private void LoadProgressSteps() + { + _progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashCheckingForLegacyDatabase, CheckDatabase)); + _progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashUpdatingLegacyDatabase, UpdateDatabase)); + _progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashMaintainingLegacyDatabase, MaintainDatabase)); + _progressSteps.Add(new ProgressStep(ProgressKey.ManageLegacyDatabase, Properties.Resources.SplashMigratingLegacyDatabase, MigrateDatabase)); + + _progressSteps.Add(new ProgressStep(ProgressKey.ManageDatabase, Properties.Resources.SplashLoadingDatabase, LoadDatabase)); + } + + private static bool CheckDatabase() + { + return LegacyDatabase.Exists; + } + + private static bool UpdateDatabase() + { + LegacyDatabase.UpdateDatabase(); + + return true; + } + + private static bool MaintainDatabase() + { + LegacyDatabase.MaintainDatabase(); + + return true; + } + + private static bool MigrateDatabase() + { + LegacyDatabase.MigrateDatabase(); + + return true; + } + + private bool LoadDatabase() + { + _dispatcher.Invoke(() => + { + Database.Load(); + + Settings.Default.Reload(); + }); + + return true; + } + + public void Dispose() + { + _backgroundWorker?.Dispose(); + } } \ No newline at end of file diff --git a/Application/app.config b/Application/app.config index 8e46379..991c074 100644 --- a/Application/app.config +++ b/Application/app.config @@ -13,12 +13,6 @@ False - - 0,0 - - - 0,0 - True @@ -32,7 +26,7 @@ True - + False @@ -40,26 +34,20 @@ True - - Bottom - 500 - + 1500 - - Normal - - + - + @@ -68,7 +56,7 @@ True - + FeedCenter.sdf @@ -83,6 +71,9 @@ https://api.github.com/repos/ckaczor/FeedCenter/releases/latest + + FeedCenter.realm +