From c6c5a56850318605b66d7f46daa7cad51b8c50d6 Mon Sep 17 00:00:00 2001 From: w4po Date: Mon, 24 Feb 2025 05:28:38 +0200 Subject: [PATCH 1/3] feat: Add plugin system for changelog viewers Implements a flexible plugin system for displaying changelog content with multiple viewer options: - Add IChangelogViewer interface for viewer implementations - Add RichTextBox viewer for simple text display - Add WebBrowser viewer for legacy IE-based browser - Add WebView2 viewer for modern Edge WebView2 - Add ChangelogViewerFactory for viewer instantiation Users can now: - Choose their preferred viewer type via AutoUpdater.ChangelogViewerType - Load content directly as text or from URL (where supported) - Extend the system with custom viewer implementations The system automatically handles viewer initialization, cleanup, and fallback behavior when preferred viewers are unavailable. --- AutoUpdater.NET/AutoUpdater.cs | 1520 +++++++++-------- .../ChangelogViewerFactory.cs | 26 + .../ChangelogViewers/ChangelogViewerType.cs | 8 + .../ChangelogViewers/IChangelogViewer.cs | 12 + .../ChangelogViewers/RichTextBoxViewer.cs | 31 + .../ChangelogViewers/WebBrowserViewer.cs | 91 + .../ChangelogViewers/WebView2Viewer.cs | 96 ++ AutoUpdater.NET/UpdateForm.Designer.cs | 40 +- AutoUpdater.NET/UpdateForm.cs | 424 ++--- AutoUpdater.NET/UpdateForm.resx | 57 +- AutoUpdater.NET/UpdateInfoEventArgs.cs | 6 + AutoUpdaterTest/MainWindow.xaml.cs | 66 +- 12 files changed, 1261 insertions(+), 1116 deletions(-) create mode 100644 AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs diff --git a/AutoUpdater.NET/AutoUpdater.cs b/AutoUpdater.NET/AutoUpdater.cs index 480419c5..379ed8e1 100644 --- a/AutoUpdater.NET/AutoUpdater.cs +++ b/AutoUpdater.NET/AutoUpdater.cs @@ -1,758 +1,764 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Net; -using System.Net.Cache; -using System.Reflection; -using System.Threading; -using System.Windows; -using System.Windows.Forms; -using System.Xml; -using System.Xml.Serialization; -using AutoUpdaterDotNET.Properties; -using Application = System.Windows.Forms.Application; -using MessageBox = System.Windows.Forms.MessageBox; -using Size = System.Drawing.Size; -using Timer = System.Timers.Timer; -using WinFormsMethodInvoker = System.Windows.Forms.MethodInvoker; - -namespace AutoUpdaterDotNET; - -/// -/// Enum representing the remind later time span. -/// -public enum RemindLaterFormat -{ - /// - /// Represents the time span in minutes. - /// - Minutes, - - /// - /// Represents the time span in hours. - /// - Hours, - - /// - /// Represents the time span in days. - /// - Days -} - -/// -/// Enum representing the effect of Mandatory flag. -/// -public enum Mode -{ - /// - /// In this mode, it ignores Remind Later and Skip values set previously and hide both buttons. - /// - [XmlEnum("0")] Normal, - - /// - /// In this mode, it won't show close button in addition to Normal mode behaviour. - /// - [XmlEnum("1")] Forced, - - /// - /// In this mode, it will start downloading and applying update without showing standard update dialog in addition to - /// Forced mode behaviour. - /// - [XmlEnum("2")] ForcedDownload -} - -/// -/// Main class that lets you auto update applications by setting some static fields and executing its Start method. -/// -public static class AutoUpdater -{ - /// - /// A delegate type to handle how to exit the application after update is downloaded. - /// - public delegate void ApplicationExitEventHandler(); - - /// - /// A delegate type for hooking up update notifications. - /// - /// - /// An object containing all the parameters received from AppCast XML file. If there will be an error - /// while looking for the XML file then this object will be null. - /// - public delegate void CheckForUpdateEventHandler(UpdateInfoEventArgs args); - - /// - /// A delegate type for hooking up parsing logic. - /// - /// An object containing the AppCast file received from server. - public delegate void ParseUpdateInfoHandler(ParseUpdateInfoEventArgs args); - - private static bool _isWinFormsApplication; - - private static IWin32Window _owner; - - private static Timer _remindLaterTimer; - - internal static Uri BaseUri; - - internal static bool Running; - - /// - /// URL of the xml file that contains information about latest version of the application. - /// - public static string AppCastURL; - - /// - /// Set the Application Title shown in Update dialog. Although AutoUpdater.NET will get it automatically, you can set - /// this property if you like to give custom Title. - /// - public static string AppTitle; - - /// - /// Set Basic Authentication credentials to navigate to the change log URL. - /// - public static IAuthentication BasicAuthChangeLog; - - /// - /// Set Basic Authentication credentials required to download the file. - /// - public static IAuthentication BasicAuthDownload; - - /// - /// Set Basic Authentication credentials required to download the XML file. - /// - public static IAuthentication BasicAuthXML; - - /// - /// Set this to true if you want to clear application directory before extracting update. - /// - public static bool ClearAppDirectory = false; - - /// - /// Set it to folder path where you want to download the update file. If not provided then it defaults to Temp folder. - /// - public static string DownloadPath; - - /// - /// If you are using a zip file as an update file, then you can set this value to a new executable path relative to the - /// installation directory. - /// - public static string ExecutablePath; - - /// - /// Login/password/domain for FTP-request - /// - public static NetworkCredential FtpCredentials; - - /// - /// Set the User-Agent string to be used for HTTP web requests. - /// - public static string HttpUserAgent; - - /// - /// Set this to change the icon shown on updater dialog. - /// - public static Bitmap Icon; - - /// - /// If you are using a zip file as an update file then you can set this value to path where your app is installed. This - /// is only necessary when your installation directory differs from your executable path. - /// - public static string InstallationPath; - - /// - /// You can set this field to your current version if you don't want to determine the version from the assembly. - /// - public static Version InstalledVersion; - - /// - /// If this is true users see dialog where they can set remind later interval otherwise it will take the interval from - /// RemindLaterAt and RemindLaterTimeSpan fields. - /// - public static bool LetUserSelectRemindLater = true; - - /// - /// Set this to true if you want to ignore previously assigned Remind Later and Skip settings. It will also hide Remind - /// Later and Skip buttons. - /// - public static bool Mandatory; - - /// - /// Opens the download URL in default browser if true. Very useful if you have portable application. - /// - public static bool OpenDownloadPage; - - /// - /// Set this to an instance implementing the IPersistenceProvider interface for using a data storage method different - /// from the default Windows Registry based one. - /// - public static IPersistenceProvider PersistenceProvider; - - /// - /// Set Proxy server to use for all the web requests in AutoUpdater.NET. - /// - public static IWebProxy Proxy; - - /// - /// Remind Later interval after user should be reminded of update. - /// - public static int RemindLaterAt = 2; - - /// - /// Set if RemindLaterAt interval should be in Minutes, Hours or Days. - /// - public static RemindLaterFormat RemindLaterTimeSpan = RemindLaterFormat.Days; - - /// - /// AutoUpdater.NET will report errors if this is true. - /// - public static bool ReportErrors = false; - - /// - /// Set this to false if your application doesn't need administrator privileges to replace the old version. - /// - public static bool RunUpdateAsAdmin = true; - - /// - /// If this is true users can see the Remind Later button. - /// - public static bool ShowRemindLaterButton = true; - - /// - /// If this is true users can see the skip button. - /// - public static bool ShowSkipButton = true; - - /// - /// Set this to true if you want to run update check synchronously. - /// - public static bool Synchronous = false; - - /// - /// Modify TopMost property of all dialogs. - /// - public static bool TopMost = false; - - /// - /// Set this if you want the default update form to have a different size. - /// - public static Size? UpdateFormSize = null; - - /// - /// Set this to any of the available modes to change behaviour of the Mandatory flag. - /// - public static Mode UpdateMode; - - /// - /// An event that developers can use to exit the application gracefully. - /// - public static event ApplicationExitEventHandler ApplicationExitEvent; - - /// - /// An event that clients can use to be notified whenever the update is checked. - /// - public static event CheckForUpdateEventHandler CheckForUpdateEvent; - - /// - /// An event that clients can use to be notified whenever the AppCast file needs parsing. - /// - public static event ParseUpdateInfoHandler ParseUpdateInfoEvent; - - /// - /// Set the owner for all dialogs. - /// - /// WPF Window or Windows Form object to be used as owner for all dialogs. - public static void SetOwner(object obj) - { - _owner = obj switch - { - Form form => form, - Window window => new Wpf32Window(window), - _ => _owner - }; - } - - /// - /// Start checking for new version of application and display a dialog to the user if update is available. - /// - /// Assembly to use for version checking. - public static void Start(Assembly myAssembly = null) - { - Start(AppCastURL, myAssembly); - } - - /// - /// Start checking for new version of application via FTP and display a dialog to the user if update is available. - /// - /// FTP URL of the xml file that contains information about latest version of the application. - /// Credentials required to connect to FTP server. - /// Assembly to use for version checking. - public static void Start(string appCast, NetworkCredential ftpCredentials, Assembly myAssembly = null) - { - FtpCredentials = ftpCredentials; - Start(appCast, myAssembly); - } - - /// - /// Start checking for new version of application and display a dialog to the user if update is available. - /// - /// URL of the xml file that contains information about latest version of the application. - /// Assembly to use for version checking. - public static void Start(string appCast, Assembly myAssembly = null) - { - try - { - ServicePointManager.SecurityProtocol |= (SecurityProtocolType)192 | - (SecurityProtocolType)768 | (SecurityProtocolType)3072; - } - catch (NotSupportedException) - { - } - - if (Mandatory && _remindLaterTimer != null) - { - _remindLaterTimer.Stop(); - _remindLaterTimer.Close(); - _remindLaterTimer = null; - } - - if (Running || _remindLaterTimer != null) - { - return; - } - - Running = true; - - AppCastURL = appCast; - - _isWinFormsApplication = Application.MessageLoop; - - if (!_isWinFormsApplication) - { - Application.EnableVisualStyles(); - } - - Assembly assembly = myAssembly ?? Assembly.GetEntryAssembly(); - - if (Synchronous) - { - try - { - object result = CheckUpdate(assembly); - - if (StartUpdate(result)) - { - return; - } - - Running = false; - } - catch (Exception exception) - { - ShowError(exception); - } - } - else - { - using var backgroundWorker = new BackgroundWorker(); - - backgroundWorker.DoWork += (_, args) => - { - var mainAssembly = args.Argument as Assembly; - - args.Result = CheckUpdate(mainAssembly); - }; - - backgroundWorker.RunWorkerCompleted += (_, args) => - { - if (args.Error != null) - { - ShowError(args.Error); - } - else - { - if (!args.Cancelled && StartUpdate(args.Result)) - { - return; - } - - Running = false; - } - }; - - backgroundWorker.RunWorkerAsync(assembly); - } - } - - private static object CheckUpdate(Assembly mainAssembly) - { - var companyAttribute = - (AssemblyCompanyAttribute)GetAttribute(mainAssembly, typeof(AssemblyCompanyAttribute)); - string appCompany = companyAttribute != null ? companyAttribute.Company : ""; - - if (string.IsNullOrEmpty(AppTitle)) - { - var titleAttribute = - (AssemblyTitleAttribute)GetAttribute(mainAssembly, typeof(AssemblyTitleAttribute)); - AppTitle = titleAttribute != null ? titleAttribute.Title : mainAssembly.GetName().Name; - } - - string registryLocation = !string.IsNullOrEmpty(appCompany) - ? $@"Software\{appCompany}\{AppTitle}\AutoUpdater" - : $@"Software\{AppTitle}\AutoUpdater"; - - PersistenceProvider ??= new RegistryPersistenceProvider(registryLocation); - - UpdateInfoEventArgs args; - string xml = null; - - if (AppCastURL != null) - { - BaseUri = new Uri(AppCastURL); - using MyWebClient client = GetWebClient(BaseUri, BasicAuthXML); - xml = client.DownloadString(BaseUri); - } - - if (ParseUpdateInfoEvent == null) - { - if (string.IsNullOrEmpty(xml)) - { - throw new Exception("It is required to handle ParseUpdateInfoEvent when XML url is not specified."); - } - - var xmlSerializer = new XmlSerializer(typeof(UpdateInfoEventArgs)); - var xmlTextReader = new XmlTextReader(new StringReader(xml)) { XmlResolver = null }; - args = (UpdateInfoEventArgs)xmlSerializer.Deserialize(xmlTextReader); - } - else - { - var parseArgs = new ParseUpdateInfoEventArgs(xml); - ParseUpdateInfoEvent(parseArgs); - args = parseArgs.UpdateInfo; - } - - if (string.IsNullOrEmpty(args?.CurrentVersion) || string.IsNullOrEmpty(args.DownloadURL)) - { - throw new MissingFieldException(); - } - - args.InstalledVersion = InstalledVersion ?? mainAssembly.GetName().Version; - args.IsUpdateAvailable = new Version(args.CurrentVersion) > args.InstalledVersion; - - if (!Mandatory) - { - if (string.IsNullOrEmpty(args.Mandatory.MinimumVersion) || - args.InstalledVersion < new Version(args.Mandatory.MinimumVersion)) - { - Mandatory = args.Mandatory.Value; - UpdateMode = args.Mandatory.UpdateMode; - } - } - - if (Mandatory) - { - ShowRemindLaterButton = false; - ShowSkipButton = false; - } - else - { - // Read the persisted state from the persistence provider. - // This method makes the persistence handling independent from the storage method. - Version skippedVersion = PersistenceProvider.GetSkippedVersion(); - if (skippedVersion != null) - { - var currentVersion = new Version(args.CurrentVersion); - if (currentVersion <= skippedVersion) - { - return null; - } - - if (currentVersion > skippedVersion) - { - // Update the persisted state. Its no longer makes sense to have this flag set as we are working on a newer application version. - PersistenceProvider.SetSkippedVersion(null); - } - } - - DateTime? remindLaterAt = PersistenceProvider.GetRemindLater(); - if (remindLaterAt == null) - { - return args; - } - - int compareResult = DateTime.Compare(DateTime.Now, remindLaterAt.Value); - - if (compareResult < 0) - { - return remindLaterAt.Value; - } - } - - return args; - } - - private static bool StartUpdate(object result) - { - if (result is DateTime time) - { - SetTimer(time); - } - else - { - if (result is not UpdateInfoEventArgs args) - { - return false; - } - - if (CheckForUpdateEvent != null) - { - CheckForUpdateEvent(args); - } - else - { - if (args.IsUpdateAvailable) - { - if (Mandatory && UpdateMode == Mode.ForcedDownload) - { - DownloadUpdate(args); - Exit(); - } - else - { - if (Thread.CurrentThread.GetApartmentState().Equals(ApartmentState.STA)) - { - ShowUpdateForm(args); - } - else - { - var thread = new Thread(new ThreadStart(delegate { ShowUpdateForm(args); })); - thread.CurrentCulture = - thread.CurrentUICulture = CultureInfo.CurrentCulture; - thread.SetApartmentState(ApartmentState.STA); - thread.Start(); - thread.Join(); - } - } - - return true; - } - - if (ReportErrors) - { - MessageBox.Show(_owner, - Resources.UpdateUnavailableMessage, - Resources.UpdateUnavailableCaption, - MessageBoxButtons.OK, MessageBoxIcon.Information); - } - } - } - - return false; - } - - private static void ShowError(Exception exception) - { - if (CheckForUpdateEvent != null) - { - CheckForUpdateEvent(new UpdateInfoEventArgs { Error = exception }); - } - else - { - if (ReportErrors) - { - if (exception is WebException) - { - MessageBox.Show(_owner, - Resources.UpdateCheckFailedMessage, - Resources.UpdateCheckFailedCaption, - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - else - { - MessageBox.Show(_owner, - exception.Message, - exception.GetType().ToString(), - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - - Running = false; - } - - /// - /// Detects and exits all instances of running assembly, including current. - /// - internal static void Exit() - { - var currentProcess = Process.GetCurrentProcess(); - foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName)) - { - try - { - string processPath = process.MainModule?.FileName; - - // Get all instances of assembly except current - if (process.Id == currentProcess.Id || currentProcess.MainModule?.FileName != processPath) - { - continue; - } - - if (process.CloseMainWindow()) - { - process.WaitForExit((int)TimeSpan.FromSeconds(10) - .TotalMilliseconds); // Give some time to process message - } - - if (process.HasExited) - { - continue; - } - - process.Kill(); //TODO: Show UI message asking user to close program himself instead of silently killing it - } - catch (Exception) - { - // ignored - } - } - - if (ApplicationExitEvent != null) - { - ApplicationExitEvent(); - } - else - { - if (_isWinFormsApplication) - { - WinFormsMethodInvoker methodInvoker = Application.Exit; - methodInvoker.Invoke(); - } - else if (System.Windows.Application.Current != null) - { - System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => - System.Windows.Application.Current.Shutdown())); - } - else - { - Environment.Exit(0); - } - } - } - - private static Attribute GetAttribute(Assembly assembly, Type attributeType) - { - object[] attributes = assembly.GetCustomAttributes(attributeType, false); - if (attributes.Length == 0) - { - return null; - } - - return (Attribute)attributes[0]; - } - - internal static string GetUserAgent() - { - return string.IsNullOrEmpty(HttpUserAgent) ? "AutoUpdater.NET" : HttpUserAgent; - } - - internal static void SetTimer(DateTime remindLater) - { - TimeSpan timeSpan = remindLater - DateTime.Now; - - SynchronizationContext context = SynchronizationContext.Current; - - _remindLaterTimer = new Timer - { - Interval = Math.Max(1, timeSpan.TotalMilliseconds), - AutoReset = false - }; - - _remindLaterTimer.Elapsed += delegate - { - _remindLaterTimer = null; - if (context != null) - { - try - { - context.Send(_ => Start(), null); - } - catch (InvalidAsynchronousStateException) - { - Start(); - } - } - else - { - Start(); - } - }; - - _remindLaterTimer.Start(); - } - - /// - /// Opens the Download window that download the update and execute the installer when download completes. - /// - public static bool DownloadUpdate(UpdateInfoEventArgs args) - { - using var downloadDialog = new DownloadUpdateDialog(args); - - try - { - return downloadDialog.ShowDialog(_owner).Equals(DialogResult.OK); - } - catch (TargetInvocationException) - { - // ignored - } - - return false; - } - - /// - /// Shows standard update dialog. - /// - public static void ShowUpdateForm(UpdateInfoEventArgs args) - { - using var updateForm = new UpdateForm(args); - - if (UpdateFormSize.HasValue) - { - updateForm.Size = UpdateFormSize.Value; - } - - if (updateForm.ShowDialog(_owner).Equals(DialogResult.OK)) - { - Exit(); - } - } - - internal static MyWebClient GetWebClient(Uri uri, IAuthentication basicAuthentication) - { - var webClient = new MyWebClient - { - CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore) - }; - - if (Proxy != null) - { - webClient.Proxy = Proxy; - } - - if (uri.Scheme.Equals(Uri.UriSchemeFtp)) - { - webClient.Credentials = FtpCredentials; - } - else - { - basicAuthentication?.Apply(ref webClient); - - webClient.Headers[HttpRequestHeader.UserAgent] = HttpUserAgent; - } - - return webClient; - } +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Net; +using System.Net.Cache; +using System.Reflection; +using System.Threading; +using System.Windows; +using System.Windows.Forms; +using System.Xml; +using System.Xml.Serialization; +using AutoUpdaterDotNET.ChangelogViewers; +using AutoUpdaterDotNET.Properties; +using Application = System.Windows.Forms.Application; +using MessageBox = System.Windows.Forms.MessageBox; +using Size = System.Drawing.Size; +using Timer = System.Timers.Timer; +using WinFormsMethodInvoker = System.Windows.Forms.MethodInvoker; + +namespace AutoUpdaterDotNET; + +/// +/// Enum representing the remind later time span. +/// +public enum RemindLaterFormat +{ + /// + /// Represents the time span in minutes. + /// + Minutes, + + /// + /// Represents the time span in hours. + /// + Hours, + + /// + /// Represents the time span in days. + /// + Days +} + +/// +/// Enum representing the effect of Mandatory flag. +/// +public enum Mode +{ + /// + /// In this mode, it ignores Remind Later and Skip values set previously and hide both buttons. + /// + [XmlEnum("0")] Normal, + + /// + /// In this mode, it won't show close button in addition to Normal mode behaviour. + /// + [XmlEnum("1")] Forced, + + /// + /// In this mode, it will start downloading and applying update without showing standard update dialog in addition to + /// Forced mode behaviour. + /// + [XmlEnum("2")] ForcedDownload +} + +/// +/// Main class that lets you auto update applications by setting some static fields and executing its Start method. +/// +public static class AutoUpdater +{ + /// + /// A delegate type to handle how to exit the application after update is downloaded. + /// + public delegate void ApplicationExitEventHandler(); + + /// + /// A delegate type for hooking up update notifications. + /// + /// + /// An object containing all the parameters received from AppCast XML file. If there will be an error + /// while looking for the XML file then this object will be null. + /// + public delegate void CheckForUpdateEventHandler(UpdateInfoEventArgs args); + + /// + /// A delegate type for hooking up parsing logic. + /// + /// An object containing the AppCast file received from server. + public delegate void ParseUpdateInfoHandler(ParseUpdateInfoEventArgs args); + + private static bool _isWinFormsApplication; + + private static IWin32Window _owner; + + private static Timer _remindLaterTimer; + + internal static Uri BaseUri; + + internal static bool Running; + + /// + /// URL of the xml file that contains information about latest version of the application. + /// + public static string AppCastURL; + + /// + /// Set the Application Title shown in Update dialog. Although AutoUpdater.NET will get it automatically, you can set + /// this property if you like to give custom Title. + /// + public static string AppTitle; + + /// + /// Set Basic Authentication credentials to navigate to the change log URL. + /// + public static IAuthentication BasicAuthChangeLog; + + /// + /// Set Basic Authentication credentials required to download the file. + /// + public static IAuthentication BasicAuthDownload; + + /// + /// Set Basic Authentication credentials required to download the XML file. + /// + public static IAuthentication BasicAuthXML; + + /// + /// Set this to true if you want to clear application directory before extracting update. + /// + public static bool ClearAppDirectory = false; + + /// + /// Set it to folder path where you want to download the update file. If not provided then it defaults to Temp folder. + /// + public static string DownloadPath; + + /// + /// If you are using a zip file as an update file, then you can set this value to a new executable path relative to the + /// installation directory. + /// + public static string ExecutablePath; + + /// + /// Login/password/domain for FTP-request + /// + public static NetworkCredential FtpCredentials; + + /// + /// Set the User-Agent string to be used for HTTP web requests. + /// + public static string HttpUserAgent; + + /// + /// Set this to change the icon shown on updater dialog. + /// + public static Bitmap Icon; + + /// + /// If you are using a zip file as an update file then you can set this value to path where your app is installed. This + /// is only necessary when your installation directory differs from your executable path. + /// + public static string InstallationPath; + + /// + /// You can set this field to your current version if you don't want to determine the version from the assembly. + /// + public static Version InstalledVersion; + + /// + /// If this is true users see dialog where they can set remind later interval otherwise it will take the interval from + /// RemindLaterAt and RemindLaterTimeSpan fields. + /// + public static bool LetUserSelectRemindLater = true; + + /// + /// Set this to true if you want to ignore previously assigned Remind Later and Skip settings. It will also hide Remind + /// Later and Skip buttons. + /// + public static bool Mandatory; + + /// + /// Opens the download URL in default browser if true. Very useful if you have portable application. + /// + public static bool OpenDownloadPage; + + /// + /// Set this to an instance implementing the IPersistenceProvider interface for using a data storage method different + /// from the default Windows Registry based one. + /// + public static IPersistenceProvider PersistenceProvider; + + /// + /// Set Proxy server to use for all the web requests in AutoUpdater.NET. + /// + public static IWebProxy Proxy; + + /// + /// Remind Later interval after user should be reminded of update. + /// + public static int RemindLaterAt = 2; + + /// + /// Set if RemindLaterAt interval should be in Minutes, Hours or Days. + /// + public static RemindLaterFormat RemindLaterTimeSpan = RemindLaterFormat.Days; + + /// + /// AutoUpdater.NET will report errors if this is true. + /// + public static bool ReportErrors = false; + + /// + /// Set this to false if your application doesn't need administrator privileges to replace the old version. + /// + public static bool RunUpdateAsAdmin = true; + + /// + /// If this is true users can see the Remind Later button. + /// + public static bool ShowRemindLaterButton = true; + + /// + /// If this is true users can see the skip button. + /// + public static bool ShowSkipButton = true; + + /// + /// Set this to true if you want to run update check synchronously. + /// + public static bool Synchronous = false; + + /// + /// Modify TopMost property of all dialogs. + /// + public static bool TopMost = false; + + /// + /// Set this if you want the default update form to have a different size. + /// + public static Size? UpdateFormSize = null; + + /// + /// Set this to any of the available modes to change behaviour of the Mandatory flag. + /// + public static Mode UpdateMode; + + /// + /// Set this to any of the available types to change the changelog viewer. + /// + public static ChangelogViewerType ChangelogViewerType = ChangelogViewerFactory.GetDefaultViewerType(); + + /// + /// An event that developers can use to exit the application gracefully. + /// + public static event ApplicationExitEventHandler ApplicationExitEvent; + + /// + /// An event that clients can use to be notified whenever the update is checked. + /// + public static event CheckForUpdateEventHandler CheckForUpdateEvent; + + /// + /// An event that clients can use to be notified whenever the AppCast file needs parsing. + /// + public static event ParseUpdateInfoHandler ParseUpdateInfoEvent; + + /// + /// Set the owner for all dialogs. + /// + /// WPF Window or Windows Form object to be used as owner for all dialogs. + public static void SetOwner(object obj) + { + _owner = obj switch + { + Form form => form, + Window window => new Wpf32Window(window), + _ => _owner + }; + } + + /// + /// Start checking for new version of application and display a dialog to the user if update is available. + /// + /// Assembly to use for version checking. + public static void Start(Assembly myAssembly = null) + { + Start(AppCastURL, myAssembly); + } + + /// + /// Start checking for new version of application via FTP and display a dialog to the user if update is available. + /// + /// FTP URL of the xml file that contains information about latest version of the application. + /// Credentials required to connect to FTP server. + /// Assembly to use for version checking. + public static void Start(string appCast, NetworkCredential ftpCredentials, Assembly myAssembly = null) + { + FtpCredentials = ftpCredentials; + Start(appCast, myAssembly); + } + + /// + /// Start checking for new version of application and display a dialog to the user if update is available. + /// + /// URL of the xml file that contains information about latest version of the application. + /// Assembly to use for version checking. + public static void Start(string appCast, Assembly myAssembly = null) + { + try + { + ServicePointManager.SecurityProtocol |= (SecurityProtocolType)192 | + (SecurityProtocolType)768 | (SecurityProtocolType)3072; + } + catch (NotSupportedException) + { + } + + if (Mandatory && _remindLaterTimer != null) + { + _remindLaterTimer.Stop(); + _remindLaterTimer.Close(); + _remindLaterTimer = null; + } + + if (Running || _remindLaterTimer != null) + { + return; + } + + Running = true; + + AppCastURL = appCast; + + _isWinFormsApplication = Application.MessageLoop; + + if (!_isWinFormsApplication) + { + Application.EnableVisualStyles(); + } + + Assembly assembly = myAssembly ?? Assembly.GetEntryAssembly(); + + if (Synchronous) + { + try + { + object result = CheckUpdate(assembly); + + if (StartUpdate(result)) + { + return; + } + + Running = false; + } + catch (Exception exception) + { + ShowError(exception); + } + } + else + { + using var backgroundWorker = new BackgroundWorker(); + + backgroundWorker.DoWork += (_, args) => + { + var mainAssembly = args.Argument as Assembly; + + args.Result = CheckUpdate(mainAssembly); + }; + + backgroundWorker.RunWorkerCompleted += (_, args) => + { + if (args.Error != null) + { + ShowError(args.Error); + } + else + { + if (!args.Cancelled && StartUpdate(args.Result)) + { + return; + } + + Running = false; + } + }; + + backgroundWorker.RunWorkerAsync(assembly); + } + } + + private static object CheckUpdate(Assembly mainAssembly) + { + var companyAttribute = + (AssemblyCompanyAttribute)GetAttribute(mainAssembly, typeof(AssemblyCompanyAttribute)); + string appCompany = companyAttribute != null ? companyAttribute.Company : ""; + + if (string.IsNullOrEmpty(AppTitle)) + { + var titleAttribute = + (AssemblyTitleAttribute)GetAttribute(mainAssembly, typeof(AssemblyTitleAttribute)); + AppTitle = titleAttribute != null ? titleAttribute.Title : mainAssembly.GetName().Name; + } + + string registryLocation = !string.IsNullOrEmpty(appCompany) + ? $@"Software\{appCompany}\{AppTitle}\AutoUpdater" + : $@"Software\{AppTitle}\AutoUpdater"; + + PersistenceProvider ??= new RegistryPersistenceProvider(registryLocation); + + UpdateInfoEventArgs args; + string xml = null; + + if (AppCastURL != null) + { + BaseUri = new Uri(AppCastURL); + using MyWebClient client = GetWebClient(BaseUri, BasicAuthXML); + xml = client.DownloadString(BaseUri); + } + + if (ParseUpdateInfoEvent == null) + { + if (string.IsNullOrEmpty(xml)) + { + throw new Exception("It is required to handle ParseUpdateInfoEvent when XML url is not specified."); + } + + var xmlSerializer = new XmlSerializer(typeof(UpdateInfoEventArgs)); + var xmlTextReader = new XmlTextReader(new StringReader(xml)) { XmlResolver = null }; + args = (UpdateInfoEventArgs)xmlSerializer.Deserialize(xmlTextReader); + } + else + { + var parseArgs = new ParseUpdateInfoEventArgs(xml); + ParseUpdateInfoEvent(parseArgs); + args = parseArgs.UpdateInfo; + } + + if (string.IsNullOrEmpty(args?.CurrentVersion) || string.IsNullOrEmpty(args.DownloadURL)) + { + throw new MissingFieldException(); + } + + args.InstalledVersion = InstalledVersion ?? mainAssembly.GetName().Version; + args.IsUpdateAvailable = new Version(args.CurrentVersion) > args.InstalledVersion; + + if (!Mandatory) + { + if (string.IsNullOrEmpty(args.Mandatory.MinimumVersion) || + args.InstalledVersion < new Version(args.Mandatory.MinimumVersion)) + { + Mandatory = args.Mandatory.Value; + UpdateMode = args.Mandatory.UpdateMode; + } + } + + if (Mandatory) + { + ShowRemindLaterButton = false; + ShowSkipButton = false; + } + else + { + // Read the persisted state from the persistence provider. + // This method makes the persistence handling independent from the storage method. + Version skippedVersion = PersistenceProvider.GetSkippedVersion(); + if (skippedVersion != null) + { + var currentVersion = new Version(args.CurrentVersion); + if (currentVersion <= skippedVersion) + { + return null; + } + + if (currentVersion > skippedVersion) + { + // Update the persisted state. Its no longer makes sense to have this flag set as we are working on a newer application version. + PersistenceProvider.SetSkippedVersion(null); + } + } + + DateTime? remindLaterAt = PersistenceProvider.GetRemindLater(); + if (remindLaterAt == null) + { + return args; + } + + int compareResult = DateTime.Compare(DateTime.Now, remindLaterAt.Value); + + if (compareResult < 0) + { + return remindLaterAt.Value; + } + } + + return args; + } + + private static bool StartUpdate(object result) + { + if (result is DateTime time) + { + SetTimer(time); + } + else + { + if (result is not UpdateInfoEventArgs args) + { + return false; + } + + if (CheckForUpdateEvent != null) + { + CheckForUpdateEvent(args); + } + else + { + if (args.IsUpdateAvailable) + { + if (Mandatory && UpdateMode == Mode.ForcedDownload) + { + DownloadUpdate(args); + Exit(); + } + else + { + if (Thread.CurrentThread.GetApartmentState().Equals(ApartmentState.STA)) + { + ShowUpdateForm(args); + } + else + { + var thread = new Thread(new ThreadStart(delegate { ShowUpdateForm(args); })); + thread.CurrentCulture = + thread.CurrentUICulture = CultureInfo.CurrentCulture; + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + } + } + + return true; + } + + if (ReportErrors) + { + MessageBox.Show(_owner, + Resources.UpdateUnavailableMessage, + Resources.UpdateUnavailableCaption, + MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + } + + return false; + } + + private static void ShowError(Exception exception) + { + if (CheckForUpdateEvent != null) + { + CheckForUpdateEvent(new UpdateInfoEventArgs { Error = exception }); + } + else + { + if (ReportErrors) + { + if (exception is WebException) + { + MessageBox.Show(_owner, + Resources.UpdateCheckFailedMessage, + Resources.UpdateCheckFailedCaption, + MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + MessageBox.Show(_owner, + exception.Message, + exception.GetType().ToString(), + MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + Running = false; + } + + /// + /// Detects and exits all instances of running assembly, including current. + /// + internal static void Exit() + { + var currentProcess = Process.GetCurrentProcess(); + foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName)) + { + try + { + string processPath = process.MainModule?.FileName; + + // Get all instances of assembly except current + if (process.Id == currentProcess.Id || currentProcess.MainModule?.FileName != processPath) + { + continue; + } + + if (process.CloseMainWindow()) + { + process.WaitForExit((int)TimeSpan.FromSeconds(10) + .TotalMilliseconds); // Give some time to process message + } + + if (process.HasExited) + { + continue; + } + + process.Kill(); //TODO: Show UI message asking user to close program himself instead of silently killing it + } + catch (Exception) + { + // ignored + } + } + + if (ApplicationExitEvent != null) + { + ApplicationExitEvent(); + } + else + { + if (_isWinFormsApplication) + { + WinFormsMethodInvoker methodInvoker = Application.Exit; + methodInvoker.Invoke(); + } + else if (System.Windows.Application.Current != null) + { + System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => + System.Windows.Application.Current.Shutdown())); + } + else + { + Environment.Exit(0); + } + } + } + + private static Attribute GetAttribute(Assembly assembly, Type attributeType) + { + object[] attributes = assembly.GetCustomAttributes(attributeType, false); + if (attributes.Length == 0) + { + return null; + } + + return (Attribute)attributes[0]; + } + + internal static string GetUserAgent() + { + return string.IsNullOrEmpty(HttpUserAgent) ? "AutoUpdater.NET" : HttpUserAgent; + } + + internal static void SetTimer(DateTime remindLater) + { + TimeSpan timeSpan = remindLater - DateTime.Now; + + SynchronizationContext context = SynchronizationContext.Current; + + _remindLaterTimer = new Timer + { + Interval = Math.Max(1, timeSpan.TotalMilliseconds), + AutoReset = false + }; + + _remindLaterTimer.Elapsed += delegate + { + _remindLaterTimer = null; + if (context != null) + { + try + { + context.Send(_ => Start(), null); + } + catch (InvalidAsynchronousStateException) + { + Start(); + } + } + else + { + Start(); + } + }; + + _remindLaterTimer.Start(); + } + + /// + /// Opens the Download window that download the update and execute the installer when download completes. + /// + public static bool DownloadUpdate(UpdateInfoEventArgs args) + { + using var downloadDialog = new DownloadUpdateDialog(args); + + try + { + return downloadDialog.ShowDialog(_owner).Equals(DialogResult.OK); + } + catch (TargetInvocationException) + { + // ignored + } + + return false; + } + + /// + /// Shows standard update dialog. + /// + public static void ShowUpdateForm(UpdateInfoEventArgs args) + { + using var updateForm = new UpdateForm(args); + + if (UpdateFormSize.HasValue) + { + updateForm.Size = UpdateFormSize.Value; + } + + if (updateForm.ShowDialog(_owner).Equals(DialogResult.OK)) + { + Exit(); + } + } + + internal static MyWebClient GetWebClient(Uri uri, IAuthentication basicAuthentication) + { + var webClient = new MyWebClient + { + CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore) + }; + + if (Proxy != null) + { + webClient.Proxy = Proxy; + } + + if (uri.Scheme.Equals(Uri.UriSchemeFtp)) + { + webClient.Credentials = FtpCredentials; + } + else + { + basicAuthentication?.Apply(ref webClient); + + webClient.Headers[HttpRequestHeader.UserAgent] = HttpUserAgent; + } + + return webClient; + } } \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs new file mode 100644 index 00000000..e705ffe3 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs @@ -0,0 +1,26 @@ +using System; + +namespace AutoUpdaterDotNET.ChangelogViewers; + +public static class ChangelogViewerFactory +{ + public static IChangelogViewer Create(ChangelogViewerType type) + { + return type switch + { + ChangelogViewerType.RichTextBox => new RichTextBoxViewer(), + ChangelogViewerType.WebBrowser => new WebBrowserViewer(), + ChangelogViewerType.WebView2 when WebView2Viewer.IsAvailable() => new WebView2Viewer(), + ChangelogViewerType.WebView2 => throw new InvalidOperationException("WebView2 runtime is not available"), + _ => throw new ArgumentException($"Unknown viewer type: {type}") + }; + } + + public static ChangelogViewerType GetDefaultViewerType() + { + if (WebView2Viewer.IsAvailable()) + return ChangelogViewerType.WebView2; + + return ChangelogViewerType.WebBrowser; + } +} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs new file mode 100644 index 00000000..10e03c82 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs @@ -0,0 +1,8 @@ +namespace AutoUpdaterDotNET.ChangelogViewers; + +public enum ChangelogViewerType +{ + RichTextBox, + WebBrowser, + WebView2 +} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs b/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs new file mode 100644 index 00000000..5a95b550 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs @@ -0,0 +1,12 @@ +using System.Windows.Forms; + +namespace AutoUpdaterDotNET.ChangelogViewers; + +public interface IChangelogViewer +{ + Control Control { get; } + bool SupportsUrl { get; } + void LoadContent(string content); + void LoadUrl(string url); + void Cleanup(); +} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs new file mode 100644 index 00000000..2d8fba93 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs @@ -0,0 +1,31 @@ +using System.Windows.Forms; + +namespace AutoUpdaterDotNET.ChangelogViewers; + +public class RichTextBoxViewer : IChangelogViewer +{ + private readonly RichTextBox _richTextBox = new() + { + ReadOnly = true, + Dock = DockStyle.Fill, + BackColor = System.Drawing.SystemColors.Control, + BorderStyle = BorderStyle.Fixed3D + }; + public Control Control => _richTextBox; + public bool SupportsUrl => false; + + public void LoadContent(string content) + { + _richTextBox.Text = content; + } + + public void LoadUrl(string url) + { + throw new System.NotSupportedException("RichTextBox does not support loading from URL"); + } + + public void Cleanup() + { + _richTextBox.Dispose(); + } +} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs new file mode 100644 index 00000000..abf3df84 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs @@ -0,0 +1,91 @@ +using System.IO; +using System.Diagnostics; +using System.Windows.Forms; +using Microsoft.Win32; + +namespace AutoUpdaterDotNET.ChangelogViewers; + +public class WebBrowserViewer : IChangelogViewer +{ + private const string EmulationKey = @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION"; + private readonly int _emulationValue; + private readonly string _executableName; + private readonly WebBrowser _webBrowser; + public Control Control => _webBrowser; + public bool SupportsUrl => true; + + public WebBrowserViewer() + { + _webBrowser = new WebBrowser + { + Dock = DockStyle.Fill, + ScriptErrorsSuppressed = true, + AllowWebBrowserDrop = false, + WebBrowserShortcutsEnabled = false, + IsWebBrowserContextMenuEnabled = false + }; + + _executableName = Path.GetFileName( + Process.GetCurrentProcess().MainModule?.FileName + ?? System.Reflection.Assembly.GetEntryAssembly()?.Location + ?? Application.ExecutablePath); + + _emulationValue = _webBrowser.Version.Major switch + { + 11 => 11001, + 10 => 10001, + 9 => 9999, + 8 => 8888, + 7 => 7000, + _ => 0 + }; + + SetupEmulation(); + } + + public void LoadContent(string content) => _webBrowser.DocumentText = content; + + public void LoadUrl(string url) + { + if (AutoUpdater.BasicAuthChangeLog != null) + _webBrowser.Navigate(url, "", null, $"Authorization: {AutoUpdater.BasicAuthChangeLog}"); + else + _webBrowser.Navigate(url); + } + private void SetupEmulation() + { + if (_emulationValue == 0) + return; + + try + { + using var registryKey = Registry.CurrentUser.OpenSubKey(EmulationKey, true); + registryKey?.SetValue(_executableName, _emulationValue, RegistryValueKind.DWord); + } + catch + { + // ignored + } + } + private void RemoveEmulation() + { + if (_emulationValue == 0) + return; + + try + { + using var registryKey = Registry.CurrentUser.OpenSubKey(EmulationKey, true); + registryKey?.DeleteValue(_executableName, false); + } + catch + { + // ignored + } + } + + public void Cleanup() + { + _webBrowser.Dispose(); + RemoveEmulation(); + } +} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs b/AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs new file mode 100644 index 00000000..ebc239a7 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using System.Windows.Forms; +using System.Threading.Tasks; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.WinForms; + +namespace AutoUpdaterDotNET.ChangelogViewers; + +public class WebView2Viewer : IChangelogViewer +{ + private bool _isInitialized; + private readonly WebView2 _webView = new() + { + Dock = DockStyle.Fill, + AllowExternalDrop = false + }; + public Control Control => _webView; + public bool SupportsUrl => true; + + private async Task EnsureInitialized() + { + if (_isInitialized) return; + + try + { + await _webView.EnsureCoreWebView2Async(await CoreWebView2Environment.CreateAsync(null, Path.GetTempPath())); + _isInitialized = true; + } + catch (Exception) + { + throw new InvalidOperationException("WebView2 runtime is not available"); + } + } + + public async void LoadContent(string content) + { + await EnsureInitialized(); + _webView.CoreWebView2.SetVirtualHostNameToFolderMapping("local.files", Path.GetTempPath(), CoreWebView2HostResourceAccessKind.Allow); + + // Write content to a temporary HTML file + var tempFile = Path.Combine(Path.GetTempPath(), "changelog.html"); + File.WriteAllText(tempFile, content); + + // Navigate to the local file + _webView.CoreWebView2.Navigate("https://local.files/changelog.html"); + } + + public async void LoadUrl(string url) + { + await EnsureInitialized(); + + if (AutoUpdater.BasicAuthChangeLog != null) + { + _webView.CoreWebView2.BasicAuthenticationRequested += delegate (object _, CoreWebView2BasicAuthenticationRequestedEventArgs args) + { + args.Response.UserName = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Username; + args.Response.Password = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Password; + }; + } + + _webView.CoreWebView2.Navigate(url); + } + + public void Cleanup() + { + if (File.Exists(Path.Combine(Path.GetTempPath(), "changelog.html"))) + { + try + { + File.Delete(Path.Combine(Path.GetTempPath(), "changelog.html")); + } + catch + { + // Ignore deletion errors + } + } + _webView.Dispose(); + } + + public static bool IsAvailable() + { + try + { + var availableBrowserVersion = CoreWebView2Environment.GetAvailableBrowserVersionString(null); + const string requiredMinBrowserVersion = "86.0.616.0"; + return !string.IsNullOrEmpty(availableBrowserVersion) && + CoreWebView2Environment.CompareBrowserVersions(availableBrowserVersion, + requiredMinBrowserVersion) >= 0; + } + catch (Exception) + { + return false; + } + } +} \ No newline at end of file diff --git a/AutoUpdater.NET/UpdateForm.Designer.cs b/AutoUpdater.NET/UpdateForm.Designer.cs index 3d84968a..bfdd6296 100644 --- a/AutoUpdater.NET/UpdateForm.Designer.cs +++ b/AutoUpdater.NET/UpdateForm.Designer.cs @@ -9,19 +9,6 @@ sealed partial class UpdateForm /// private System.ComponentModel.IContainer components = null; - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - #region Windows Form Designer generated code /// @@ -31,7 +18,6 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdateForm)); - this.webBrowser = new System.Windows.Forms.WebBrowser(); this.labelUpdate = new System.Windows.Forms.Label(); this.labelDescription = new System.Windows.Forms.Label(); this.labelReleaseNotes = new System.Windows.Forms.Label(); @@ -39,17 +25,10 @@ private void InitializeComponent() this.buttonRemindLater = new System.Windows.Forms.Button(); this.pictureBoxIcon = new System.Windows.Forms.PictureBox(); this.buttonSkip = new System.Windows.Forms.Button(); - this.webView2 = new Microsoft.Web.WebView2.WinForms.WebView2(); + this.pnlChangelog = new System.Windows.Forms.Panel(); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxIcon)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.webView2)).BeginInit(); this.SuspendLayout(); // - // webBrowser - // - resources.ApplyResources(this.webBrowser, "webBrowser"); - this.webBrowser.Name = "webBrowser"; - this.webBrowser.ScriptErrorsSuppressed = true; - // // labelUpdate // resources.ApplyResources(this.labelUpdate, "labelUpdate"); @@ -97,14 +76,10 @@ private void InitializeComponent() this.buttonSkip.UseVisualStyleBackColor = true; this.buttonSkip.Click += new System.EventHandler(this.ButtonSkipClick); // - // webView2 + // pnlChangelog // - this.webView2.AllowExternalDrop = true; - this.webView2.CreationProperties = null; - this.webView2.DefaultBackgroundColor = System.Drawing.Color.White; - resources.ApplyResources(this.webView2, "webView2"); - this.webView2.Name = "webView2"; - this.webView2.ZoomFactor = 1D; + resources.ApplyResources(this.pnlChangelog, "pnlChangelog"); + this.pnlChangelog.Name = "pnlChangelog"; // // UpdateForm // @@ -118,8 +93,7 @@ private void InitializeComponent() this.Controls.Add(this.buttonUpdate); this.Controls.Add(this.buttonSkip); this.Controls.Add(this.buttonRemindLater); - this.Controls.Add(this.webView2); - this.Controls.Add(this.webBrowser); + this.Controls.Add(this.pnlChangelog); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.MinimizeBox = false; @@ -128,7 +102,6 @@ private void InitializeComponent() this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.UpdateForm_FormClosed); this.Load += new System.EventHandler(this.UpdateFormLoad); ((System.ComponentModel.ISupportInitialize)(this.pictureBoxIcon)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.webView2)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -139,11 +112,10 @@ private void InitializeComponent() private System.Windows.Forms.Button buttonRemindLater; private System.Windows.Forms.Button buttonUpdate; private System.Windows.Forms.Button buttonSkip; - private System.Windows.Forms.WebBrowser webBrowser; private System.Windows.Forms.Label labelUpdate; private System.Windows.Forms.Label labelDescription; private System.Windows.Forms.Label labelReleaseNotes; private System.Windows.Forms.PictureBox pictureBoxIcon; - private Microsoft.Web.WebView2.WinForms.WebView2 webView2; + private System.Windows.Forms.Panel pnlChangelog; } } \ No newline at end of file diff --git a/AutoUpdater.NET/UpdateForm.cs b/AutoUpdater.NET/UpdateForm.cs index cc33d13d..018af80c 100644 --- a/AutoUpdater.NET/UpdateForm.cs +++ b/AutoUpdater.NET/UpdateForm.cs @@ -1,254 +1,172 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Windows.Forms; -using Microsoft.Web.WebView2.Core; -using Microsoft.Win32; -using static System.Reflection.Assembly; - -namespace AutoUpdaterDotNET; - -internal sealed partial class UpdateForm : Form -{ - private readonly UpdateInfoEventArgs _args; - - public UpdateForm(UpdateInfoEventArgs args) - { - _args = args; - InitializeComponent(); - InitializeBrowserControl(); - TopMost = AutoUpdater.TopMost; - - if (AutoUpdater.Icon != null) - { - pictureBoxIcon.Image = AutoUpdater.Icon; - Icon = Icon.FromHandle(AutoUpdater.Icon.GetHicon()); - } - - buttonSkip.Visible = AutoUpdater.ShowSkipButton; - buttonRemindLater.Visible = AutoUpdater.ShowRemindLaterButton; - var resources = new ComponentResourceManager(typeof(UpdateForm)); - Text = string.Format(resources.GetString("$this.Text", CultureInfo.CurrentCulture)!, - AutoUpdater.AppTitle, _args.CurrentVersion); - labelUpdate.Text = string.Format(resources.GetString("labelUpdate.Text", CultureInfo.CurrentCulture)!, - AutoUpdater.AppTitle); - labelDescription.Text = - string.Format(resources.GetString("labelDescription.Text", CultureInfo.CurrentCulture)!, - AutoUpdater.AppTitle, _args.CurrentVersion, _args.InstalledVersion); - - if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced) - { - ControlBox = false; - } - } - - private async void InitializeBrowserControl() - { - if (string.IsNullOrEmpty(_args.ChangelogURL)) - { - int reduceHeight = labelReleaseNotes.Height + webBrowser.Height; - labelReleaseNotes.Hide(); - webBrowser.Hide(); - webView2.Hide(); - Height -= reduceHeight; - } - else - { - var webView2RuntimeFound = false; - try - { - string availableBrowserVersion = CoreWebView2Environment.GetAvailableBrowserVersionString(null); - var requiredMinBrowserVersion = "86.0.616.0"; - if (!string.IsNullOrEmpty(availableBrowserVersion) - && CoreWebView2Environment.CompareBrowserVersions(availableBrowserVersion, - requiredMinBrowserVersion) >= 0) - { - webView2RuntimeFound = true; - } - } - catch (Exception) - { - // ignored - } - - if (webView2RuntimeFound) - { - webBrowser.Hide(); - webView2.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted; - await webView2.EnsureCoreWebView2Async( - await CoreWebView2Environment.CreateAsync(null, Path.GetTempPath())); - } - else - { - UseLatestIE(); - if (null != AutoUpdater.BasicAuthChangeLog) - { - webBrowser.Navigate(_args.ChangelogURL, "", null, - $"Authorization: {AutoUpdater.BasicAuthChangeLog}"); - } - else - { - webBrowser.Navigate(_args.ChangelogURL); - } - } - } - } - - private void WebView_CoreWebView2InitializationCompleted(object sender, - CoreWebView2InitializationCompletedEventArgs e) - { - if (!e.IsSuccess) - { - if (AutoUpdater.ReportErrors) - { - MessageBox.Show(this, e.InitializationException.Message, e.InitializationException.GetType().ToString(), - MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - return; - } - - webView2.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; - webView2.CoreWebView2.Settings.IsStatusBarEnabled = false; - webView2.CoreWebView2.Settings.AreDevToolsEnabled = Debugger.IsAttached; - webView2.CoreWebView2.Settings.UserAgent = AutoUpdater.GetUserAgent(); - webView2.CoreWebView2.Profile.ClearBrowsingDataAsync(); - webView2.Show(); - webView2.BringToFront(); - if (null != AutoUpdater.BasicAuthChangeLog) - { - webView2.CoreWebView2.BasicAuthenticationRequested += delegate( - object _, - CoreWebView2BasicAuthenticationRequestedEventArgs args) - { - args.Response.UserName = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Username; - args.Response.Password = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Password; - }; - } - - webView2.CoreWebView2.Navigate(_args.ChangelogURL); - } - - private void UseLatestIE() - { - int ieValue = webBrowser.Version.Major switch - { - 11 => 11001, - 10 => 10001, - 9 => 9999, - 8 => 8888, - 7 => 7000, - _ => 0 - }; - - if (ieValue == 0) - { - return; - } - - try - { - using RegistryKey registryKey = - Registry.CurrentUser.OpenSubKey( - @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION", - true); - registryKey?.SetValue( - Path.GetFileName(Process.GetCurrentProcess().MainModule?.FileName ?? - GetEntryAssembly()?.Location ?? Application.ExecutablePath), - ieValue, - RegistryValueKind.DWord); - } - catch (Exception) - { - // ignored - } - } - - private void UpdateFormLoad(object sender, EventArgs e) - { - var labelSize = new Size(webBrowser.Width, 0); - labelDescription.MaximumSize = labelUpdate.MaximumSize = labelSize; - } - - private void ButtonUpdateClick(object sender, EventArgs e) - { - if (AutoUpdater.OpenDownloadPage) - { - - var processStartInfo = new ProcessStartInfo(_args.DownloadURL); -#if NETCOREAPP - // for .NET Core, UseShellExecute must be set to true, otherwise - // opening URLs via Process.Start() fails - processStartInfo.UseShellExecute = true; -#endif - Process.Start(processStartInfo); - - DialogResult = DialogResult.OK; - } - else - { - if (AutoUpdater.DownloadUpdate(_args)) - { - DialogResult = DialogResult.OK; - } - } - } - - private void ButtonSkipClick(object sender, EventArgs e) - { - AutoUpdater.PersistenceProvider.SetSkippedVersion(new Version(_args.CurrentVersion)); - } - - private void ButtonRemindLaterClick(object sender, EventArgs e) - { - if (AutoUpdater.LetUserSelectRemindLater) - { - using var remindLaterForm = new RemindLaterForm(); - DialogResult dialogResult = remindLaterForm.ShowDialog(this); - - switch (dialogResult) - { - case DialogResult.OK: - AutoUpdater.RemindLaterTimeSpan = remindLaterForm.RemindLaterFormat; - AutoUpdater.RemindLaterAt = remindLaterForm.RemindLaterAt; - break; - case DialogResult.Abort: - ButtonUpdateClick(sender, e); - return; - default: - return; - } - } - - AutoUpdater.PersistenceProvider.SetSkippedVersion(null); - - DateTime remindLaterDateTime = AutoUpdater.RemindLaterTimeSpan switch - { - RemindLaterFormat.Days => DateTime.Now + TimeSpan.FromDays(AutoUpdater.RemindLaterAt), - RemindLaterFormat.Hours => DateTime.Now + TimeSpan.FromHours(AutoUpdater.RemindLaterAt), - RemindLaterFormat.Minutes => DateTime.Now + TimeSpan.FromMinutes(AutoUpdater.RemindLaterAt), - _ => DateTime.Now - }; - - AutoUpdater.PersistenceProvider.SetRemindLater(remindLaterDateTime); - AutoUpdater.SetTimer(remindLaterDateTime); - - DialogResult = DialogResult.Cancel; - } - - private void UpdateForm_FormClosed(object sender, FormClosedEventArgs e) - { - AutoUpdater.Running = false; - } - - private void UpdateForm_FormClosing(object sender, FormClosingEventArgs e) - { - if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced) - { - AutoUpdater.Exit(); - } - } +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.Windows.Forms; +using AutoUpdaterDotNET.ChangelogViewers; + +namespace AutoUpdaterDotNET; + +internal sealed partial class UpdateForm : Form +{ + private readonly UpdateInfoEventArgs _args; + private IChangelogViewer _changelogViewer; + + public UpdateForm(UpdateInfoEventArgs args) + { + _args = args; + InitializeComponent(); + InitializeChangelogViewer(); + TopMost = AutoUpdater.TopMost; + + if (AutoUpdater.Icon != null) + { + pictureBoxIcon.Image = AutoUpdater.Icon; + Icon = Icon.FromHandle(AutoUpdater.Icon.GetHicon()); + } + + buttonSkip.Visible = AutoUpdater.ShowSkipButton; + buttonRemindLater.Visible = AutoUpdater.ShowRemindLaterButton; + var resources = new ComponentResourceManager(typeof(UpdateForm)); + Text = string.Format(resources.GetString("$this.Text", CultureInfo.CurrentCulture)!, + AutoUpdater.AppTitle, _args.CurrentVersion); + labelUpdate.Text = string.Format(resources.GetString("labelUpdate.Text", CultureInfo.CurrentCulture)!, + AutoUpdater.AppTitle); + labelDescription.Text = + string.Format(resources.GetString("labelDescription.Text", CultureInfo.CurrentCulture)!, + AutoUpdater.AppTitle, _args.CurrentVersion, _args.InstalledVersion); + + if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced) + { + ControlBox = false; + } + } + + private void InitializeChangelogViewer() + { + if (string.IsNullOrEmpty(_args.ChangelogURL) && string.IsNullOrEmpty(_args.ChangelogText)) + { + var reduceHeight = labelReleaseNotes.Height + pnlChangelog.Height; + labelReleaseNotes.Hide(); + Height -= reduceHeight; + return; + } + + // Create and configure the new viewer + _changelogViewer = ChangelogViewerFactory.Create(AutoUpdater.ChangelogViewerType); + var viewerControl = _changelogViewer.Control; + viewerControl.Dock = DockStyle.Fill; + pnlChangelog.Controls.Add(viewerControl); + + if (!string.IsNullOrEmpty(_args.ChangelogText)) + { + _changelogViewer.LoadContent(_args.ChangelogText); + } + else if (_changelogViewer.SupportsUrl && !string.IsNullOrEmpty(_args.ChangelogURL)) + { + _changelogViewer.LoadUrl(_args.ChangelogURL); + } + else + { + labelReleaseNotes.Hide(); + viewerControl.Hide(); + Height -= (labelReleaseNotes.Height + viewerControl.Height); + } + } + + private void UpdateFormLoad(object sender, EventArgs e) + { + var labelSize = new Size(pnlChangelog.Width, 0); + labelDescription.MaximumSize = labelUpdate.MaximumSize = labelSize; + } + + private void ButtonUpdateClick(object sender, EventArgs e) + { + if (AutoUpdater.OpenDownloadPage) + { + + var processStartInfo = new ProcessStartInfo(_args.DownloadURL); +#if NETCOREAPP + // for .NET Core, UseShellExecute must be set to true, otherwise + // opening URLs via Process.Start() fails + processStartInfo.UseShellExecute = true; +#endif + Process.Start(processStartInfo); + + DialogResult = DialogResult.OK; + } + else + { + if (AutoUpdater.DownloadUpdate(_args)) + { + DialogResult = DialogResult.OK; + } + } + } + + private void ButtonSkipClick(object sender, EventArgs e) + { + AutoUpdater.PersistenceProvider.SetSkippedVersion(new Version(_args.CurrentVersion)); + } + + private void ButtonRemindLaterClick(object sender, EventArgs e) + { + if (AutoUpdater.LetUserSelectRemindLater) + { + using var remindLaterForm = new RemindLaterForm(); + DialogResult dialogResult = remindLaterForm.ShowDialog(this); + + switch (dialogResult) + { + case DialogResult.OK: + AutoUpdater.RemindLaterTimeSpan = remindLaterForm.RemindLaterFormat; + AutoUpdater.RemindLaterAt = remindLaterForm.RemindLaterAt; + break; + case DialogResult.Abort: + ButtonUpdateClick(sender, e); + return; + default: + return; + } + } + + AutoUpdater.PersistenceProvider.SetSkippedVersion(null); + + DateTime remindLaterDateTime = AutoUpdater.RemindLaterTimeSpan switch + { + RemindLaterFormat.Days => DateTime.Now + TimeSpan.FromDays(AutoUpdater.RemindLaterAt), + RemindLaterFormat.Hours => DateTime.Now + TimeSpan.FromHours(AutoUpdater.RemindLaterAt), + RemindLaterFormat.Minutes => DateTime.Now + TimeSpan.FromMinutes(AutoUpdater.RemindLaterAt), + _ => DateTime.Now + }; + + AutoUpdater.PersistenceProvider.SetRemindLater(remindLaterDateTime); + AutoUpdater.SetTimer(remindLaterDateTime); + + DialogResult = DialogResult.Cancel; + } + + private void UpdateForm_FormClosed(object sender, FormClosedEventArgs e) + { + AutoUpdater.Running = false; + } + + private void UpdateForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (AutoUpdater.Mandatory && AutoUpdater.UpdateMode == Mode.Forced) + { + AutoUpdater.Exit(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _changelogViewer?.Cleanup(); + components?.Dispose(); + } + base.Dispose(disposing); + } } \ No newline at end of file diff --git a/AutoUpdater.NET/UpdateForm.resx b/AutoUpdater.NET/UpdateForm.resx index 58a31560..38729b0d 100644 --- a/AutoUpdater.NET/UpdateForm.resx +++ b/AutoUpdater.NET/UpdateForm.resx @@ -125,37 +125,37 @@ - + Top, Bottom, Left, Right - + 94, 120 - + 2, 2, 2, 2 - + 23, 23 - + 538, 432 - + 4 - - webBrowser + + pnlChangelog - - System.Windows.Forms.WebBrowser, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + $this - + 8 @@ -395,39 +395,6 @@ 5 - - - Top, Bottom, Left, Right - - - 94, 120 - - - 2, 2, 2, 2 - - - 23, 23 - - - 538, 432 - - - 3 - - - False - - - webView2 - - - Microsoft.Web.WebView2.WinForms.WebView2, Microsoft.Web.WebView2.WinForms, Version=1.0.1210.39, Culture=neutral, PublicKeyToken=2a8ab48044d2601e - - - $this - - - 7 diff --git a/AutoUpdater.NET/UpdateInfoEventArgs.cs b/AutoUpdater.NET/UpdateInfoEventArgs.cs index ba326437..b795a653 100644 --- a/AutoUpdater.NET/UpdateInfoEventArgs.cs +++ b/AutoUpdater.NET/UpdateInfoEventArgs.cs @@ -49,6 +49,12 @@ public string ChangelogURL set => _changelogURL = value; } + /// + /// Returns text specifying changes in the new update. + /// + [XmlElement("changelogText")] + public string ChangelogText { get; set; } + /// /// Returns newest version of the application available to download. /// diff --git a/AutoUpdaterTest/MainWindow.xaml.cs b/AutoUpdaterTest/MainWindow.xaml.cs index 4e3e35ff..08536123 100644 --- a/AutoUpdaterTest/MainWindow.xaml.cs +++ b/AutoUpdaterTest/MainWindow.xaml.cs @@ -25,33 +25,6 @@ public MainWindow() private void ButtonCheckForUpdate_Click(object sender, RoutedEventArgs e) { - // Uncomment following lines to handle parsing logic of custom AppCast file. - // AutoUpdater.ParseUpdateInfoEvent += args => - // { - // dynamic? json = JsonConvert.DeserializeObject(args.RemoteData); - // if (json != null) - // { - // args.UpdateInfo = new UpdateInfoEventArgs - // { - // CurrentVersion = json.version, - // ChangelogURL = json.changelog, - // DownloadURL = json.url, - // Mandatory = new Mandatory - // { - // Value = json.mandatory.value, - // UpdateMode = json.mandatory.mode, - // MinimumVersion = json.mandatory.minVersion - // }, - // CheckSum = new CheckSum - // { - // Value = json.checksum.value, - // HashingAlgorithm = json.checksum.hashingAlgorithm - // } - // }; - // } - // }; - // AutoUpdater.Start("https://rbsoft.org/updates/AutoUpdaterTest.json"); - // Uncomment following line to run update process without admin privileges. // AutoUpdater.RunUpdateAsAdmin = false; @@ -200,6 +173,45 @@ private void ButtonCheckForUpdate_Click(object sender, RoutedEventArgs e) // Uncomment following line to change the Icon shown on the updater dialog. AutoUpdater.Icon = Resource.Icon; + // Uncomment following line to change the Changelog viewer type. + //AutoUpdater.ChangelogViewerType = ChangelogViewerType.RichTextBox; + + // Uncomment following lines to handle parsing logic of custom AppCast file. + //AutoUpdater.HttpUserAgent = "AutoUpdaterTest"; + //AutoUpdater.ParseUpdateInfoEvent += p => + //{ + // var json = JsonConvert.DeserializeObject(p.RemoteData); + // if (json == null) return; + // p.UpdateInfo = new UpdateInfoEventArgs(); + // p.UpdateInfo.CurrentVersion = json.tag_name.Value.TrimStart('v'); + // p.UpdateInfo.ChangelogText = json.body; + // if (AutoUpdater.ChangelogViewerType is ChangelogViewerType.WebBrowser or ChangelogViewerType.WebView2) + // { + // p.UpdateInfo.ChangelogText = json.body.Value.Replace("\r\n", "
").Replace("\n", "
"); + // p.UpdateInfo.ChangelogText = $"{p.UpdateInfo.ChangelogText}"; + // } + // //e.UpdateInfo.ChangelogURL = json.html_url; + + // foreach (var asset in json.assets) + // { + // // Get the matched runtime & architecture asset + // //var split = asset.name.Value.Split('_'); + // //if (split.Length < 4) + // // continue; + + // //var arch = split[2].Split('.')[0]; + // //var runtime = split[3].Replace(".zip", ""); + + // //if ("NetFW4.8.1" != runtime || arch != "x64") + // // continue; + + // var matchedAsset = asset.browser_download_url; + // p.UpdateInfo.DownloadURL = matchedAsset; + // break; + // } + //}; + //AutoUpdater.Start("https://api.github.com/repos/ravibpatel/AutoUpdater.NET/releases/latest"); + AutoUpdater.Start("https://rbsoft.org/updates/AutoUpdaterTest.xml"); } } \ No newline at end of file From f6b6624b1cfb8491b48a7e6be9eb105f9e3e4ee4 Mon Sep 17 00:00:00 2001 From: w4po Date: Mon, 24 Feb 2025 06:47:04 +0200 Subject: [PATCH 2/3] refactor: Split WebView2 into separate package - Move WebView2 viewer into its own NuGet package to reduce core dependencies - Implement extension-based architecture for changelog viewers - Add auto-discovery of viewer extensions at runtime - Update core package to dynamically load WebView2 when available - Remove WebView2 dependency from core package - Clean up project structure and dependencies This change makes the WebView2 viewer optional, reducing the core package size and dependencies. Users who want WebView2 support can install the additional package, while the core functionality remains lightweight. --- .../AutoUpdater.NET.WebView2.csproj | 37 +++++++ .../WebView2Viewer.cs | 10 +- .../WebView2ViewerProvider.cs | 14 +++ .../build/Autoupdater.NET.WebView2.nuspec | 63 +++++++++++ AutoUpdater.NET.sln | 13 ++- AutoUpdater.NET/AutoUpdater.NET.csproj | 6 +- AutoUpdater.NET/AutoUpdater.cs | 10 +- AutoUpdater.NET/BasicAuthentication.cs | 4 +- .../ChangelogViewerFactory.cs | 104 +++++++++++++++--- .../ChangelogViewers/ChangelogViewerType.cs | 8 -- .../ChangelogViewers/IChangelogViewer.cs | 22 ++++ .../IChangelogViewerProvider.cs | 22 ++++ .../RichTextBoxViewerProvider.cs | 10 ++ .../WebBrowserViewerProvider.cs | 10 ++ AutoUpdater.NET/UpdateForm.cs | 2 +- .../build/Autoupdater.NET.Official.nuspec | 24 +--- AutoUpdaterTest/AutoUpdaterTest.csproj | 1 + AutoUpdaterTest/MainWindow.xaml.cs | 4 +- build.bat | 13 ++- 19 files changed, 318 insertions(+), 59 deletions(-) create mode 100644 AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj rename {AutoUpdater.NET/ChangelogViewers => AutoUpdater.NET.WebView2}/WebView2Viewer.cs (94%) create mode 100644 AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs create mode 100644 AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec delete mode 100644 AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs create mode 100644 AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs diff --git a/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj b/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj new file mode 100644 index 00000000..dca9742b --- /dev/null +++ b/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj @@ -0,0 +1,37 @@ + + + + library + net462;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows + true + AutoUpdaterDotNET.WebView2 + AutoUpdater.NET.WebView2 + RBSoft + AutoUpdater.NET.WebView2 + Copyright © 2012-2024 RBSoft + 1.9.3.0 + 1.9.3.0 + 1.9.3.0 + 1.9.3.0 + true + ..\AutoUpdater.NET\AutoUpdater.NET.snk + en + Autoupdater.NET.WebView2 + true + MIT + AutoUpdater.NET WebView2 Extension + rbsoft + WebView2 extension for AutoUpdater.NET that provides modern web rendering capabilities for changelogs. + https://github.com/ravibpatel/AutoUpdater.NET + autoupdate updater webview2 edge + https://github.com/ravibpatel/AutoUpdater.NET/releases + build + $(OutDir)\AutoUpdater.NET.WebView2.xml + latest + + + + + + + diff --git a/AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs b/AutoUpdater.NET.WebView2/WebView2Viewer.cs similarity index 94% rename from AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs rename to AutoUpdater.NET.WebView2/WebView2Viewer.cs index ebc239a7..0d2d806a 100644 --- a/AutoUpdater.NET/ChangelogViewers/WebView2Viewer.cs +++ b/AutoUpdater.NET.WebView2/WebView2Viewer.cs @@ -3,14 +3,14 @@ using System.Windows.Forms; using System.Threading.Tasks; using Microsoft.Web.WebView2.Core; -using Microsoft.Web.WebView2.WinForms; +using AutoUpdaterDotNET.ChangelogViewers; -namespace AutoUpdaterDotNET.ChangelogViewers; +namespace AutoUpdaterDotNET.WebView2; public class WebView2Viewer : IChangelogViewer { private bool _isInitialized; - private readonly WebView2 _webView = new() + private readonly Microsoft.Web.WebView2.WinForms.WebView2 _webView = new() { Dock = DockStyle.Fill, AllowExternalDrop = false @@ -37,11 +37,11 @@ public async void LoadContent(string content) { await EnsureInitialized(); _webView.CoreWebView2.SetVirtualHostNameToFolderMapping("local.files", Path.GetTempPath(), CoreWebView2HostResourceAccessKind.Allow); - + // Write content to a temporary HTML file var tempFile = Path.Combine(Path.GetTempPath(), "changelog.html"); File.WriteAllText(tempFile, content); - + // Navigate to the local file _webView.CoreWebView2.Navigate("https://local.files/changelog.html"); } diff --git a/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs b/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs new file mode 100644 index 00000000..9dcebc95 --- /dev/null +++ b/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs @@ -0,0 +1,14 @@ +using AutoUpdaterDotNET.ChangelogViewers; + +namespace AutoUpdaterDotNET.WebView2; + +public class WebView2ViewerProvider(int priority = 2) : IChangelogViewerProvider +{ + public WebView2ViewerProvider() : this(2) { } + + public bool IsAvailable => WebView2Viewer.IsAvailable(); + + public int Priority { get; } = priority; + + public IChangelogViewer CreateViewer() => new WebView2Viewer(); +} diff --git a/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec b/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec new file mode 100644 index 00000000..59dfc375 --- /dev/null +++ b/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec @@ -0,0 +1,63 @@ + + + + Autoupdater.NET.WebView2 + 1.9.3.0 + AutoUpdater.NET WebView2 Extension + rbsoft + false + MIT + https://licenses.nuget.org/MIT + https://github.com/ravibpatel/AutoUpdater.NET + WebView2 extension for AutoUpdater.NET that provides modern web rendering capabilities for changelogs. + https://github.com/ravibpatel/AutoUpdater.NET/releases + Copyright 2012-2024 RBSoft + autoupdate updater webview2 edge + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutoUpdater.NET.sln b/AutoUpdater.NET.sln index 8b512fc2..1138e5a1 100644 --- a/AutoUpdater.NET.sln +++ b/AutoUpdater.NET.sln @@ -5,21 +5,26 @@ VisualStudioVersion = 16.0.29215.179 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET", "AutoUpdater.NET\AutoUpdater.NET.csproj", "{FB9E7E6B-B19F-4F37-A708-2996190CEF13}" ProjectSection(ProjectDependencies) = postProject - {91DE558C-6DB8-429B-A069-C0491DCFF15B} = {91DE558C-6DB8-429B-A069-C0491DCFF15B} + {EDB311FC-50D3-468B-AC36-4CDFE04D29A3} = {EDB311FC-50D3-468B-AC36-4CDFE04D29A3} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1DBD2EE1-6C31-4B24-9212-E221404F4ADE}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore appveyor.yml = appveyor.yml + build.bat = build.bat LICENSE = LICENSE README.md = README.md - build.bat = build.bat EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZipExtractor", "ZipExtractor\ZipExtractor.csproj", "{EDB311FC-50D3-468B-AC36-4CDFE04D29A3}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdaterTest", "AutoUpdaterTest\AutoUpdaterTest.csproj", "{5C1E186E-2622-425D-8E90-2CC6C25EDE29}" + ProjectSection(ProjectDependencies) = postProject + {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3} = {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET.WebView2", "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj", "{A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +44,10 @@ Global {5C1E186E-2622-425D-8E90-2CC6C25EDE29}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C1E186E-2622-425D-8E90-2CC6C25EDE29}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C1E186E-2622-425D-8E90-2CC6C25EDE29}.Release|Any CPU.Build.0 = Release|Any CPU + {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AutoUpdater.NET/AutoUpdater.NET.csproj b/AutoUpdater.NET/AutoUpdater.NET.csproj index 2d680f5c..87007254 100644 --- a/AutoUpdater.NET/AutoUpdater.NET.csproj +++ b/AutoUpdater.NET/AutoUpdater.NET.csproj @@ -48,9 +48,6 @@ - - - DownloadUpdateDialog.cs @@ -242,4 +239,7 @@ UpdateForm.cs + + + \ No newline at end of file diff --git a/AutoUpdater.NET/AutoUpdater.cs b/AutoUpdater.NET/AutoUpdater.cs index 379ed8e1..eec98a37 100644 --- a/AutoUpdater.NET/AutoUpdater.cs +++ b/AutoUpdater.NET/AutoUpdater.cs @@ -247,9 +247,15 @@ public static class AutoUpdater public static Mode UpdateMode; /// - /// Set this to any of the available types to change the changelog viewer. + /// Gets or sets whether to automatically load and register changelog viewer extensions from DLLs in the application directory. + /// Default is true. /// - public static ChangelogViewerType ChangelogViewerType = ChangelogViewerFactory.GetDefaultViewerType(); + public static bool AutoLoadExtensions { get; set; } = true; + + /// + /// Gets or sets the provider to use for displaying changelogs. If null, the factory will use the highest priority available provider. + /// + public static IChangelogViewerProvider ChangelogViewerProvider { get; set; } /// /// An event that developers can use to exit the application gracefully. diff --git a/AutoUpdater.NET/BasicAuthentication.cs b/AutoUpdater.NET/BasicAuthentication.cs index 59fb7326..65b4ef2f 100644 --- a/AutoUpdater.NET/BasicAuthentication.cs +++ b/AutoUpdater.NET/BasicAuthentication.cs @@ -20,9 +20,9 @@ public BasicAuthentication(string username, string password) Password = password; } - internal string Username { get; } + public string Username { get; } - internal string Password { get; } + public string Password { get; } /// public void Apply(ref MyWebClient webClient) diff --git a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs index e705ffe3..d631b398 100644 --- a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs +++ b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerFactory.cs @@ -1,26 +1,102 @@ using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; namespace AutoUpdaterDotNET.ChangelogViewers; -public static class ChangelogViewerFactory +internal static class ChangelogViewerFactory { - public static IChangelogViewer Create(ChangelogViewerType type) + private static readonly List Providers = + [ + new RichTextBoxViewerProvider(), + new WebBrowserViewerProvider() + ]; + + static ChangelogViewerFactory() + { + if (AutoUpdater.AutoLoadExtensions) + LoadExtensions(); + } + + public static void RegisterProvider(IChangelogViewerProvider provider) + { + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + var providerType = provider.GetType(); + var existingProvider = Providers.FirstOrDefault(p => p.GetType() == providerType); + if (existingProvider != null) + { + if (existingProvider.Priority == provider.Priority) + return; // Same type and priority, nothing to do + + // Remove existing provider to allow priority override + Providers.Remove(existingProvider); + } + + Providers.Add(provider); + } + + internal static IChangelogViewer Create(IChangelogViewerProvider provider = null) { - return type switch + provider ??= AutoUpdater.ChangelogViewerProvider; + + if (provider != null) { - ChangelogViewerType.RichTextBox => new RichTextBoxViewer(), - ChangelogViewerType.WebBrowser => new WebBrowserViewer(), - ChangelogViewerType.WebView2 when WebView2Viewer.IsAvailable() => new WebView2Viewer(), - ChangelogViewerType.WebView2 => throw new InvalidOperationException("WebView2 runtime is not available"), - _ => throw new ArgumentException($"Unknown viewer type: {type}") - }; + if (!provider.IsAvailable) + throw new InvalidOperationException($"The specified viewer provider '{provider.GetType().Name}' is not available in this environment."); + + return provider.CreateViewer(); + } + + // Get all available providers ordered by priority (highest first) + var availableProviders = Providers + .Where(p => p.IsAvailable) + .OrderByDescending(p => p.Priority); + + var changelogViewerProvider = availableProviders.FirstOrDefault(); + + if (changelogViewerProvider == null) + throw new InvalidOperationException("No changelog viewers are available in this environment."); + + return changelogViewerProvider.CreateViewer(); } - public static ChangelogViewerType GetDefaultViewerType() + private static void LoadExtensions() { - if (WebView2Viewer.IsAvailable()) - return ChangelogViewerType.WebView2; - - return ChangelogViewerType.WebBrowser; + var extensions = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "AutoUpdater.NET.*.dll"); + if (extensions.Length == 0) return; + + var iProviderType = typeof(IChangelogViewerProvider); + foreach (var extension in extensions) + { + try + { + var assembly = Assembly.LoadFrom(extension); + var providers = assembly + .GetTypes() + .Where(t => t.IsClass && !t.IsAbstract && iProviderType.IsAssignableFrom(t)); + + foreach (var provider in providers) + { + try + { + var instance = (IChangelogViewerProvider)Activator.CreateInstance(provider); + if (instance?.IsAvailable == true) + RegisterProvider(instance); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to create instance of provider {provider.FullName}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to load extension {extension}: {ex.Message}"); + } + } } } \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs b/AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs deleted file mode 100644 index 10e03c82..00000000 --- a/AutoUpdater.NET/ChangelogViewers/ChangelogViewerType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace AutoUpdaterDotNET.ChangelogViewers; - -public enum ChangelogViewerType -{ - RichTextBox, - WebBrowser, - WebView2 -} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs b/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs index 5a95b550..369f75bd 100644 --- a/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs +++ b/AutoUpdater.NET/ChangelogViewers/IChangelogViewer.cs @@ -2,11 +2,33 @@ namespace AutoUpdaterDotNET.ChangelogViewers; +/// +/// Interface for changelog viewer implementations +/// public interface IChangelogViewer { + /// + /// The Windows Forms control that displays the changelog + /// Control Control { get; } + + /// + /// Whether this viewer supports loading content from URLs directly + /// bool SupportsUrl { get; } + + /// + /// Load changelog content as HTML or plain text + /// void LoadContent(string content); + + /// + /// Load changelog content from a URL + /// void LoadUrl(string url); + + /// + /// Clean up any resources used by the viewer + /// void Cleanup(); } \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs new file mode 100644 index 00000000..7f4e01b2 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/IChangelogViewerProvider.cs @@ -0,0 +1,22 @@ +namespace AutoUpdaterDotNET.ChangelogViewers; + +/// +/// Factory for creating changelog viewers +/// +public interface IChangelogViewerProvider +{ + /// + /// Whether this provider can create a viewer in the current environment + /// + bool IsAvailable { get; } + + /// + /// Priority of this provider (higher numbers = higher priority) + /// + int Priority { get; } + + /// + /// Create a new changelog viewer instance + /// + IChangelogViewer CreateViewer(); +} \ No newline at end of file diff --git a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs new file mode 100644 index 00000000..316d0e4c --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs @@ -0,0 +1,10 @@ +namespace AutoUpdaterDotNET.ChangelogViewers; + +public class RichTextBoxViewerProvider(int priority = 0) : IChangelogViewerProvider +{ + public bool IsAvailable => true; + + public int Priority { get; } = priority; + + public IChangelogViewer CreateViewer() => new RichTextBoxViewer(); +} diff --git a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs new file mode 100644 index 00000000..c0417930 --- /dev/null +++ b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs @@ -0,0 +1,10 @@ +namespace AutoUpdaterDotNET.ChangelogViewers; + +public class WebBrowserViewerProvider(int priority = 1) : IChangelogViewerProvider +{ + public bool IsAvailable => true; + + public int Priority { get; } = priority; + + public IChangelogViewer CreateViewer() => new WebBrowserViewer(); +} diff --git a/AutoUpdater.NET/UpdateForm.cs b/AutoUpdater.NET/UpdateForm.cs index 018af80c..d3c27824 100644 --- a/AutoUpdater.NET/UpdateForm.cs +++ b/AutoUpdater.NET/UpdateForm.cs @@ -54,7 +54,7 @@ private void InitializeChangelogViewer() } // Create and configure the new viewer - _changelogViewer = ChangelogViewerFactory.Create(AutoUpdater.ChangelogViewerType); + _changelogViewer = ChangelogViewerFactory.Create(AutoUpdater.ChangelogViewerProvider); var viewerControl = _changelogViewer.Control; viewerControl.Dock = DockStyle.Fill; pnlChangelog.Controls.Add(viewerControl); diff --git a/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec b/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec index f71cf5ca..2e58e3e7 100644 --- a/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec +++ b/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec @@ -17,24 +17,12 @@ Copyright © 2012-2024 RBSoft autoupdate updater c# vb wpf winforms - - - - - - - - - - - - - - - - - - + + + + + + diff --git a/AutoUpdaterTest/AutoUpdaterTest.csproj b/AutoUpdaterTest/AutoUpdaterTest.csproj index c9eb2b55..fcb713bb 100644 --- a/AutoUpdaterTest/AutoUpdaterTest.csproj +++ b/AutoUpdaterTest/AutoUpdaterTest.csproj @@ -11,6 +11,7 @@ + diff --git a/AutoUpdaterTest/MainWindow.xaml.cs b/AutoUpdaterTest/MainWindow.xaml.cs index 08536123..d3187815 100644 --- a/AutoUpdaterTest/MainWindow.xaml.cs +++ b/AutoUpdaterTest/MainWindow.xaml.cs @@ -174,7 +174,7 @@ private void ButtonCheckForUpdate_Click(object sender, RoutedEventArgs e) AutoUpdater.Icon = Resource.Icon; // Uncomment following line to change the Changelog viewer type. - //AutoUpdater.ChangelogViewerType = ChangelogViewerType.RichTextBox; + //AutoUpdater.ChangelogViewerProvider = new RichTextBoxViewerProvider(); // Uncomment following lines to handle parsing logic of custom AppCast file. //AutoUpdater.HttpUserAgent = "AutoUpdaterTest"; @@ -185,7 +185,7 @@ private void ButtonCheckForUpdate_Click(object sender, RoutedEventArgs e) // p.UpdateInfo = new UpdateInfoEventArgs(); // p.UpdateInfo.CurrentVersion = json.tag_name.Value.TrimStart('v'); // p.UpdateInfo.ChangelogText = json.body; - // if (AutoUpdater.ChangelogViewerType is ChangelogViewerType.WebBrowser or ChangelogViewerType.WebView2) + // if (AutoUpdater.ChangelogViewerProvider is WebBrowserViewerProvider or WebView2ViewerProvider) // { // p.UpdateInfo.ChangelogText = json.body.Value.Replace("\r\n", "
").Replace("\n", "
"); // p.UpdateInfo.ChangelogText = $"{p.UpdateInfo.ChangelogText}"; diff --git a/build.bat b/build.bat index ea6aea42..c7a2e410 100644 --- a/build.bat +++ b/build.bat @@ -3,26 +3,35 @@ msbuild "ZipExtractor\ZipExtractor.csproj" /p:Configuration=Release /verbosity:m :: .NET Framework 4.6.2 msbuild "AutoUpdater.NET\AutoUpdater.NET.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal +msbuild "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal :: .NET Core 3.1 dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\netcoreapp3.1" +dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\netcoreapp3.1" :: .NET 5.0 dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net5.0-windows7.0" +dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net5.0-windows7.0" :: .NET 6.0 dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net6.0-windows7.0" +dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net6.0-windows7.0" :: .NET 7.0 dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net7.0-windows7.0" +dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net7.0-windows7.0" :: .NET 8.0 dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net8.0-windows7.0" +dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net8.0-windows7.0" :: Remove unnecessary files -Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET\build\lib\* -include runtimes,Microsoft.Web.WebView2*,AutoUpdater.NET.deps.json -Recurse" +Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET\build\lib\* -include runtimes,AutoUpdater.NET.deps.json -Recurse" +:: Remove unnecessary files from WebView2 package +:: Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET.WebView2\build\lib\* -include runtimes,Microsoft.Web.WebView2*,AutoUpdater.NET.WebView2.deps.json -Recurse" -:: Create NuGet package +:: Create NuGet packages nuget pack AutoUpdater.NET\build\Autoupdater.NET.Official.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET\build +nuget pack AutoUpdater.NET.WebView2\build\Autoupdater.NET.WebView2.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET.WebView2\build pause \ No newline at end of file From bedddefcf4207b813c98684d35bffa82c8268b3a Mon Sep 17 00:00:00 2001 From: w4po Date: Tue, 25 Feb 2025 06:10:49 +0200 Subject: [PATCH 3/3] feat: Add Markdown support with AutoUpdater.NET.Official.Markdown package Introduce a new package for rendering Markdown changelogs with enhanced styling and flexibility: - Add AutoUpdater.NET.Official.Markdown package with modern CSS styling and emoji support - Implement composite viewer pattern allowing to use and viewer like Webview2/WebBrowser/RichTextBox - Improve documentation with XML comments and see tags - Update build scripts to include Markdown package compilation - Bump version to 1.9.5.1 across all packages - Update copyright year to 2025 The new Markdown package provides: - GitHub-style Markdown rendering - Customizable viewer priority system - Support for both WebView2 and WebBrowser rendering - Proper cleanup handling for viewer resources --- .gitignore | 4 + .../AutoUpdater.NET.Markdown.csproj | 36 ++++ AutoUpdater.NET.Markdown/MarkdownViewer.cs | 154 ++++++++++++++++++ .../MarkdownViewerProvider.cs | 39 +++++ .../build/Autoupdater.NET.Markdown.nuspec | 53 ++++++ .../AutoUpdater.NET.WebView2.csproj | 9 +- AutoUpdater.NET.WebView2/WebView2Viewer.cs | 18 +- .../WebView2ViewerProvider.cs | 17 +- .../build/Autoupdater.NET.WebView2.nuspec | 37 +++-- AutoUpdater.NET.sln | 7 + AutoUpdater.NET/AutoUpdater.NET.csproj | 12 +- AutoUpdater.NET/AutoUpdater.cs | 4 +- .../ChangelogViewers/RichTextBoxViewer.cs | 27 +++ .../RichTextBoxViewerProvider.cs | 10 ++ .../ChangelogViewers/WebBrowserViewer.cs | 15 +- .../WebBrowserViewerProvider.cs | 15 ++ .../build/Autoupdater.NET.Official.nuspec | 4 +- AutoUpdaterTest/AutoUpdaterTest.csproj | 4 +- LICENSE | 2 +- README.md | 109 ++++++++++++- ZipExtractor/ZipExtractor.csproj | 2 +- build.bat | 13 +- 22 files changed, 545 insertions(+), 46 deletions(-) create mode 100644 AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj create mode 100644 AutoUpdater.NET.Markdown/MarkdownViewer.cs create mode 100644 AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs create mode 100644 AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec diff --git a/.gitignore b/.gitignore index 2f97a261..37efa7e2 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ bld/ [Oo]bj/ [Ll]og/ +build/ +# Keep nuspec files in build directories +!**/build/*.nuspec + # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot diff --git a/AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj b/AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj new file mode 100644 index 00000000..fbaa2848 --- /dev/null +++ b/AutoUpdater.NET.Markdown/AutoUpdater.NET.Markdown.csproj @@ -0,0 +1,36 @@ + + + library + net462;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows + true + AutoUpdaterDotNET.Markdown + AutoUpdater.NET.Markdown + RBSoft + AutoUpdater.NET.Markdown + Copyright © 2012-2025 RBSoft + 1.9.5.1 + + + en + Autoupdater.NET.Markdown + true + MIT + AutoUpdater.NET Markdown Extension + rbsoft + Markdown extension for AutoUpdater.NET that provides markdown support. + https://github.com/ravibpatel/AutoUpdater.NET + autoupdate updater markdown + https://github.com/ravibpatel/AutoUpdater.NET/releases + build + true + latest + + + + + + + + + + diff --git a/AutoUpdater.NET.Markdown/MarkdownViewer.cs b/AutoUpdater.NET.Markdown/MarkdownViewer.cs new file mode 100644 index 00000000..292d1754 --- /dev/null +++ b/AutoUpdater.NET.Markdown/MarkdownViewer.cs @@ -0,0 +1,154 @@ +using System.Windows.Forms; +using AutoUpdaterDotNET.ChangelogViewers; +using Markdig; + +namespace AutoUpdaterDotNET.Markdown; + +/// +/// A changelog viewer that renders Markdown content using either a provided viewer or a WebBrowser control. +/// +public class MarkdownViewer : IChangelogViewer +{ + private readonly IChangelogViewer _innerViewer; + private readonly bool _cleanup; + private const string DefaultStyle = @" + "; + + /// + /// Initializes a new instance of the MarkdownViewer class. + /// + /// Optional viewer to use for rendering. If not provided, uses WebBrowser control. + /// Whether to clean up the inner viewer when this viewer is disposed. + public MarkdownViewer(IChangelogViewer viewer = null, bool cleanup = true) + { + _innerViewer = viewer ?? new WebBrowserViewer(); + _cleanup = cleanup || viewer == null; + } + + /// + public Control Control => _innerViewer.Control; + + /// + public bool SupportsUrl => true; + + /// + public void LoadContent(string content) + { + if (_innerViewer is RichTextBoxViewer or MarkdownViewer) + { + _innerViewer.LoadContent(content); + return; + } + + var pipeline = new MarkdownPipelineBuilder() + .UseAdvancedExtensions() + .Build(); + + var html = Markdig.Markdown.ToHtml(content, pipeline); + var fullHtml = $"{DefaultStyle}{html}"; + + _innerViewer.LoadContent(fullHtml); + } + + /// + public void LoadUrl(string url) + { + using var client = new System.Net.WebClient(); + if (AutoUpdater.BasicAuthChangeLog != null) + { + var auth = (BasicAuthentication)AutoUpdater.BasicAuthChangeLog; + client.Credentials = new System.Net.NetworkCredential(auth.Username, auth.Password); + } + + var content = client.DownloadString(url); + LoadContent(content); + } + + /// + public void Cleanup() + { + if (_cleanup) + _innerViewer.Cleanup(); + } +} \ No newline at end of file diff --git a/AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs b/AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs new file mode 100644 index 00000000..d76ab127 --- /dev/null +++ b/AutoUpdater.NET.Markdown/MarkdownViewerProvider.cs @@ -0,0 +1,39 @@ +using AutoUpdaterDotNET.ChangelogViewers; + +namespace AutoUpdaterDotNET.Markdown; + +/// +/// Provides a Markdown viewer that uses a WebBrowser control or a user provided viewer to render Markdown content. +/// +public class MarkdownViewerProvider(int priority = 2) : IChangelogViewerProvider +{ + private readonly IChangelogViewer _viewer; + + /// + /// Creates a new instance of the with default priority 2. + /// + public MarkdownViewerProvider() : this(2) { } + + /// + /// Creates a new instance of the with a specific viewer. + /// + /// The viewer to use for rendering Markdown content. + /// The priority of this provider. + public MarkdownViewerProvider(IChangelogViewer viewer, int priority = 2) : this(priority) + { + _viewer = viewer; + } + + /// + /// Gets whether this provider is available. Always returns true as it uses WebBrowser as fallback. + /// + public bool IsAvailable => true; + + /// + public int Priority { get; } = priority; + + /// + /// Creates a new instance of the class. + /// + public IChangelogViewer CreateViewer() => new MarkdownViewer(_viewer); +} \ No newline at end of file diff --git a/AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec b/AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec new file mode 100644 index 00000000..a0e6942a --- /dev/null +++ b/AutoUpdater.NET.Markdown/build/Autoupdater.NET.Markdown.nuspec @@ -0,0 +1,53 @@ + + + + Autoupdater.NET.Official.Markdown + 1.9.5.1 + AutoUpdater.NET Markdown Extension + rbsoft + false + MIT + docs\README.md + https://licenses.nuget.org/MIT + https://github.com/ravibpatel/AutoUpdater.NET + Markdown extension for AutoUpdater.NET that provides Markdown rendering capabilities for changelogs. + https://github.com/ravibpatel/AutoUpdater.NET/releases + Copyright 2012-2025 RBSoft + autoupdate updater markdown changelog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj b/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj index dca9742b..dfc8e753 100644 --- a/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj +++ b/AutoUpdater.NET.WebView2/AutoUpdater.NET.WebView2.csproj @@ -8,11 +8,8 @@ AutoUpdater.NET.WebView2 RBSoft AutoUpdater.NET.WebView2 - Copyright © 2012-2024 RBSoft - 1.9.3.0 - 1.9.3.0 - 1.9.3.0 - 1.9.3.0 + Copyright © 2012-2025 RBSoft + 1.9.5.1 true ..\AutoUpdater.NET\AutoUpdater.NET.snk en @@ -26,7 +23,7 @@ autoupdate updater webview2 edge https://github.com/ravibpatel/AutoUpdater.NET/releases build - $(OutDir)\AutoUpdater.NET.WebView2.xml + true latest diff --git a/AutoUpdater.NET.WebView2/WebView2Viewer.cs b/AutoUpdater.NET.WebView2/WebView2Viewer.cs index 0d2d806a..6203c990 100644 --- a/AutoUpdater.NET.WebView2/WebView2Viewer.cs +++ b/AutoUpdater.NET.WebView2/WebView2Viewer.cs @@ -7,6 +7,9 @@ namespace AutoUpdaterDotNET.WebView2; +/// +/// A changelog viewer for displaying changelogs using a modern browser. +/// public class WebView2Viewer : IChangelogViewer { private bool _isInitialized; @@ -15,7 +18,11 @@ public class WebView2Viewer : IChangelogViewer Dock = DockStyle.Fill, AllowExternalDrop = false }; + + /// public Control Control => _webView; + + /// public bool SupportsUrl => true; private async Task EnsureInitialized() @@ -33,6 +40,7 @@ private async Task EnsureInitialized() } } + /// public async void LoadContent(string content) { await EnsureInitialized(); @@ -40,12 +48,13 @@ public async void LoadContent(string content) // Write content to a temporary HTML file var tempFile = Path.Combine(Path.GetTempPath(), "changelog.html"); - File.WriteAllText(tempFile, content); + File.WriteAllText(tempFile, content, System.Text.Encoding.UTF8); // Navigate to the local file _webView.CoreWebView2.Navigate("https://local.files/changelog.html"); } + /// public async void LoadUrl(string url) { await EnsureInitialized(); @@ -53,7 +62,7 @@ public async void LoadUrl(string url) if (AutoUpdater.BasicAuthChangeLog != null) { _webView.CoreWebView2.BasicAuthenticationRequested += delegate (object _, CoreWebView2BasicAuthenticationRequestedEventArgs args) - { + { args.Response.UserName = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Username; args.Response.Password = ((BasicAuthentication)AutoUpdater.BasicAuthChangeLog).Password; }; @@ -62,6 +71,7 @@ public async void LoadUrl(string url) _webView.CoreWebView2.Navigate(url); } + /// public void Cleanup() { if (File.Exists(Path.Combine(Path.GetTempPath(), "changelog.html"))) @@ -78,6 +88,10 @@ public void Cleanup() _webView.Dispose(); } + /// + /// Checks if WebView2 is available on the current environment + /// + /// True if WebView2 is available, false otherwise public static bool IsAvailable() { try diff --git a/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs b/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs index 9dcebc95..9f088478 100644 --- a/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs +++ b/AutoUpdater.NET.WebView2/WebView2ViewerProvider.cs @@ -2,13 +2,26 @@ namespace AutoUpdaterDotNET.WebView2; -public class WebView2ViewerProvider(int priority = 2) : IChangelogViewerProvider +/// +/// Provides a changelog viewer that uses the modern WebView2 control. +/// +public class WebView2ViewerProvider(int priority = 3) : IChangelogViewerProvider { - public WebView2ViewerProvider() : this(2) { } + /// + /// Creates a new instance of the with default priority 3. + /// + public WebView2ViewerProvider() : this(3) { } + /// + /// Gets whether WebView2 runtime is available on the current system. + /// public bool IsAvailable => WebView2Viewer.IsAvailable(); + /// public int Priority { get; } = priority; + /// + /// Creates a new instance of the class. + /// public IChangelogViewer CreateViewer() => new WebView2Viewer(); } diff --git a/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec b/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec index 59dfc375..ecf0b656 100644 --- a/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec +++ b/AutoUpdater.NET.WebView2/build/Autoupdater.NET.WebView2.nuspec @@ -2,41 +2,42 @@ Autoupdater.NET.WebView2 - 1.9.3.0 + 1.9.5.1 AutoUpdater.NET WebView2 Extension rbsoft false MIT + docs\README.md https://licenses.nuget.org/MIT https://github.com/ravibpatel/AutoUpdater.NET WebView2 extension for AutoUpdater.NET that provides modern web rendering capabilities for changelogs. https://github.com/ravibpatel/AutoUpdater.NET/releases - Copyright 2012-2024 RBSoft + Copyright 2012-2025 RBSoft autoupdate updater webview2 edge - - + + - - + + - - + + - - + + - - + + - - + + @@ -58,6 +59,12 @@ - + + + + + + + diff --git a/AutoUpdater.NET.sln b/AutoUpdater.NET.sln index 1138e5a1..b360789e 100644 --- a/AutoUpdater.NET.sln +++ b/AutoUpdater.NET.sln @@ -22,10 +22,13 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdaterTest", "AutoUpdaterTest\AutoUpdaterTest.csproj", "{5C1E186E-2622-425D-8E90-2CC6C25EDE29}" ProjectSection(ProjectDependencies) = postProject {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3} = {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3} + {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A} = {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET.WebView2", "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj", "{A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdater.NET.Markdown", "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj", "{D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +51,10 @@ Global {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {A9848B77-2D66-4C5C-9F0F-2F9F3FB6C5A3}.Release|Any CPU.Build.0 = Release|Any CPU + {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D93F12ED-BCF8-B6DB-D5AA-B67FAA700F4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AutoUpdater.NET/AutoUpdater.NET.csproj b/AutoUpdater.NET/AutoUpdater.NET.csproj index 87007254..0e25b592 100644 --- a/AutoUpdater.NET/AutoUpdater.NET.csproj +++ b/AutoUpdater.NET/AutoUpdater.NET.csproj @@ -10,11 +10,8 @@ AutoUpdater.NET RBSoft AutoUpdater.NET - Copyright © 2012-2024 RBSoft - 1.9.3.0 - 1.9.3.0 - 1.9.3.0 - 1.9.3.0 + Copyright © 2012-2025 RBSoft + 1.9.5.1 true AutoUpdater.NET.snk en @@ -28,7 +25,7 @@ autoupdate updater c# vb wpf winforms https://github.com/ravibpatel/AutoUpdater.NET/releases build - $(OutDir)\AutoUpdater.NET.xml + true latest @@ -239,7 +236,4 @@ UpdateForm.cs
- - - \ No newline at end of file diff --git a/AutoUpdater.NET/AutoUpdater.cs b/AutoUpdater.NET/AutoUpdater.cs index eec98a37..178aec3a 100644 --- a/AutoUpdater.NET/AutoUpdater.cs +++ b/AutoUpdater.NET/AutoUpdater.cs @@ -245,7 +245,7 @@ public static class AutoUpdater /// Set this to any of the available modes to change behaviour of the Mandatory flag. ///
public static Mode UpdateMode; - + /// /// Gets or sets whether to automatically load and register changelog viewer extensions from DLLs in the application directory. /// Default is true. @@ -256,7 +256,7 @@ public static class AutoUpdater /// Gets or sets the provider to use for displaying changelogs. If null, the factory will use the highest priority available provider. /// public static IChangelogViewerProvider ChangelogViewerProvider { get; set; } - + /// /// An event that developers can use to exit the application gracefully. /// diff --git a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs index 2d8fba93..2fc37a27 100644 --- a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs +++ b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewer.cs @@ -2,6 +2,9 @@ namespace AutoUpdaterDotNET.ChangelogViewers; +/// +/// A changelog viewer for displaying changelogs using a RichTextBox control. +/// public class RichTextBoxViewer : IChangelogViewer { private readonly RichTextBox _richTextBox = new() @@ -11,19 +14,43 @@ public class RichTextBoxViewer : IChangelogViewer BackColor = System.Drawing.SystemColors.Control, BorderStyle = BorderStyle.Fixed3D }; + + /// public Control Control => _richTextBox; + + /// + /// Always returns false + /// public bool SupportsUrl => false; + /// + /// Loads the content into the RichTextBox control. + /// + /// The content to load. public void LoadContent(string content) { _richTextBox.Text = content; } + /// + /// Loads the RTF content into the RichTextBox control. + /// + /// The RTF content to load. + public void LoadContentRtf(string content) + { + _richTextBox.Rtf = content; + } + + /// + /// Always throws a NotSupportedException + /// + /// public void LoadUrl(string url) { throw new System.NotSupportedException("RichTextBox does not support loading from URL"); } + /// public void Cleanup() { _richTextBox.Dispose(); diff --git a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs index 316d0e4c..eb141712 100644 --- a/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs +++ b/AutoUpdater.NET/ChangelogViewers/RichTextBoxViewerProvider.cs @@ -1,10 +1,20 @@ namespace AutoUpdaterDotNET.ChangelogViewers; +/// +/// Provides a text viewer that uses a RichTextBox control. +/// public class RichTextBoxViewerProvider(int priority = 0) : IChangelogViewerProvider { + /// + /// Gets whether this provider is available. Always returns true as RichTextBox is always available. + /// public bool IsAvailable => true; + /// public int Priority { get; } = priority; + /// + /// Creates a new instance of the class. + /// public IChangelogViewer CreateViewer() => new RichTextBoxViewer(); } diff --git a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs index abf3df84..aaa6486c 100644 --- a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs +++ b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewer.cs @@ -5,22 +5,32 @@ namespace AutoUpdaterDotNET.ChangelogViewers; +/// +/// A changelog viewer for displaying changelogs using a WebBrowser control. +/// public class WebBrowserViewer : IChangelogViewer { private const string EmulationKey = @"SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION"; private readonly int _emulationValue; private readonly string _executableName; private readonly WebBrowser _webBrowser; + + /// public Control Control => _webBrowser; + + + /// public bool SupportsUrl => true; + /// + /// Initializes a new instance of the class. + /// public WebBrowserViewer() { _webBrowser = new WebBrowser { Dock = DockStyle.Fill, ScriptErrorsSuppressed = true, - AllowWebBrowserDrop = false, WebBrowserShortcutsEnabled = false, IsWebBrowserContextMenuEnabled = false }; @@ -43,8 +53,10 @@ public WebBrowserViewer() SetupEmulation(); } + /// public void LoadContent(string content) => _webBrowser.DocumentText = content; + /// public void LoadUrl(string url) { if (AutoUpdater.BasicAuthChangeLog != null) @@ -83,6 +95,7 @@ private void RemoveEmulation() } } + /// public void Cleanup() { _webBrowser.Dispose(); diff --git a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs index c0417930..38ec5595 100644 --- a/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs +++ b/AutoUpdater.NET/ChangelogViewers/WebBrowserViewerProvider.cs @@ -1,10 +1,25 @@ namespace AutoUpdaterDotNET.ChangelogViewers; +/// +/// Provides a changelog viewer that uses the default WebBrowser control. +/// public class WebBrowserViewerProvider(int priority = 1) : IChangelogViewerProvider { + /// + /// Creates a new instance of the with default priority 1. + /// + public WebBrowserViewerProvider() : this(1) { } + + /// + /// Gets whether this provider is available. Always returns true as WebBrowser is always available on Windows. + /// public bool IsAvailable => true; + /// public int Priority { get; } = priority; + /// + /// Creates a new instance of the class. + /// public IChangelogViewer CreateViewer() => new WebBrowserViewer(); } diff --git a/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec b/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec index 2e58e3e7..e377d25f 100644 --- a/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec +++ b/AutoUpdater.NET/build/Autoupdater.NET.Official.nuspec @@ -2,7 +2,7 @@ Autoupdater.NET.Official - 1.9.3.0 + 1.9.5.1 AutoUpdater.NET rbsoft false @@ -14,7 +14,7 @@ functionality to their WinForms or WPF application projects. https://github.com/ravibpatel/AutoUpdater.NET/releases - Copyright © 2012-2024 RBSoft + Copyright © 2012-2025 RBSoft autoupdate updater c# vb wpf winforms diff --git a/AutoUpdaterTest/AutoUpdaterTest.csproj b/AutoUpdaterTest/AutoUpdaterTest.csproj index fcb713bb..6efb2d89 100644 --- a/AutoUpdaterTest/AutoUpdaterTest.csproj +++ b/AutoUpdaterTest/AutoUpdaterTest.csproj @@ -2,7 +2,7 @@ WinExe - net8.0-windows + net462;netcoreapp3.1;net5.0-windows;net6.0-windows;net7.0-windows;net8.0-windows enable true latest @@ -12,6 +12,7 @@ + @@ -39,5 +40,6 @@ + diff --git a/LICENSE b/LICENSE index f8d69934..1bf48718 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012-2024 RBSoft +Copyright (c) 2012-2025 RBSoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a4b24c69..8e49b115 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,14 @@ desktop application projects. PM> Install-Package Autoupdater.NET.Official ```` +````powershell +PM> Install-Package Autoupdater.NET.Official.Markdown +```` + +````powershell +PM> Install-Package Autoupdater.NET.Official.WebView2 +```` + ## Supported .NET versions * .NET Framework 4.6.2 or above @@ -47,6 +55,7 @@ software. You need to create XML file like below and then you need to upload it 2.0.0.0 https://rbsoft.org/downloads/AutoUpdaterTest.zip + https://github.com/ravibpatel/AutoUpdater.NET/releases false @@ -58,6 +67,8 @@ There are two things you need to provide in XML file as you can see above. X.X.X.X format. * url (Required): You need to provide URL of the latest version installer file or zip file between url tags. AutoUpdater.NET downloads the file provided here and install it when user press the Update button. +* changelogText (Optional): You can provide the change log of your application between changelogText tags. If you + provide changelogText it will take precedence over changelog URL. * changelog (Optional): You need to provide URL of the change log of your application between changelog tags. If you don't provide the URL of the changelog then update dialog won't show the change log. * mandatory (Optional): You can set this to true if you don't want user to skip this version. This will ignore Remind @@ -376,6 +387,99 @@ You can create your own PersistenceProvider by implementing [IPersistenceProvider](https://github.com/ravibpatel/AutoUpdater.NET/blob/master/AutoUpdater.NET/IPersistenceProvider.cs) interface. +## Changelog Viewers + +AutoUpdater.NET provides multiple options for displaying changelogs: + +### Core Package (Autoupdater.NET.Official) + +The core package includes two basic changelog viewers: +* **WebBrowser Viewer** (Default, Priority 1): Uses the default WebBrowser control +* **RichTextBox Viewer** (Priority 0): Simple text-only viewer, used as a last resort + +### Additional Packages + +#### Markdown Package (Autoupdater.NET.Official.Markdown) + +````powershell +PM> Install-Package Autoupdater.NET.Official.Markdown +```` + +Adds support for rendering Markdown changelogs with: +* Syntax highlighting +* Tables +* Lists +* And other Markdown features +* Priority 2 (higher than WebBrowser) + +#### WebView2 Package (Autoupdater.NET.Official.WebView2) + +````powershell +PM> Install-Package Autoupdater.NET.Official.WebView2 +```` + +Adds modern web rendering support using Microsoft Edge WebView2: +* Better performance +* Modern web standards support +* Enhanced security +* Priority 3 (highest priority when available) + +### Priority System + +The viewers are selected based on their priority and availability: +1. WebView2 (Priority 3) - If WebView2 runtime is available +2. Markdown (Priority 2) - If Markdown package is installed +3. WebBrowser (Priority 1) - Always available on Windows +4. RichTextBox (Priority 0) - Fallback option + +You can bypass the priority system and use a specific viewer directly: +````csharp +// Use RichTextBox viewer regardless of other providers +AutoUpdater.ChangelogViewerProvider = new RichTextBoxViewerProvider(); +```` + +### Custom Viewers + +You can even implement your own changelog viewer by: +1. Implementing the `IChangelogViewer` interface +2. Creating a provider that implements `IChangelogViewerProvider` +3. Registering your provider using `ChangelogViewerFactory.RegisterProvider` + +Example: +````csharp +public class CustomViewer : IChangelogViewer +{ + public Control Control => /* your viewer control */; + public bool SupportsUrl => true; + + public void LoadContent(string content) + { + // Implementation + } + + public void LoadUrl(string url) + { + // Implementation + } + + public void Cleanup() + { + // Cleanup resources + } +} + +public class CustomViewerProvider : IChangelogViewerProvider +{ + public bool IsAvailable => true; + public int Priority => 100; // Higher than built-in viewers + + public IChangelogViewer CreateViewer() => new CustomViewer(); +} + +// Register your custom viewer +ChangelogViewerFactory.RegisterProvider(new CustomViewerProvider()); +```` + ## Check updates frequently You can call Start method inside Timer to check for updates frequently. @@ -505,6 +609,7 @@ update dialog. * IsUpdateAvailable (bool) : If update is available then returns true otherwise false. * DownloadURL (string) : Download URL of the update file.. +* ChangelogText (string) : Text of the changelog. * ChangelogURL (string) : URL of the webpage specifying changes in the new update. * CurrentVersion (Version) : Newest version of the application available to download. * InstalledVersion (Version) : Version of the application currently installed on the user's PC. @@ -525,6 +630,7 @@ private void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) args.UpdateInfo = new UpdateInfoEventArgs { CurrentVersion = json.version, + // ChangelogText = json.changelog, ChangelogURL = json.changelog, DownloadURL = json.url, Mandatory = new Mandatory @@ -548,6 +654,7 @@ private void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) { "version":"2.0.0.0", "url":"https://rbsoft.org/downloads/AutoUpdaterTest.zip", + // "changelogText":"Update to version 2.0.0.0...", "changelog":"https://github.com/ravibpatel/AutoUpdater.NET/releases", "mandatory":{ "value":true, @@ -581,7 +688,7 @@ You can follow below steps to build the project on your local development enviro ``` * Build ZipExtractor project in "Release" configuration to create the executable in Resources folder. While compiling it - for .NET Core 3.1 or above, you have to use publish command instead of build as + for .NET Core 3.1 or above then you have to use publish command instead of build as shown [here](https://learn.microsoft.com/en-us/dotnet/core/tutorials/publishing-with-visual-studio?pivots=dotnet-7-0) and copy the resulting executable to "AutoUpdater.NET/Resources" folder. * Visual Studio 2022 doesn't allow building .NET Framework 4.5 by default, so if you are using Visual Studio 2022 then diff --git a/ZipExtractor/ZipExtractor.csproj b/ZipExtractor/ZipExtractor.csproj index b27d1ac7..4e9704c6 100644 --- a/ZipExtractor/ZipExtractor.csproj +++ b/ZipExtractor/ZipExtractor.csproj @@ -8,7 +8,7 @@ ZipExtractor RBSoft ZipExtractor - Copyright © 2012-2024 RBSoft + Copyright © 2012-2025 RBSoft 1.5.4.0 1.5.4.0 1.5.4.0 diff --git a/build.bat b/build.bat index c7a2e410..ffdec46b 100644 --- a/build.bat +++ b/build.bat @@ -4,34 +4,41 @@ msbuild "ZipExtractor\ZipExtractor.csproj" /p:Configuration=Release /verbosity:m :: .NET Framework 4.6.2 msbuild "AutoUpdater.NET\AutoUpdater.NET.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal msbuild "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal +msbuild "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" /p:OutputPath=build\lib\net462;TargetFramework=net462;Configuration=Release /verbosity:minimal :: .NET Core 3.1 dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\netcoreapp3.1" dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\netcoreapp3.1" +dotnet publish --configuration Release --framework netcoreapp3.1 "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\netcoreapp3.1" :: .NET 5.0 dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net5.0-windows7.0" dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net5.0-windows7.0" +dotnet publish --configuration Release --framework net5.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net5.0-windows7.0" :: .NET 6.0 dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net6.0-windows7.0" dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net6.0-windows7.0" +dotnet publish --configuration Release --framework net6.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net6.0-windows7.0" :: .NET 7.0 dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net7.0-windows7.0" dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net7.0-windows7.0" +dotnet publish --configuration Release --framework net7.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net7.0-windows7.0" :: .NET 8.0 dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET\AutoUpdater.NET.csproj" --output "AutoUpdater.NET\build\lib\net8.0-windows7.0" dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET.WebView2\AutoUpdater.NET.WebView2.csproj" --output "AutoUpdater.NET.WebView2\build\lib\net8.0-windows7.0" +dotnet publish --configuration Release --framework net8.0-windows "AutoUpdater.NET.Markdown\AutoUpdater.NET.Markdown.csproj" --output "AutoUpdater.NET.Markdown\build\lib\net8.0-windows7.0" :: Remove unnecessary files -Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET\build\lib\* -include runtimes,AutoUpdater.NET.deps.json -Recurse" -:: Remove unnecessary files from WebView2 package -:: Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET.WebView2\build\lib\* -include runtimes,Microsoft.Web.WebView2*,AutoUpdater.NET.WebView2.deps.json -Recurse" +Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET\build\lib\* -include *.deps.json -Recurse" +Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET.Markdown\build\lib\* -include *.deps.json,*.resources.dll,AutoUpdater.NET.dll,AutoUpdater.NET.xml,AutoUpdater.NET.pdb,Markdig.dll -Recurse" +Powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -Command "Remove-Item -path AutoUpdater.NET.WebView2\build\lib\* -include runtimes,*.deps.json,*.resources.dll,Microsoft.Web.WebView2*,AutoUpdater.NET.dll,AutoUpdater.NET.xml,AutoUpdater.NET.pdb -Recurse" :: Create NuGet packages nuget pack AutoUpdater.NET\build\Autoupdater.NET.Official.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET\build nuget pack AutoUpdater.NET.WebView2\build\Autoupdater.NET.WebView2.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET.WebView2\build +nuget pack AutoUpdater.NET.Markdown\build\Autoupdater.NET.Markdown.nuspec -Verbosity detailed -OutputDirectory AutoUpdater.NET.Markdown\build pause \ No newline at end of file