From d4fb3681aea2206e2a15c72bfd47a8540d2ff165 Mon Sep 17 00:00:00 2001 From: DanielKirshner Date: Sat, 4 Apr 2026 22:45:26 +0300 Subject: [PATCH] Add an option to import/export Scrupdate configuration --- .../ConfigurationImportExportUtilities.cs | 234 ++++++++++++++++++ .../UiElements/Windows/MainWindow.xaml.cs | 45 +++- .../UiElements/Windows/SettingsWindow.xaml | 48 +++- .../UiElements/Windows/SettingsWindow.xaml.cs | 66 ++++- 4 files changed, 387 insertions(+), 6 deletions(-) create mode 100644 Scrupdate/Classes/Utilities/ConfigurationImportExportUtilities.cs diff --git a/Scrupdate/Classes/Utilities/ConfigurationImportExportUtilities.cs b/Scrupdate/Classes/Utilities/ConfigurationImportExportUtilities.cs new file mode 100644 index 0000000..431fae8 --- /dev/null +++ b/Scrupdate/Classes/Utilities/ConfigurationImportExportUtilities.cs @@ -0,0 +1,234 @@ +// Copyright © 2021-2025 Matan Brightbert +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + + + +using System; +using System.IO; +using System.IO.Compression; +using System.Text; + + +namespace Scrupdate.Classes.Utilities +{ + public static class ConfigurationImportExportUtilities + { + // Constants /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + public const string CONFIGURATION_FILE_EXTENSION = ".scrupdate"; + public const string CONFIGURATION_FILE_FILTER = "Scrupdate Configuration Files (*.scrupdate)|*.scrupdate"; + private const string ENTRY_NAME__SETTINGS = "Settings.json"; + private const string ENTRY_NAME__SETTINGS_CHECKSUM = "Settings.json.md5"; + private const string ENTRY_NAME__PROGRAM_DATABASE = "Programs.sqlite"; + private const string ENTRY_NAME__PROGRAM_DATABASE_CHECKSUM = "Programs.sqlite.md5"; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + // Methods ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + public static bool ExportConfiguration(string exportFilePath) + { + if (exportFilePath == null) + throw new ArgumentNullException(nameof(exportFilePath)); + try + { + string exportFileDirectoryPath = Path.GetDirectoryName(exportFilePath); + if (!Directory.Exists(exportFileDirectoryPath)) + Directory.CreateDirectory(exportFileDirectoryPath); + using (FileStream fileStream = new FileStream( + exportFilePath, + FileMode.Create, + FileAccess.Write, + FileShare.None + )) + { + using (ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create)) + { + if (File.Exists(ApplicationUtilities.settingsFilePath)) + { + AddFileToZipArchive( + zipArchive, + ApplicationUtilities.settingsFilePath, + ENTRY_NAME__SETTINGS + ); + } + if (File.Exists(ApplicationUtilities.settingsChecksumFilePath)) + { + AddFileToZipArchive( + zipArchive, + ApplicationUtilities.settingsChecksumFilePath, + ENTRY_NAME__SETTINGS_CHECKSUM + ); + } + if (File.Exists(ApplicationUtilities.programDatabaseFilePath)) + { + AddFileToZipArchive( + zipArchive, + ApplicationUtilities.programDatabaseFilePath, + ENTRY_NAME__PROGRAM_DATABASE + ); + } + if (File.Exists(ApplicationUtilities.programDatabaseChecksumFilePath)) + { + AddFileToZipArchive( + zipArchive, + ApplicationUtilities.programDatabaseChecksumFilePath, + ENTRY_NAME__PROGRAM_DATABASE_CHECKSUM + ); + } + } + } + return true; + } + catch + { + try + { + if (File.Exists(exportFilePath)) + File.Delete(exportFilePath); + } + catch { } + return false; + } + } + public static bool ImportConfiguration(string importFilePath) + { + if (importFilePath == null) + throw new ArgumentNullException(nameof(importFilePath)); + try + { + if (!File.Exists(importFilePath)) + return false; + using (FileStream fileStream = new FileStream( + importFilePath, + FileMode.Open, + FileAccess.Read, + FileShare.Read + )) + { + using (ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read)) + { + bool hasSettings = (zipArchive.GetEntry(ENTRY_NAME__SETTINGS) != null); + bool hasSettingsChecksum = (zipArchive.GetEntry(ENTRY_NAME__SETTINGS_CHECKSUM) != null); + if (hasSettings != hasSettingsChecksum) + return false; + bool hasProgramDatabase = (zipArchive.GetEntry(ENTRY_NAME__PROGRAM_DATABASE) != null); + bool hasProgramDatabaseChecksum = (zipArchive.GetEntry(ENTRY_NAME__PROGRAM_DATABASE_CHECKSUM) != null); + if (hasProgramDatabase != hasProgramDatabaseChecksum) + return false; + if (!hasSettings && !hasProgramDatabase) + return false; + ApplicationUtilities.CreateDataFolderIfNotExists(); + if (hasSettings) + { + ExtractFileFromZipArchive( + zipArchive, + ENTRY_NAME__SETTINGS, + ApplicationUtilities.settingsFilePath + ); + ExtractFileFromZipArchive( + zipArchive, + ENTRY_NAME__SETTINGS_CHECKSUM, + ApplicationUtilities.settingsChecksumFilePath + ); + try + { + File.SetAttributes( + ApplicationUtilities.settingsChecksumFilePath, + File.GetAttributes(ApplicationUtilities.settingsChecksumFilePath) | FileAttributes.Hidden + ); + } + catch { } + } + if (hasProgramDatabase) + { + ExtractFileFromZipArchive( + zipArchive, + ENTRY_NAME__PROGRAM_DATABASE, + ApplicationUtilities.programDatabaseFilePath + ); + ExtractFileFromZipArchive( + zipArchive, + ENTRY_NAME__PROGRAM_DATABASE_CHECKSUM, + ApplicationUtilities.programDatabaseChecksumFilePath + ); + try + { + File.SetAttributes( + ApplicationUtilities.programDatabaseChecksumFilePath, + File.GetAttributes(ApplicationUtilities.programDatabaseChecksumFilePath) | FileAttributes.Hidden + ); + } + catch { } + } + } + } + return true; + } + catch + { + return false; + } + } + private static void AddFileToZipArchive(ZipArchive zipArchive, string sourceFilePath, string entryName) + { + ZipArchiveEntry entry = zipArchive.CreateEntry(entryName, CompressionLevel.Optimal); + using (Stream entryStream = entry.Open()) + { + using (FileStream sourceFileStream = new FileStream( + sourceFilePath, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite + )) + { + sourceFileStream.CopyTo(entryStream); + } + } + } + private static void ExtractFileFromZipArchive(ZipArchive zipArchive, string entryName, string destinationFilePath) + { + ZipArchiveEntry entry = zipArchive.GetEntry(entryName); + if (entry == null) + return; + string destinationDirectoryPath = Path.GetDirectoryName(destinationFilePath); + if (!Directory.Exists(destinationDirectoryPath)) + Directory.CreateDirectory(destinationDirectoryPath); + if (File.Exists(destinationFilePath)) + { + try + { + File.SetAttributes( + destinationFilePath, + File.GetAttributes(destinationFilePath) & ~FileAttributes.Hidden & ~FileAttributes.ReadOnly + ); + } + catch { } + } + using (Stream entryStream = entry.Open()) + { + using (FileStream destinationFileStream = new FileStream( + destinationFilePath, + FileMode.Create, + FileAccess.Write, + FileShare.None + )) + { + entryStream.CopyTo(destinationFileStream); + } + } + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } +} diff --git a/Scrupdate/UiElements/Windows/MainWindow.xaml.cs b/Scrupdate/UiElements/Windows/MainWindow.xaml.cs index 8e8718c..9dde9e1 100644 --- a/Scrupdate/UiElements/Windows/MainWindow.xaml.cs +++ b/Scrupdate/UiElements/Windows/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -// Copyright © 2021-2025 Matan Brightbert +// Copyright © 2021-2025 Matan Brightbert // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -46,6 +46,8 @@ public partial class MainWindow : Window, INotifyPropertyChanged private const string ERROR_DIALOG_MESSAGE__PROGRAM_DATABASE_RECREATION_WAS_FAILED = "Program Database Recreation Was Failed!\r\n\r\n• If this error persists, try to restart your computer or reinstall Scrupdate."; private const string ERROR_DIALOG_MESSAGE__FAILED_TO_RESET_ONE_OR_MORE_COMPONENTS = "Failed to Reset One or More Components!"; private const string ERROR_DIALOG_MESSAGE__FAILED_TO_SAVE_SETTINGS = "Failed to Save Settings!"; + private const string ERROR_DIALOG_MESSAGE__AN_ERROR_HAS_OCCURRED_WHILE_IMPORTING_CONFIGURATION = "An error has occurred while importing the configuration!\r\n\r\n• The selected file may be invalid or corrupted."; + private const string INFORMATION_DIALOG_MESSAGE__CONFIGURATION_HAS_BEEN_IMPORTED_SUCCESSFULLY = "The configuration has been imported successfully!\r\n\r\n• Scrupdate will now close.\r\n• Please reopen Scrupdate to load the imported configuration."; private const string QUESTION_DIALOG_MESSAGE__ARE_YOU_SURE_YOU_WANT_TO_CLOSE_SCRUPDATE_FORCEFULLY = "Are You Sure You Want to Close Scrupdate Forcefully?\r\n\r\n• If you close Scrupdate forcefully, ChromeDriver will not have a chance to delete its temporary files."; private const string QUESTION_DIALOG_MESSAGE__DO_YOU_WANT_TO_RECREATE_THE_PROGRAM_DATABASE = "Do You Want to Recreate the Program Database?\r\n\r\n• All the program information and configurations will be lost."; private const string QUESTION_DIALOG_MESSAGE__REMOVE_THE_SELECTED_PROGRAMS_FROM_THE_LIST = "Remove the Selected Program(s) from the List?"; @@ -445,9 +447,38 @@ private void OnButtonClickEvent(object sender, RoutedEventArgs e) else if (senderButton == button_settings) { Settings updatedSettings; - if (OpenSettingsWindowAsDialog(out updatedSettings) == true) + bool hasImportedConfiguration; + string importConfigurationFilePath; + if (OpenSettingsWindowAsDialog(out updatedSettings, out hasImportedConfiguration, out importConfigurationFilePath) == true) { - if (updatedSettings != null) + if (hasImportedConfiguration) + { + resettingAllSettingsAndData = true; + programDatabase?.Close(); + if (App.SettingsHandler != null) + { + App.SettingsHandler.Dispose(); + App.SettingsHandler = null; + } + if (ConfigurationImportExportUtilities.ImportConfiguration(importConfigurationFilePath)) + { + DialogUtilities.ShowInformationDialog( + "", + INFORMATION_DIALOG_MESSAGE__CONFIGURATION_HAS_BEEN_IMPORTED_SUCCESSFULLY, + this + ); + } + else + { + DialogUtilities.ShowErrorDialog( + "", + ERROR_DIALOG_MESSAGE__AN_ERROR_HAS_OCCURRED_WHILE_IMPORTING_CONFIGURATION, + this + ); + } + Application.Current.Shutdown(); + } + else if (updatedSettings != null) ApplyUpdatedSettings(updatedSettings); else { @@ -873,9 +904,11 @@ private void OnGridViewColumnsCollectionCollectionChangedEvent(object sender, No // Methods ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private bool? OpenSettingsWindowAsDialog(out Settings updatedSettings) + private bool? OpenSettingsWindowAsDialog(out Settings updatedSettings, out bool hasImportedConfiguration, out string importConfigurationFilePath) { updatedSettings = null; + hasImportedConfiguration = false; + importConfigurationFilePath = null; bool? returnValue = null; SettingsWindow settingsWindow = null; ThreadingUtilities.RunOnAnotherThread( @@ -891,7 +924,11 @@ private void OnGridViewColumnsCollectionCollectionChangedEvent(object sender, No } ); if (returnValue == true) + { updatedSettings = settingsWindow.GetUpdatedSettings(); + hasImportedConfiguration = settingsWindow.HasImportedConfiguration(); + importConfigurationFilePath = settingsWindow.GetImportConfigurationFilePath(); + } return returnValue; } private void OpenUserManualFile() diff --git a/Scrupdate/UiElements/Windows/SettingsWindow.xaml b/Scrupdate/UiElements/Windows/SettingsWindow.xaml index 5cf86d2..0a3d426 100644 --- a/Scrupdate/UiElements/Windows/SettingsWindow.xaml +++ b/Scrupdate/UiElements/Windows/SettingsWindow.xaml @@ -1,4 +1,4 @@ -