From 700fb07e4092fe0148679b8482c42cda344b3918 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Thu, 16 Apr 2020 21:19:58 +0200 Subject: [PATCH] Changed the update check, this should be more stable and flexible. --- Greenshot/Drawing/Surface.cs | 2 +- Greenshot/Forms/MainForm.Designer.cs | 22 +-- Greenshot/Forms/MainForm.cs | 94 ++++------ Greenshot/Helpers/Entities/UpdateFeed.cs | 35 ++++ Greenshot/Helpers/UpdateHelper.cs | 184 ------------------- Greenshot/Helpers/UpdateService.cs | 222 +++++++++++++++++++++++ GreenshotPlugin/Core/RssHelper.cs | 204 --------------------- 7 files changed, 304 insertions(+), 459 deletions(-) create mode 100644 Greenshot/Helpers/Entities/UpdateFeed.cs delete mode 100644 Greenshot/Helpers/UpdateHelper.cs create mode 100644 Greenshot/Helpers/UpdateService.cs delete mode 100644 GreenshotPlugin/Core/RssHelper.cs diff --git a/Greenshot/Drawing/Surface.cs b/Greenshot/Drawing/Surface.cs index 3ec79db52..bd56db3a2 100644 --- a/Greenshot/Drawing/Surface.cs +++ b/Greenshot/Drawing/Surface.cs @@ -239,7 +239,7 @@ namespace Greenshot.Drawing _counterStart = value; Invalidate(); - _propertyChanged?.Invoke(this, new PropertyChangedEventArgs("CounterStart")); + _propertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CounterStart))); } } diff --git a/Greenshot/Forms/MainForm.Designer.cs b/Greenshot/Forms/MainForm.Designer.cs index a93b815d0..f94f4b87d 100644 --- a/Greenshot/Forms/MainForm.Designer.cs +++ b/Greenshot/Forms/MainForm.Designer.cs @@ -73,7 +73,6 @@ namespace Greenshot { this.toolStripCloseSeparator = new System.Windows.Forms.ToolStripSeparator(); this.contextmenu_exit = new GreenshotPlugin.Controls.GreenshotToolStripMenuItem(); this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); - this.backgroundWorkerTimer = new System.Windows.Forms.Timer(this.components); this.contextMenu.SuspendLayout(); this.SuspendLayout(); // @@ -122,7 +121,7 @@ namespace Greenshot { this.contextmenu_capturelastregion.Name = "contextmenu_capturelastregion"; this.contextmenu_capturelastregion.ShortcutKeyDisplayString = "Shift + Print"; this.contextmenu_capturelastregion.Size = new System.Drawing.Size(170, 22); - this.contextmenu_capturelastregion.Click += new System.EventHandler(this.Contextmenu_capturelastregionClick); + this.contextmenu_capturelastregion.Click += new System.EventHandler(this.Contextmenu_CaptureLastRegionClick); // // contextmenu_capturewindow // @@ -130,7 +129,7 @@ namespace Greenshot { this.contextmenu_capturewindow.Name = "contextmenu_capturewindow"; this.contextmenu_capturewindow.ShortcutKeyDisplayString = "Alt + Print"; this.contextmenu_capturewindow.Size = new System.Drawing.Size(170, 22); - this.contextmenu_capturewindow.Click += new System.EventHandler(this.Contextmenu_capturewindow_Click); + this.contextmenu_capturewindow.Click += new System.EventHandler(this.Contextmenu_CaptureWindow_Click); // // contextmenu_capturefullscreen // @@ -144,7 +143,7 @@ namespace Greenshot { this.contextmenu_captureie.Name = "contextmenu_captureie"; this.contextmenu_captureie.ShortcutKeyDisplayString = "Ctrl + Shift + Print"; this.contextmenu_captureie.Size = new System.Drawing.Size(170, 22); - this.contextmenu_captureie.Click += new System.EventHandler(this.Contextmenu_captureie_Click); + this.contextmenu_captureie.Click += new System.EventHandler(this.Contextmenu_CaptureIe_Click); // // toolStripListCaptureSeparator // @@ -210,7 +209,7 @@ namespace Greenshot { this.contextmenu_settings.Image = ((System.Drawing.Image)(resources.GetObject("contextmenu_settings.Image"))); this.contextmenu_settings.Name = "contextmenu_settings"; this.contextmenu_settings.Size = new System.Drawing.Size(170, 22); - this.contextmenu_settings.Click += new System.EventHandler(this.Contextmenu_settingsClick); + this.contextmenu_settings.Click += new System.EventHandler(this.Contextmenu_SettingsClick); // // toolStripMiscSeparator // @@ -229,13 +228,13 @@ namespace Greenshot { this.contextmenu_donate.Image = ((System.Drawing.Image)(resources.GetObject("contextmenu_donate.Image"))); this.contextmenu_donate.Name = "contextmenu_donate"; this.contextmenu_donate.Size = new System.Drawing.Size(170, 22); - this.contextmenu_donate.Click += new System.EventHandler(this.Contextmenu_donateClick); + this.contextmenu_donate.Click += new System.EventHandler(this.Contextmenu_DonateClick); // // contextmenu_about // this.contextmenu_about.Name = "contextmenu_about"; this.contextmenu_about.Size = new System.Drawing.Size(170, 22); - this.contextmenu_about.Click += new System.EventHandler(this.Contextmenu_aboutClick); + this.contextmenu_about.Click += new System.EventHandler(this.Contextmenu_AboutClick); // // toolStripCloseSeparator // @@ -255,12 +254,6 @@ namespace Greenshot { this.notifyIcon.Text = "Greenshot"; this.notifyIcon.MouseUp += new System.Windows.Forms.MouseEventHandler(this.NotifyIconClickTest); // - // backgroundWorkerTimer - // - this.backgroundWorkerTimer.Enabled = true; - this.backgroundWorkerTimer.Interval = 300000; - this.backgroundWorkerTimer.Tick += new System.EventHandler(this.BackgroundWorkerTimerTick); - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); @@ -282,8 +275,7 @@ namespace Greenshot { private GreenshotPlugin.Controls.GreenshotToolStripMenuItem contextmenu_capturewindowfromlist; private System.Windows.Forms.ToolStripSeparator toolStripListCaptureSeparator; private GreenshotPlugin.Controls.GreenshotToolStripMenuItem contextmenu_openrecentcapture; - private System.Windows.Forms.Timer backgroundWorkerTimer; - private GreenshotPlugin.Controls.GreenshotToolStripMenuItem contextmenu_captureie; + private GreenshotPlugin.Controls.GreenshotToolStripMenuItem contextmenu_captureie; private GreenshotPlugin.Controls.GreenshotToolStripMenuItem contextmenu_donate; private GreenshotPlugin.Controls.GreenshotToolStripMenuItem contextmenu_openfile; private System.Windows.Forms.ToolStripSeparator toolStripPluginSeparator; diff --git a/Greenshot/Forms/MainForm.cs b/Greenshot/Forms/MainForm.cs index 9a7f21290..b96211a97 100644 --- a/Greenshot/Forms/MainForm.cs +++ b/Greenshot/Forms/MainForm.cs @@ -418,6 +418,11 @@ namespace Greenshot { HandleDataTransport(dataTransport); } + // Start the update check in the background + var updateService = new UpdateService(); + updateService.Startup(); + SimpleServiceProvider.Current.AddService(updateService); + // Make Greenshot use less memory after startup if (_conf.MinimizeWorkingSetSize) { PsAPI.EmptyWorkingSet(); @@ -444,11 +449,9 @@ namespace Greenshot { Exit(); break; case CommandEnum.FirstLaunch: - Invoke((MethodInvoker)delegate { LOG.Info("FirstLaunch: Created new configuration, showing balloon."); var notifyIconClassicMessageHandler = SimpleServiceProvider.Current.GetInstance(); notifyIconClassicMessageHandler.ShowInfoMessage(Language.GetFormattedString(LangKey.tooltip_firststart, HotkeyControl.GetLocalizedHotkeyStringFromString(_conf.RegionHotkey)), 2000, ShowSetting); - }); break; case CommandEnum.ReloadConfig: LOG.Info("Reload requested"); @@ -554,15 +557,16 @@ namespace Greenshot { /// object /// PropertyChangedEventArgs private void OnIconSizeChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == "IconSize") - { - ApplyDpiScaling(); - string ieExePath = PluginUtils.GetExePath("iexplore.exe"); - if (!string.IsNullOrEmpty(ieExePath)) { - contextmenu_captureie.Image = PluginUtils.GetCachedExeIcon(ieExePath, 0); - } - } - } + if (e.PropertyName != "IconSize") + { + return; + } + ApplyDpiScaling(); + string ieExePath = PluginUtils.GetExePath("iexplore.exe"); + if (!string.IsNullOrEmpty(ieExePath)) { + contextmenu_captureie.Image = PluginUtils.GetCachedExeIcon(ieExePath, 0); + } + } /// /// Modify the DPI settings depending in the current value @@ -627,7 +631,7 @@ namespace Greenshot { /// /// Check if OneDrive is blocking hotkeys /// - /// true if onedrive has hotkeys turned on + /// true if one-drive has hotkeys turned on private static bool IsOneDriveBlockingHotkey() { if (!WindowsVersion.IsWindows10OrLater) @@ -706,14 +710,16 @@ namespace Greenshot { private void CaptureFile() { var openFileDialog = new OpenFileDialog { - Filter = "Image files (*.greenshot, *.png, *.jpg, *.gif, *.bmp, *.ico, *.tiff, *.wmf)|*.greenshot; *.png; *.jpg; *.jpeg; *.gif; *.bmp; *.ico; *.tiff; *.tif; *.wmf" + Filter = @"Image files (*.greenshot, *.png, *.jpg, *.gif, *.bmp, *.ico, *.tiff, *.wmf)|*.greenshot; *.png; *.jpg; *.jpeg; *.gif; *.bmp; *.ico; *.tiff; *.tif; *.wmf" }; - if (openFileDialog.ShowDialog() == DialogResult.OK) { - if (File.Exists(openFileDialog.FileName)) { - CaptureHelper.CaptureFile(openFileDialog.FileName); - } - } - } + if (openFileDialog.ShowDialog() != DialogResult.OK) + { + return; + } + if (File.Exists(openFileDialog.FileName)) { + CaptureHelper.CaptureFile(openFileDialog.FileName); + } + } private void CaptureFullScreen() { CaptureHelper.CaptureFullscreen(true, _conf.ScreenCaptureMode); @@ -807,7 +813,7 @@ namespace Greenshot { int index = counter.ContainsKey(tabData.Key) ? counter[tabData.Key] : 0; captureIeTabItem.Image = tabData.Key.DisplayIcon; captureIeTabItem.Tag = new KeyValuePair(tabData.Key, index++); - captureIeTabItem.Click += Contextmenu_captureiefromlist_Click; + captureIeTabItem.Click += Contextmenu_CaptureIeFromList_Click; contextmenu_captureiefromlist.DropDownItems.Add(captureIeTabItem); if (counter.ContainsKey(tabData.Key)) { counter[tabData.Key] = index; @@ -884,7 +890,7 @@ namespace Greenshot { // captureForm.MakeCapture(CaptureMode.Window, false); // Now we check which windows are there to capture ToolStripMenuItem captureWindowFromListMenuItem = (ToolStripMenuItem)sender; - AddCaptureWindowMenuItems(captureWindowFromListMenuItem, Contextmenu_capturewindowfromlist_Click); + AddCaptureWindowMenuItems(captureWindowFromListMenuItem, Contextmenu_CaptureWindowFromList_Click); } private void CaptureWindowFromListMenuDropDownClosed(object sender, EventArgs e) { @@ -959,19 +965,19 @@ namespace Greenshot { }); } - private void Contextmenu_capturelastregionClick(object sender, EventArgs e) { + private void Contextmenu_CaptureLastRegionClick(object sender, EventArgs e) { BeginInvoke((MethodInvoker)delegate { CaptureHelper.CaptureLastRegion(false); }); } - private void Contextmenu_capturewindow_Click(object sender,EventArgs e) { + private void Contextmenu_CaptureWindow_Click(object sender,EventArgs e) { BeginInvoke((MethodInvoker)delegate { CaptureHelper.CaptureWindowInteractive(false); }); } - private void Contextmenu_capturewindowfromlist_Click(object sender,EventArgs e) { + private void Contextmenu_CaptureWindowFromList_Click(object sender,EventArgs e) { ToolStripMenuItem clickedItem = (ToolStripMenuItem)sender; BeginInvoke((MethodInvoker)delegate { try { @@ -983,11 +989,11 @@ namespace Greenshot { }); } - private void Contextmenu_captureie_Click(object sender, EventArgs e) { + private void Contextmenu_CaptureIe_Click(object sender, EventArgs e) { CaptureIE(); } - private void Contextmenu_captureiefromlist_Click(object sender, EventArgs e) { + private void Contextmenu_CaptureIeFromList_Click(object sender, EventArgs e) { if (!_conf.IECapture) { LOG.InfoFormat("IE Capture is disabled."); return; @@ -1015,9 +1021,9 @@ namespace Greenshot { /// /// Context menu entry "Support Greenshot" /// - /// - /// - private void Contextmenu_donateClick(object sender, EventArgs e) { + /// object + /// EventArgs + private void Contextmenu_DonateClick(object sender, EventArgs e) { BeginInvoke((MethodInvoker)delegate { Process.Start("http://getgreenshot.org/support/?version=" + Assembly.GetEntryAssembly().GetName().Version); }); @@ -1028,7 +1034,7 @@ namespace Greenshot { /// /// /// - private void Contextmenu_settingsClick(object sender, EventArgs e) { + private void Contextmenu_SettingsClick(object sender, EventArgs e) { BeginInvoke((MethodInvoker)ShowSetting); } @@ -1056,7 +1062,7 @@ namespace Greenshot { /// /// /// - private void Contextmenu_aboutClick(object sender, EventArgs e) { + private void Contextmenu_AboutClick(object sender, EventArgs e) { ShowAbout(); } @@ -1110,7 +1116,7 @@ namespace Greenshot { // Only add if the value is not fixed if (!_conf.Values["CaptureMousepointer"].IsFixed) { - // For the capture mousecursor option + // For the capture mouse-cursor option ToolStripMenuSelectListItem captureMouseItem = new ToolStripMenuSelectListItem { Text = Language.GetString("settings_capture_mousepointer"), @@ -1463,27 +1469,5 @@ namespace Greenshot { notifyIcon = null; } } - - - /// - /// Do work in the background - /// - /// - /// - private void BackgroundWorkerTimerTick(object sender, EventArgs e) { - if (_conf.MinimizeWorkingSetSize) { - PsAPI.EmptyWorkingSet(); - } - if (UpdateHelper.IsUpdateCheckNeeded()) { - LOG.Debug("BackgroundWorkerTimerTick checking for update"); - // Start update check in the background - var backgroundTask = new Thread(UpdateHelper.CheckAndAskForUpdate) - { - Name = "Update check", - IsBackground = true - }; - backgroundTask.Start(); - } - } - } + } } \ No newline at end of file diff --git a/Greenshot/Helpers/Entities/UpdateFeed.cs b/Greenshot/Helpers/Entities/UpdateFeed.cs new file mode 100644 index 000000000..a7a80afb8 --- /dev/null +++ b/Greenshot/Helpers/Entities/UpdateFeed.cs @@ -0,0 +1,35 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using Newtonsoft.Json; + +namespace Greenshot.Helpers.Entities +{ + [JsonObject] + public class UpdateFeed + { + [JsonProperty("release")] + public string CurrentReleaseVersion { get; set; } + + [JsonProperty("beta")] + public string CurrentBetaVersion { get; set; } + } +} diff --git a/Greenshot/Helpers/UpdateHelper.cs b/Greenshot/Helpers/UpdateHelper.cs deleted file mode 100644 index 479a66955..000000000 --- a/Greenshot/Helpers/UpdateHelper.cs +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Windows.Forms; -using Greenshot.Configuration; -using GreenshotPlugin.Core; -using GreenshotPlugin.IniFile; -using log4net; - -namespace Greenshot.Helpers { - /// - /// Description of RssFeedHelper. - /// - public static class UpdateHelper { - private static readonly ILog Log = LogManager.GetLogger(typeof(UpdateHelper)); - private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection(); - private const string StableDownloadLink = "https://getgreenshot.org/downloads/"; - private const string VersionHistoryLink = "https://getgreenshot.org/version-history/"; - private static readonly object LockObject = new object(); - private static RssFile _latestGreenshot; - private static string _downloadLink = StableDownloadLink; - - /// - /// Is an update check needed? - /// - /// bool true if yes - public static bool IsUpdateCheckNeeded() { - lock (LockObject) - { - if (CoreConfig.UpdateCheckInterval == 0) - { - return false; - } - DateTime checkTime = CoreConfig.LastUpdateCheck; - checkTime = checkTime.AddDays(CoreConfig.UpdateCheckInterval); - if (DateTime.Now.CompareTo(checkTime) < 0) - { - Log.DebugFormat("No need to check RSS feed for updates, feed check will be after {0}", checkTime); - return false; - } - Log.DebugFormat("Update check is due, last check was {0} check needs to be made after {1} (which is one {2} later)", CoreConfig.LastUpdateCheck, checkTime, CoreConfig.UpdateCheckInterval); - if (!RssHelper.IsRssModifiedAfter(CoreConfig.LastUpdateCheck)) - { - Log.DebugFormat("RSS feed has not been updated since after {0}", CoreConfig.LastUpdateCheck); - return false; - } - } - return true; - } - - /// - /// Read the RSS feed to see if there is a Greenshot update - /// - public static void CheckAndAskForUpdate() { - lock (LockObject) { - Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version; - // Test like this: - // currentVersion = new Version("0.8.1.1198"); - - // Make sure we update the LastUpdateCheck, in case an error occurs we should not retry every 5 minutes - // This actually prevents an update check, but rather one to less than many to many - CoreConfig.LastUpdateCheck = DateTime.Now; - - try - { - _latestGreenshot = null; - ProcessRssInfo(currentVersion); - if (_latestGreenshot != null) { - var notifyIcon = SimpleServiceProvider.Current.GetInstance(); - - notifyIcon.BalloonTipClicked += HandleBalloonTipClick; - notifyIcon.BalloonTipClosed += CleanupBalloonTipClick; - notifyIcon.ShowBalloonTip(10000, "Greenshot", Language.GetFormattedString(LangKey.update_found, "'" + _latestGreenshot.File + "'"), ToolTipIcon.Info); - } - } catch (Exception e) { - Log.Error("An error occured while checking for updates, the error will be ignored: ", e); - } - } - } - - private static void CleanupBalloonTipClick(object sender, EventArgs e) { - var notifyIcon = SimpleServiceProvider.Current.GetInstance(); - notifyIcon.BalloonTipClicked -= HandleBalloonTipClick; - notifyIcon.BalloonTipClosed -= CleanupBalloonTipClick; - } - - private static void HandleBalloonTipClick(object sender, EventArgs e) { - try { - if (_latestGreenshot != null) { - // "Direct" download link - // Process.Start(latestGreenshot.Link); - // Go to getgreenshot.org - Process.Start(_downloadLink); - } - } catch (Exception) { - MessageBox.Show(Language.GetFormattedString(LangKey.error_openlink, _downloadLink), Language.GetString(LangKey.error)); - } finally { - CleanupBalloonTipClick(sender, e); - } - } - - private static void ProcessRssInfo(Version currentVersion) { - // Reset latest Greenshot - IList rssFiles = RssHelper.ReadRss(); - - if (rssFiles == null) { - return; - } - - // Retrieve the current and latest greenshot - foreach(RssFile rssFile in rssFiles) { - if (rssFile.File.StartsWith("Greenshot")) { - // check for exe - if (!rssFile.IsExe) { - continue; - } - - // do we have a version? - if (rssFile.Version == null) { - Log.DebugFormat("Skipping unversioned exe {0} which is published at {1} : {2}", rssFile.File, rssFile.Pubdate.ToLocalTime(), rssFile.Link); - continue; - } - - // if the file is unstable, we will skip it when: - // the current version is a release or release candidate AND check unstable is turned off. - if (rssFile.IsUnstable) { - // Skip if we shouldn't check unstables - if ((CoreConfig.BuildState == BuildStates.RELEASE) && !CoreConfig.CheckForUnstable) { - continue; - } - } - - // if the file is a release candidate, we will skip it when: - // the current version is a release AND check unstable is turned off. - if (rssFile.IsReleaseCandidate) { - if (CoreConfig.BuildState == BuildStates.RELEASE && !CoreConfig.CheckForUnstable) { - continue; - } - } - - // Compare versions - int versionCompare = rssFile.Version.CompareTo(currentVersion); - if (versionCompare > 0) { - Log.DebugFormat("Found newer Greenshot '{0}' with version {1} published at {2} : {3}", rssFile.File, rssFile.Version, rssFile.Pubdate.ToLocalTime(), rssFile.Link); - if (_latestGreenshot == null || rssFile.Version.CompareTo(_latestGreenshot.Version) > 0) { - _latestGreenshot = rssFile; - if (rssFile.IsReleaseCandidate || rssFile.IsUnstable) { - _downloadLink = VersionHistoryLink; - } else { - _downloadLink = StableDownloadLink; - } - } - } else if (versionCompare < 0) { - Log.DebugFormat("Skipping older greenshot with version {0}", rssFile.Version); - } else if (versionCompare == 0) { - Log.DebugFormat("Found current version as exe {0} with version {1} published at {2} : {3}", rssFile.File, rssFile.Version, rssFile.Pubdate.ToLocalTime(), rssFile.Link); - } - } - } - } - } -} diff --git a/Greenshot/Helpers/UpdateService.cs b/Greenshot/Helpers/UpdateService.cs new file mode 100644 index 000000000..d1f755472 --- /dev/null +++ b/Greenshot/Helpers/UpdateService.cs @@ -0,0 +1,222 @@ +// Greenshot - a free and open source screenshot tool +// Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom +// +// For more information see: http://getgreenshot.org/ +// The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 1 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Dapplo.HttpExtensions; +using Dapplo.HttpExtensions.JsonNet; +using Greenshot.Configuration; +using Greenshot.Helpers.Entities; +using GreenshotPlugin.Core; +using GreenshotPlugin.IniFile; +using GreenshotPlugin.Interfaces; +using log4net; + +namespace Greenshot.Helpers +{ + /// + /// This processes the information, if there are updates available. + /// + public class UpdateService + { + private static readonly ILog Log = LogManager.GetLogger(typeof(UpdateService)); + private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection(); + private static readonly Uri UpdateFeed = new Uri("https://getgreenshot.org/update-feed.json"); + private static readonly Uri Downloads = new Uri("https://getgreenshot.org/downloads"); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + /// + /// Provides the current version + /// + public Version CurrentVersion { get; } + + /// + /// Provides the latest known version + /// + public Version LatestReleaseVersion { get; private set; } + + /// + /// The latest beta version + /// + public Version LatestBetaVersion { get; private set; } + + /// + /// Checks if there is an release update available + /// + public bool IsUpdateAvailable => LatestReleaseVersion > CurrentVersion; + + /// + /// Checks if there is an beta update available + /// + public bool IsBetaUpdateAvailable => LatestBetaVersion > CurrentVersion; + + /// + /// Keep track of when the update was shown, so it won't be every few minutes + /// + public DateTimeOffset LastUpdateShown = DateTimeOffset.MinValue; + + /// + /// Constructor with dependencies + /// + public UpdateService() + { + JsonNetJsonSerializer.RegisterGlobally(); + var version = FileVersionInfo.GetVersionInfo(GetType().Assembly.Location); + LatestReleaseVersion = CurrentVersion = new Version(version.FileMajorPart, version.FileMinorPart, version.FileBuildPart); + CoreConfig.LastSaveWithVersion = CurrentVersion.ToString(); + } + + /// + /// Start the background task which checks for updates + /// + public void Startup() + { + _ = BackgroundTask(() => TimeSpan.FromDays(CoreConfig.UpdateCheckInterval), UpdateCheck, _cancellationTokenSource.Token); + } + + /// + /// Stop the update checks + /// + public void Shutdown() + { + if (!_cancellationTokenSource.IsCancellationRequested) + { + _cancellationTokenSource.Cancel(); + } + } + + /// + /// This runs a periodic task in the background + /// + /// Func which returns a TimeSpan + /// Func which returns a task + /// CancellationToken + /// Task + private async Task BackgroundTask(Func intervalFactory, Func reoccurringTask, CancellationToken cancellationToken = default) + { + // Initial delay, to make sure this doesn't happen at the startup + await Task.Delay(20000, cancellationToken); + Log.Info("Starting background task to check for updates"); + await Task.Run(async () => + { + while (!cancellationToken.IsCancellationRequested) + { + var interval = intervalFactory(); + var task = reoccurringTask; + // If the check is disabled, handle that here + if (TimeSpan.Zero == interval) + { + interval = TimeSpan.FromMinutes(10); + task = c => Task.FromResult(true); + } + + try + { + await task(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + Log.Error("Error occured when trying to check for updates.", ex); + } + + try + { + await Task.Delay(interval, cancellationToken).ConfigureAwait(false); + } + catch (TaskCanceledException) + { + // Ignore, this always happens + } + catch (Exception ex) + { + Log.Error("Error occured await for the next update check.", ex); + } + + } + }, cancellationToken).ConfigureAwait(false); + } + + /// + /// Do the actual update check + /// + /// CancellationToken + /// Task + private async Task UpdateCheck(CancellationToken cancellationToken = default) + { + Log.InfoFormat("Checking for updates from {0}", UpdateFeed); + var updateFeed = await UpdateFeed.GetAsAsync(cancellationToken); + if (updateFeed == null) + { + return; + } + + CoreConfig.LastUpdateCheck = DateTime.Now; + + ProcessFeed(updateFeed); + + // Only show if the update was shown >24 hours ago. + if (DateTimeOffset.Now.AddDays(-1) > LastUpdateShown) + { + if (IsBetaUpdateAvailable) + { + LastUpdateShown = DateTimeOffset.Now; + ShowUpdate(LatestBetaVersion); + } else if (IsUpdateAvailable) + { + LastUpdateShown = DateTimeOffset.Now; + ShowUpdate(LatestReleaseVersion); + } + } + + } + + + /// + /// This takes care of creating the toast view model, publishing it, and disposing afterwards + /// + /// Version + private void ShowUpdate(Version newVersion) + { + var notificationService = SimpleServiceProvider.Current.GetInstance(); + var message = Language.GetFormattedString(LangKey.update_found, newVersion.ToString()); + notificationService.ShowInfoMessage(message, 10000, () => Process.Start(Downloads.AbsoluteUri)); + } + + /// + /// Process the update feed to get the latest version + /// + /// + private void ProcessFeed(UpdateFeed updateFeed) + { + var latestReleaseString = Regex.Replace(updateFeed.CurrentReleaseVersion, "[a-zA-Z\\-]*", ""); + if (Version.TryParse(latestReleaseString, out var latestReleaseVersion)) + { + LatestReleaseVersion = latestReleaseVersion; + } + var latestBetaString = Regex.Replace(updateFeed.CurrentBetaVersion, "[a-zA-Z\\-]*", ""); + if (Version.TryParse(latestBetaString, out var latestBetaVersion)) + { + LatestBetaVersion = latestBetaVersion; + } + } + } +} \ No newline at end of file diff --git a/GreenshotPlugin/Core/RssHelper.cs b/GreenshotPlugin/Core/RssHelper.cs deleted file mode 100644 index f7a48289c..000000000 --- a/GreenshotPlugin/Core/RssHelper.cs +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text.RegularExpressions; -using System.Xml; -using log4net; - -namespace GreenshotPlugin.Core { - public class RssFile { - public string File { get; } - private readonly DateTime _pubdate; - public DateTime Pubdate => _pubdate; - - public string Link { get; } - public Version Version { get; set; } - - public string Language { get; set; } - - public bool IsExe { - get { - if (File != null) { - return File.ToLower().EndsWith(".exe"); - } - return false; - } - } - - public bool IsUnstable { - get { - if (File != null) { - return File.ToLower().Contains("unstable"); - } - return false; - } - } - - public bool IsReleaseCandidate { - get { - if (File != null) { - return Regex.IsMatch(File.ToLower(), "rc[0-9]+"); - } - return false; - } - } - - public RssFile(string file, string pubdate, string link) { - File = file; - if (!DateTime.TryParse(pubdate, out _pubdate)) - { - DateTime.TryParseExact(pubdate.Replace(" UT", ""), "ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out _pubdate); - } - Link = link; - } - } - - /// - /// Description of RssHelper. - /// - public class RssHelper { - private static readonly ILog Log = LogManager.GetLogger(typeof(RssHelper)); - private const string Rssfeed = "http://getgreenshot.org/project-feed/"; - - /// - /// This is using the HTTP HEAD Method to check if the RSS Feed is modified after the supplied date - /// - /// DateTime - /// true if the feed is newer - public static bool IsRssModifiedAfter(DateTime updateTime) { - DateTime lastModified = NetworkHelper.GetLastModified(new Uri(Rssfeed)); - if (lastModified == DateTime.MinValue) - { - // Time could not be read, just take now and add one hour to it. - // This assist BUG-1850 - lastModified = DateTime.Now.AddHours(1); - } - return updateTime.CompareTo(lastModified) < 0; - } - - /// - /// Read the Greenshot RSS feed, so we can use this information to check for updates - /// - /// List with RssFile(s) - public static IList ReadRss() { - XmlDocument rssDoc = new XmlDocument(); - try { - HttpWebRequest webRequest = NetworkHelper.CreateWebRequest(Rssfeed); - XmlTextReader rssReader = new XmlTextReader(webRequest.GetResponse().GetResponseStream()); - - // Load the XML content into a XmlDocument - rssDoc.Load(rssReader); - } catch (Exception wE) { - Log.WarnFormat("Problem reading RSS from {0}", Rssfeed); - Log.Warn(wE.Message); - return null; - } - - // Loop for the tag - XmlNode nodeRss = null; - for (int i = 0; i < rssDoc.ChildNodes.Count; i++) { - // If it is the rss tag - if (rssDoc.ChildNodes[i].Name == "rss") { - // tag found - nodeRss = rssDoc.ChildNodes[i]; - } - } - - if (nodeRss == null) { - Log.Debug("No RSS Feed!"); - return null; - } - - // Loop for the tag - XmlNode nodeChannel = null; - for (int i = 0; i < nodeRss.ChildNodes.Count; i++) { - // If it is the channel tag - if (nodeRss.ChildNodes[i].Name == "channel") { - // tag found - nodeChannel = nodeRss.ChildNodes[i]; - } - } - - if (nodeChannel == null) { - Log.Debug("No channel in RSS feed!"); - return null; - } - - IList rssFiles = new List(); - - // Loop for the , <link>, <description> and all the other tags - for (int i = 0; i < nodeChannel.ChildNodes.Count; i++) { - // If it is the item tag, then it has children tags which we will add as items to the ListView - - if (nodeChannel.ChildNodes[i].Name != "item") - { - continue; - } - XmlNode nodeItem = nodeChannel.ChildNodes[i]; - string link = nodeItem["link"]?.InnerText; - string pubdate = nodeItem["pubDate"]?.InnerText; - try { - if (link == null) - { - continue; - } - Match match = Regex.Match(Uri.UnescapeDataString(link), @"^.*\/(Greenshot.+)\/download$"); - if (!match.Success) - { - continue; - } - string file = match.Groups[1].Value; - - RssFile rssFile = new RssFile(file, pubdate, link); - if (file.EndsWith(".exe") ||file.EndsWith(".zip")) { - string version = Regex.Replace(file, @".*[a-zA-Z_]\-", string.Empty); - version = version.Replace(@"\-[a-zA-Z]+.*",""); - version = Regex.Replace(version, @"\.exe$", string.Empty); - version = Regex.Replace(version, @"\.zip$", string.Empty); - version = Regex.Replace(version, @"RC[0-9]+", string.Empty); - if (version.Trim().Length > 0) { - version = version.Replace('-','.'); - version = version.Replace(',','.'); - version = Regex.Replace(version, @"^[a-zA-Z_]*\.", string.Empty); - version = Regex.Replace(version, @"\.[a-zA-Z_]*$", string.Empty); - - try { - rssFile.Version = new Version(version); - } catch (Exception) { - Log.DebugFormat("Found invalid version {0} in file {1}", version, file); - } - } - rssFiles.Add(rssFile); - } - } catch (Exception ex) { - Log.WarnFormat("Couldn't read RSS entry for: {0}", nodeChannel["title"]?.InnerText); - Log.Warn("Reason: ", ex); - } - } - - return rssFiles; - } - } -}