diff --git a/src/Greenshot.Addons/Core/CoreConfigurationExtensions.cs b/src/Greenshot.Addons/Core/CoreConfigurationExtensions.cs index 12c449554..d9bbec239 100644 --- a/src/Greenshot.Addons/Core/CoreConfigurationExtensions.cs +++ b/src/Greenshot.Addons/Core/CoreConfigurationExtensions.cs @@ -103,6 +103,7 @@ namespace Greenshot.Addons.Core /// /// Supply values we can't put as defaults /// + /// ICoreConfiguration /// The property to return a default for /// object with the default value for the supplied property public static object GetDefault(this ICoreConfiguration coreConfiguration, string property) diff --git a/src/Greenshot.Addons/Core/RssHelper.cs b/src/Greenshot.Addons/Core/RssHelper.cs deleted file mode 100644 index 675f4e2fe..000000000 --- a/src/Greenshot.Addons/Core/RssHelper.cs +++ /dev/null @@ -1,235 +0,0 @@ -#region Greenshot GNU General Public License - -// Greenshot - a free and open source screenshot tool -// Copyright (C) 2007-2018 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 . - -#endregion - -#region Usings - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text.RegularExpressions; -using System.Xml; -using Dapplo.Log; - -#endregion - -namespace Greenshot.Addons.Core -{ - public class RssFile - { - private readonly DateTime _pubdate; - - 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; - } - - public string File { get; } - 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 IsAlpha => File?.ToLowerInvariant().Contains("alpha") == true; - - public bool IsBeta => File?.ToLowerInvariant().Contains("beta") == true; - - public bool IsReleaseCandidate - { - get - { - if (File != null) - { - return Regex.IsMatch(File.ToLower(), "rc[0-9]+"); - } - return false; - } - } - } - - /// - /// Description of RssHelper. - /// - public class RssHelper - { - private const string Rssfeed = "http://getgreenshot.org/project-feed/"; - private static readonly LogSource Log = new LogSource(); - - /// - /// 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) - { - var 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() - { - var rssDoc = new XmlDocument(); - try - { - var webRequest = NetworkHelper.CreateWebRequest(Rssfeed); - var rssReader = new XmlTextReader(webRequest.GetResponse().GetResponseStream()); - - // Load the XML content into a XmlDocument - rssDoc.Load(rssReader); - } - catch (Exception wE) - { - Log.Warn().WriteLine("Problem reading RSS from {0}", Rssfeed); - Log.Warn().WriteLine(wE.Message); - return null; - } - - // Loop for the tag - XmlNode nodeRss = null; - for (var 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().WriteLine("No RSS Feed!"); - return null; - } - - // Loop for the tag - XmlNode nodeChannel = null; - for (var 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().WriteLine("No channel in RSS feed!"); - return null; - } - - IList rssFiles = new List(); - - // Loop for the , <link>, <description> and all the other tags - for (var 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; - } - var nodeItem = nodeChannel.ChildNodes[i]; - var link = nodeItem["link"]?.InnerText; - var pubdate = nodeItem["pubDate"]?.InnerText; - try - { - if (link == null) - { - continue; - } - var match = Regex.Match(Uri.UnescapeDataString(link), @"^.*\/(Greenshot.+)\/download$"); - if (!match.Success) - { - continue; - } - var file = match.Groups[1].Value; - - var rssFile = new RssFile(file, pubdate, link); - if (file.EndsWith(".exe") || file.EndsWith(".zip")) - { - var version = Regex.Replace(file, @".*[a-zA-Z_]\-", ""); - version = version.Replace(@"\-[a-zA-Z]+.*", ""); - version = Regex.Replace(version, @"\.exe$", ""); - version = Regex.Replace(version, @"\.zip$", ""); - version = Regex.Replace(version, @"RC[0-9]+", ""); - if (version.Trim().Length > 0) - { - version = version.Replace('-', '.'); - version = version.Replace(',', '.'); - version = Regex.Replace(version, @"^[a-zA-Z_]*\.", ""); - version = Regex.Replace(version, @"\.[a-zA-Z_]*$", ""); - - try - { - rssFile.Version = new Version(version); - } - catch (Exception) - { - Log.Debug().WriteLine("Found invalid version {0} in file {1}", version, file); - } - } - rssFiles.Add(rssFile); - } - } - catch (Exception ex) - { - Log.Warn().WriteLine("Couldn't read RSS entry for: {0}", nodeChannel["title"]?.InnerText); - Log.Warn().WriteLine(ex, "Reason: "); - } - } - - return rssFiles; - } - } -} \ No newline at end of file diff --git a/src/Greenshot.Addons/Greenshot.Addons.csproj b/src/Greenshot.Addons/Greenshot.Addons.csproj index e22c8ed1e..0150e00ce 100644 --- a/src/Greenshot.Addons/Greenshot.Addons.csproj +++ b/src/Greenshot.Addons/Greenshot.Addons.csproj @@ -363,7 +363,6 @@ <Compile Include="Core\NetworkHelper.cs" /> <Compile Include="Core\ObjectExtensions.cs" /> <Compile Include="Core\PluginUtils.cs" /> - <Compile Include="Core\RssHelper.cs" /> <Compile Include="Core\WindowCapture.cs" /> <Compile Include="Interfaces\ICaptureDetails.cs" /> <Compile Include="Interfaces\Drawing\IDrawableContainer.cs" /> diff --git a/src/Greenshot.Tests/Greenshot.Tests.csproj b/src/Greenshot.Tests/Greenshot.Tests.csproj index 1bf980274..57cba32f0 100644 --- a/src/Greenshot.Tests/Greenshot.Tests.csproj +++ b/src/Greenshot.Tests/Greenshot.Tests.csproj @@ -83,6 +83,9 @@ <Reference Include="Dapplo.CaliburnMicro.Translations, Version=1.0.46.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Dapplo.CaliburnMicro.Translations.1.0.46\lib\net46\Dapplo.CaliburnMicro.Translations.dll</HintPath> </Reference> + <Reference Include="Dapplo.HttpExtensions, Version=0.8.35.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\Dapplo.HttpExtensions.0.8.35\lib\net46\Dapplo.HttpExtensions.dll</HintPath> + </Reference> <Reference Include="Dapplo.Ini, Version=0.5.28.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Dapplo.Ini.0.5.28\lib\net45\Dapplo.Ini.dll</HintPath> </Reference> @@ -173,6 +176,7 @@ <HintPath>..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath> </Reference> <Reference Include="System.Management" /> + <Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Numerics" /> <Reference Include="System.Reactive, Version=4.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL"> <HintPath>..\packages\System.Reactive.4.0.0\lib\net46\System.Reactive.dll</HintPath> @@ -200,6 +204,7 @@ <Reference Include="System.Security.Cryptography.X509Certificates, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\System.Security.Cryptography.X509Certificates.4.3.2\lib\net46\System.Security.Cryptography.X509Certificates.dll</HintPath> </Reference> + <Reference Include="System.ServiceModel" /> <Reference Include="System.Text.Encoding.CodePages, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <HintPath>..\packages\System.Text.Encoding.CodePages.4.5.0\lib\net46\System.Text.Encoding.CodePages.dll</HintPath> </Reference> @@ -262,11 +267,13 @@ <Compile Include="Murmur3Tests.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="StitchTests.cs" /> + <Compile Include="UpdateTests.cs" /> </ItemGroup> <ItemGroup> - <None Include="app.config" /> - </ItemGroup> - <ItemGroup> + <ProjectReference Include="..\Greenshot.Addons\Greenshot.Addons.csproj"> + <Project>{5b924697-4dcd-4f98-85f1-105cb84b7341}</Project> + <Name>Greenshot.Addons</Name> + </ProjectReference> <ProjectReference Include="..\Greenshot.Gfx.Experimental\Greenshot.Gfx.Experimental.csproj"> <Project>{14894a45-aa2c-4bc3-85a3-e388d0bdc1ca}</Project> <Name>Greenshot.Gfx.Experimental</Name> @@ -306,6 +313,12 @@ <Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.2.6.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" /> <Analyzer Include="..\packages\xunit.analyzers.0.9.0\analyzers\dotnet\cs\xunit.analyzers.dll" /> </ItemGroup> + <ItemGroup> + <None Include="TestFiles\project-feed.xml"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + <SubType>Designer</SubType> + </None> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> diff --git a/src/Greenshot.Tests/TestFiles/project-feed.xml b/src/Greenshot.Tests/TestFiles/project-feed.xml new file mode 100644 index 000000000..a36a74ef1 --- /dev/null +++ b/src/Greenshot.Tests/TestFiles/project-feed.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?> +<rss version="2.0"> + <channel> + <title>Greenshot + https://getgreenshot.org + Greenshot update feed + + Greenshot-INSTALLER-1.2.9.129-RELEASE.exe + http://sourceforge/projects/greenshot/files/release/Greenshot/Greenshot-INSTALLER-1.2.9.129-RELEASE.exe/download + Sat, 28 Jan 2017 15:59:07 +0000 + + + + Greenshot-INSTALLER-1.2.10.6-RELEASE.exe + http://sourceforge/projects/greenshot/files/release/Greenshot/Greenshot-INSTALLER-1.2.10.6-RELEASE.exe/download + Wed, 09 Aug 2017 15:35:30 +0000 + + + + Greenshot-INSTALLER-1.2.9.112-RELEASE.exe + http://sourceforge/projects/greenshot/files/release/Greenshot/Greenshot-INSTALLER-1.2.9.112-RELEASE.exe/download + Fri, 30 Dec 2016 21:39:18 +0000 + + + + Greenshot-INSTALLER-1.2.9.104-RELEASE.exe + http://sourceforge/projects/greenshot/files/release/Greenshot/Greenshot-INSTALLER-1.2.9.104-RELEASE.exe/download + Thu, 15 Dec 2016 19:35:59 +0000 + + + + Greenshot-INSTALLER-1.3.0.14-BETA.exe + http://sourceforge/projects/greenshot/files/release/Greenshot/Greenshot-INSTALLER-1.3.0.0-BETA.exe/download + Thu, 03 Nov 2016 20:59:15 +0000 + + + + Greenshot-INSTALLER-1.2.8.12-RELEASE.exe + http://sourceforge/projects/greenshot/files/release/Greenshot/Greenshot-INSTALLER-1.2.8.12-RELEASE.exe/download + Tue, 10 Nov 2015 10:58:37 +0000 + + + + Greenshot-INSTALLER-1.2.10.6-RELEASE.exe + https://github.com/greenshot/greenshot/releases/download/Greenshot-RELEASE-1.2.10.6/Greenshot-INSTALLER-1.2.10.6-RELEASE.exe + Wed, 09 Aug 2017 15:35:30 +0000 + + + Greenshot-INSTALLER-1.2.9.129-RELEASE.exe + https://github.com/greenshot/greenshot/releases/download/Greenshot-RELEASE-1.2.9.129/Greenshot-INSTALLER-1.2.9.129-RELEASE.exe + Sat, 28 Jan 2017 15:59:07 +0000 + + + Greenshot-INSTALLER-1.2.9.112-RELEASE.exe + https://github.com/greenshot/greenshot/releases/download/Greenshot-RELEASE-1.2.9.112/Greenshot-INSTALLER-1.2.9.112-RELEASE.exe + Fri, 30 Dec 2016 21:39:18 +0000 + + + Greenshot-INSTALLER-1.2.9.104-RELEASE.exe + https://github.com/greenshot/greenshot/releases/download/Greenshot-RELEASE-1.2.9.104/Greenshot-INSTALLER-1.2.9.104-RELEASE.exe + Thu, 15 Dec 2016 19:35:59 +0000 + + + Greenshot-INSTALLER-1.2.20.99-RC1.exe + https://github.com/greenshot/greenshot/releases/download/Greenshot-RC1-1.2.20.99/Greenshot-INSTALLER-1.2.20.99-RC1.exe + Thu, 03 Nov 2016 20:59:15 +0000 + + + Greenshot-INSTALLER-1.2.8.12-RELEASE.exe + https://github.com/greenshot/greenshot/releases/download/Greenshot-RELEASE-1.2.8.12/Greenshot-INSTALLER-1.2.8.12-RELEASE.exe + Tue, 10 Nov 2015 10:58:37 +0000 + + + + \ No newline at end of file diff --git a/src/Greenshot.Tests/UpdateTests.cs b/src/Greenshot.Tests/UpdateTests.cs new file mode 100644 index 000000000..382d220da --- /dev/null +++ b/src/Greenshot.Tests/UpdateTests.cs @@ -0,0 +1,35 @@ +using System; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using System.Xml; +using Dapplo.HttpExtensions; +using Dapplo.Log; +using Greenshot.Components; +using Xunit; + +namespace Greenshot.Tests +{ + public class UpdateTests + { + [Fact] + public async Task UpdateTest_GetFeed() + { + var updateFeed = await new Uri("http://getgreenshot.org/project-feed/").GetAsAsync(); + Assert.True(updateFeed.Links.Count >0); + } + + [Fact] + public void UpdateTest() + { + var updateService = new UpdateService(null, null); + using (var reader = XmlReader.Create(@"TestFiles\project-feed.xml")) + { + var feed = SyndicationFeed.Load(reader); + updateService.ProcessFeed(feed); + Assert.Equal(new Version("1.2.10.6"), updateService.LatestVersion); + Assert.Equal(new Version("1.3.0.0"), updateService.BetaVersion); + Assert.Equal(new Version("1.2.20.99"), updateService.ReleaseCandidateVersion); + } + } + } +} diff --git a/src/Greenshot.Tests/app.config b/src/Greenshot.Tests/app.config deleted file mode 100644 index 4a3bb809c..000000000 --- a/src/Greenshot.Tests/app.config +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Greenshot.Tests/packages.config b/src/Greenshot.Tests/packages.config index 67c7225c8..2a90c7987 100644 --- a/src/Greenshot.Tests/packages.config +++ b/src/Greenshot.Tests/packages.config @@ -14,6 +14,7 @@ + diff --git a/src/Greenshot/Components/GreenshotClient.cs b/src/Greenshot/Components/GreenshotClient.cs index 7bca346c5..da6ce4ad8 100644 --- a/src/Greenshot/Components/GreenshotClient.cs +++ b/src/Greenshot/Components/GreenshotClient.cs @@ -36,7 +36,6 @@ namespace Greenshot.Components public static void Capture(string parameters) { - using (var factory = ChannelFactory) { var client = factory.CreateChannel(); diff --git a/src/Greenshot/Components/GreenshotStartupOrder.cs b/src/Greenshot/Components/GreenshotStartupOrder.cs index 14d026489..b0025826a 100644 --- a/src/Greenshot/Components/GreenshotStartupOrder.cs +++ b/src/Greenshot/Components/GreenshotStartupOrder.cs @@ -34,6 +34,7 @@ namespace Greenshot.Components /// Start the forms initialization before the shell is initialized! /// Forms = CaliburnStartOrder.Shell-1, + User = CaliburnStartOrder.User, Server = CaliburnStartOrder.User + 100, Sound = CaliburnStartOrder.User + 200 } diff --git a/src/Greenshot/Components/GreenshotUiStartupOrder.cs b/src/Greenshot/Components/GreenshotUiStartupOrder.cs index 0919e07a7..fc2b58c8d 100644 --- a/src/Greenshot/Components/GreenshotUiStartupOrder.cs +++ b/src/Greenshot/Components/GreenshotUiStartupOrder.cs @@ -29,7 +29,7 @@ namespace Greenshot.Components public enum GreenshotUiStartupOrder { TrayIcon = 100, - Plugins = 200, + Addons = 200, Hotkeys = 300 } } diff --git a/src/Greenshot/Components/MainFormStartup.cs b/src/Greenshot/Components/MainFormStartup.cs index b85c438d3..38e5cc3f1 100644 --- a/src/Greenshot/Components/MainFormStartup.cs +++ b/src/Greenshot/Components/MainFormStartup.cs @@ -25,6 +25,7 @@ using System; using System.Linq; using System.Windows.Forms; +using Autofac.Features.OwnedInstances; using Dapplo.Addons; using Dapplo.CaliburnMicro; using Dapplo.Log; @@ -45,12 +46,18 @@ namespace Greenshot.Components private static readonly LogSource Log = new LogSource(); private readonly ICoreConfiguration _coreConfiguration; private readonly MainForm _mainForm; + private readonly Func> _languageDialogFactory; private readonly WindowHandle _windowHandle; - public MainFormStartup(ICoreConfiguration coreConfiguration, MainForm mainForm, WindowHandle windowHandle) + public MainFormStartup( + ICoreConfiguration coreConfiguration, + MainForm mainForm, + Func> languageDialogFactory, + WindowHandle windowHandle) { _coreConfiguration = coreConfiguration; _mainForm = mainForm; + _languageDialogFactory = languageDialogFactory; _windowHandle = windowHandle; } @@ -61,9 +68,11 @@ namespace Greenshot.Components // if language is not set, show language dialog if (string.IsNullOrEmpty(_coreConfiguration.Language)) { - var languageDialog = LanguageDialog.GetInstance(); - languageDialog.ShowDialog(); - _coreConfiguration.Language = languageDialog.SelectedLanguage; + using (var ownedLanguageDialog = _languageDialogFactory()) + { + ownedLanguageDialog.Value.ShowDialog(); + _coreConfiguration.Language = ownedLanguageDialog.Value.SelectedLanguage; + } } // This makes sure the MainForm can initialize, calling show first would create the "Handle" and causing e.g. the DPI Handler to be to late. diff --git a/src/Greenshot/Components/UpdateService.cs b/src/Greenshot/Components/UpdateService.cs new file mode 100644 index 000000000..ed071e7ee --- /dev/null +++ b/src/Greenshot/Components/UpdateService.cs @@ -0,0 +1,233 @@ +#region Greenshot GNU General Public License + +// Greenshot - a free and open source screenshot tool +// Copyright (C) 2007-2018 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 . + +#endregion + +#region Usings + +using System; +using System.Diagnostics; +using System.Linq; +using System.ServiceModel.Syndication; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using Dapplo.Addons; +using Dapplo.CaliburnMicro; +using Dapplo.HttpExtensions; +using Dapplo.Log; +using Dapplo.Utils; +using Greenshot.Addons; +using Greenshot.Addons.Core; +using Greenshot.Forms; + +#endregion + +namespace Greenshot.Components +{ + /// + /// This processes the information, if there are updates available. + /// + [ServiceOrder(GreenshotStartupOrder.User)] + public class UpdateService : IStartup, IShutdown, IVersionProvider + { + private static readonly LogSource Log = new LogSource(); + private static readonly Regex VersionRegex = new Regex(@"^.*[^-]-(?[0-9\.]+)\-(?(release|beta|rc[0-9]+))\.exe.*", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private const string StableDownloadLink = "https://getgreenshot.org/downloads/"; + private static readonly Uri UpdateFeed = new Uri("http://getgreenshot.org/project-feed/"); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private readonly ICoreConfiguration _coreConfiguration; + private readonly IGreenshotLanguage _greenshotLanguage; + + /// + public Version CurrentVersion { get; } + + /// + public Version LatestVersion { get; private set; } + + /// + /// The latest beta version + /// + public Version BetaVersion { get; private set; } + + /// + /// The latest RC version + /// + public Version ReleaseCandidateVersion { get; private set; } + + /// + public bool IsUpdateAvailable => LatestVersion > CurrentVersion; + + /// + /// Constructor with dependencies + /// + /// ICoreConfiguration + /// IGreenshotLanguage + public UpdateService( + ICoreConfiguration coreConfiguration, + IGreenshotLanguage greenshotLanguage) + { + _coreConfiguration = coreConfiguration; + _greenshotLanguage = greenshotLanguage; + LatestVersion = CurrentVersion = GetType().Assembly.GetName().Version; + _coreConfiguration.LastSaveWithVersion = CurrentVersion.ToString(); + + } + + /// + public void Start() + { + var ignore = BackgroundTask(() => TimeSpan.FromDays(_coreConfiguration.UpdateCheckInterval), UpdateCheck, _cancellationTokenSource.Token); + } + + /// + public void Shutdown() + { + _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) + { + Log.Info().WriteLine("Starting background task"); + await Task.Run(async () => + { + while (true) + { + var interval = intervalFactory(); + var task = reoccurringTask; + if (TimeSpan.Zero == interval) + { + interval = TimeSpan.FromMinutes(10); + task = c => Task.FromResult(true); + } + try + { + await Task.WhenAll(Task.Delay(interval, cancellationToken), task(cancellationToken)); + } + catch (Exception ex) + { + Log.Error().WriteLine(ex, "Error occured when trying to check for updates."); + } + if (cancellationToken.IsCancellationRequested) + { + break; + } + } + }, cancellationToken); + Log.Info().WriteLine("Finished background task"); + } + + private async Task UpdateCheck(CancellationToken cancellationToken = default) + { + Log.Info().WriteLine("Checking for updates"); + var updateFeed = await UpdateFeed.GetAsAsync(cancellationToken); + if (updateFeed == null) + { + return; + } + _coreConfiguration.LastUpdateCheck = DateTime.Now; + + ProcessFeed(updateFeed); + + if (IsUpdateAvailable) + { + await UiContext.RunOn(() => + { + // TODO: Show update + MainForm.Instance.NotifyIcon.BalloonTipClicked += HandleBalloonTipClick; + MainForm.Instance.NotifyIcon.BalloonTipClosed += CleanupBalloonTipClick; + MainForm.Instance.NotifyIcon.ShowBalloonTip(10000, "Greenshot", string.Format(_greenshotLanguage.UpdateFound, LatestVersion), ToolTipIcon.Info); + }, cancellationToken); + } + } + + /// + /// Process the update feed to get the latest version + /// + /// + public void ProcessFeed(SyndicationFeed updateFeed) + { + var versions = + from link in updateFeed.Items.SelectMany(i => i.Links) + select VersionRegex.Match(link.Uri.AbsoluteUri) into match + where match.Success + group match by Regex.Replace(match.Groups["type"].Value, @"[\d-]", string.Empty) into groupedVersions + select groupedVersions.OrderByDescending(m => new Version(m.Groups["version"].Value)).First(); + + foreach (var versionMatch in versions) + { + var version = new Version(versionMatch.Groups["version"].Value); + var type = versionMatch.Groups["type"].Value; + if (string.IsNullOrEmpty(type)) + { + continue; + } + Log.Debug().WriteLine("Got {0} {1}", type, version); + if ("release".Equals(type, StringComparison.OrdinalIgnoreCase)) + { + LatestVersion = version; + } + if ("beta".Equals(type, StringComparison.OrdinalIgnoreCase)) + { + BetaVersion = version; + } + if ("rc".Equals(type, StringComparison.OrdinalIgnoreCase)) + { + ReleaseCandidateVersion = version; + } + } + } + + private void CleanupBalloonTipClick(object sender, EventArgs e) + { + MainForm.Instance.NotifyIcon.BalloonTipClicked -= HandleBalloonTipClick; + MainForm.Instance.NotifyIcon.BalloonTipClosed -= CleanupBalloonTipClick; + } + + private void HandleBalloonTipClick(object sender, EventArgs e) + { + try + { + // "Direct" download link + // Process.Start(latestGreenshot.Link); + // Go to getgreenshot.org + Process.Start(StableDownloadLink); + } + catch (Exception) + { + MessageBox.Show(string.Format(_greenshotLanguage.ErrorOpenlink, StableDownloadLink), _greenshotLanguage.Error); + } + finally + { + CleanupBalloonTipClick(sender, e); + } + } + } +} \ No newline at end of file diff --git a/src/Greenshot/Destinations/PrinterDestination.cs b/src/Greenshot/Destinations/PrinterDestination.cs index 3065afbfc..cd8219080 100644 --- a/src/Greenshot/Destinations/PrinterDestination.cs +++ b/src/Greenshot/Destinations/PrinterDestination.cs @@ -28,11 +28,11 @@ using System.Collections.Generic; using System.Drawing; using System.Drawing.Printing; using System.Windows.Forms; +using Autofac.Features.OwnedInstances; using Greenshot.Addons; using Greenshot.Addons.Components; using Greenshot.Addons.Core; using Greenshot.Addons.Interfaces; -using Greenshot.Configuration; using Greenshot.Helpers; #endregion @@ -46,19 +46,23 @@ namespace Greenshot.Destinations public class PrinterDestination : AbstractDestination { private readonly IGreenshotLanguage _greenshotLanguage; + private readonly Func> _printHelperFactory; private readonly string _printerName; public PrinterDestination(ICoreConfiguration coreConfiguration, - IGreenshotLanguage greenshotLanguage - ): base(coreConfiguration, greenshotLanguage) + IGreenshotLanguage greenshotLanguage, + Func> printHelperFactory + ) : base(coreConfiguration, greenshotLanguage) { _greenshotLanguage = greenshotLanguage; + _printHelperFactory = printHelperFactory; } protected PrinterDestination( ICoreConfiguration coreConfiguration, IGreenshotLanguage greenshotLanguage, - string printerName) : this(coreConfiguration, greenshotLanguage) + Func> printHelperFactory, + string printerName) : this(coreConfiguration, greenshotLanguage, printHelperFactory) { _printerName = printerName; } @@ -110,7 +114,7 @@ namespace Greenshot.Destinations }); foreach (var printer in printers) { - yield return new PrinterDestination(CoreConfiguration, GreenshotLanguage, printer); + yield return new PrinterDestination(CoreConfiguration, GreenshotLanguage, _printHelperFactory, printer); } } @@ -127,25 +131,26 @@ namespace Greenshot.Destinations PrinterSettings printerSettings; if (!string.IsNullOrEmpty(_printerName)) { - using (var printHelper = new PrintHelper(CoreConfiguration, GreenshotLanguage,surface, captureDetails)) + using (var ownedPrintHelper = _printHelperFactory(surface, captureDetails)) { - printerSettings = printHelper.PrintTo(_printerName); + printerSettings = ownedPrintHelper.Value.PrintTo(_printerName); } } else if (!manuallyInitiated) { var settings = new PrinterSettings(); - using (var printHelper = new PrintHelper(CoreConfiguration, GreenshotLanguage, surface, captureDetails)) - { - printerSettings = printHelper.PrintTo(settings.PrinterName); - } + + using (var ownedPrintHelper = _printHelperFactory(surface, captureDetails)) + { + printerSettings = ownedPrintHelper.Value.PrintTo(settings.PrinterName); + } } else { - using (var printHelper = new PrintHelper(CoreConfiguration, GreenshotLanguage, surface, captureDetails)) - { - printerSettings = printHelper.PrintWithDialog(); - } + using (var ownedPrintHelper = _printHelperFactory(surface, captureDetails)) + { + printerSettings = ownedPrintHelper.Value.PrintWithDialog(); + } } if (printerSettings != null) { diff --git a/src/Greenshot/Forms/LanguageDialog.cs b/src/Greenshot/Forms/LanguageDialog.cs index 68ecafbde..285cd8b23 100644 --- a/src/Greenshot/Forms/LanguageDialog.cs +++ b/src/Greenshot/Forms/LanguageDialog.cs @@ -41,7 +41,6 @@ namespace Greenshot.Forms public partial class LanguageDialog : Form { private static readonly LogSource Log = new LogSource(); - private static LanguageDialog _uniqueInstance; private bool _properOkPressed; private LanguageDialog() @@ -108,10 +107,5 @@ namespace Greenshot.Forms Language.CurrentLanguage = SelectedLanguage; Close(); } - - public static LanguageDialog GetInstance() - { - return _uniqueInstance ?? (_uniqueInstance = new LanguageDialog()); - } } } \ No newline at end of file diff --git a/src/Greenshot/Forms/MainForm.cs b/src/Greenshot/Forms/MainForm.cs index 08111c417..86a76992c 100644 --- a/src/Greenshot/Forms/MainForm.cs +++ b/src/Greenshot/Forms/MainForm.cs @@ -74,11 +74,11 @@ namespace Greenshot.Forms private static readonly LogSource Log = new LogSource(); private readonly ICoreConfiguration _coreConfiguration; private readonly IWindowManager _windowManager; - private readonly ConfigViewModel _configViewModel; + private readonly Func> _configViewModelFactory; + private readonly Func> _aboutFormFactory; // Timer for the double click test private readonly Timer _doubleClickTimer = new Timer(); - private readonly Func> _aboutFormFactory; private readonly DestinationHolder _destinationHolder; // Thumbnail preview @@ -89,13 +89,13 @@ namespace Greenshot.Forms public MainForm(ICoreConfiguration coreConfiguration, IWindowManager windowManager, IGreenshotLanguage greenshotLanguage, - ConfigViewModel configViewModel, + Func> configViewModelFactory, Func> aboutFormFactory, DestinationHolder destinationHolder) : base(greenshotLanguage) { _coreConfiguration = coreConfiguration; _windowManager = windowManager; - _configViewModel = configViewModel; + _configViewModelFactory = configViewModelFactory; _aboutFormFactory = aboutFormFactory; _destinationHolder = destinationHolder; Instance = this; @@ -365,17 +365,7 @@ namespace Greenshot.Forms { PsApi.EmptyWorkingSet(); } - if (UpdateHelper.IsUpdateCheckNeeded()) - { - Log.Debug().WriteLine("BackgroundWorkerTimerTick checking for update"); - // Start update check in the background - var backgroundTask = new Thread(UpdateHelper.CheckAndAskForUpdate) - { - Name = "Update check", - IsBackground = true - }; - backgroundTask.Start(); - } + } @@ -841,10 +831,9 @@ namespace Greenshot.Forms /// public void ShowSetting() { - // The new MVVM Configuration - if (!_configViewModel.IsActive) + using(var ownedConfigViewModel = _configViewModelFactory()) { - _windowManager.ShowDialog(_configViewModel); + _windowManager.ShowDialog(ownedConfigViewModel.Value); } InitializeQuickSettingsMenu(); } diff --git a/src/Greenshot/Greenshot.csproj b/src/Greenshot/Greenshot.csproj index 47a1ebaf8..610568f8c 100644 --- a/src/Greenshot/Greenshot.csproj +++ b/src/Greenshot/Greenshot.csproj @@ -289,7 +289,7 @@ - + diff --git a/src/Greenshot/GreenshotAutofacModule.cs b/src/Greenshot/GreenshotAutofacModule.cs index 53dfe62ac..05933161d 100644 --- a/src/Greenshot/GreenshotAutofacModule.cs +++ b/src/Greenshot/GreenshotAutofacModule.cs @@ -29,6 +29,7 @@ using Dapplo.CaliburnMicro.Security; using Greenshot.Addons.Components; using Greenshot.Components; using Greenshot.Forms; +using Greenshot.Helpers; using Greenshot.Ui.Configuration.ViewModels; using Greenshot.Ui.Misc.ViewModels; @@ -41,8 +42,8 @@ namespace Greenshot { builder .RegisterType() - .AsSelf() - .SingleInstance(); + .AsSelf(); + builder .RegisterType() .As() @@ -53,6 +54,18 @@ namespace Greenshot .AsSelf() .SingleInstance(); + builder + .RegisterType() + .AsSelf(); + + builder + .RegisterType() + .AsSelf(); + + builder + .RegisterType() + .AsSelf(); + builder .RegisterType() .As() @@ -83,7 +96,7 @@ namespace Greenshot .AssignableTo() .As() .SingleInstance(); - + builder .RegisterType() .AsSelf(); diff --git a/src/Greenshot/Helpers/PrintHelper.cs b/src/Greenshot/Helpers/PrintHelper.cs index b39d5a1b6..2d3b5c411 100644 --- a/src/Greenshot/Helpers/PrintHelper.cs +++ b/src/Greenshot/Helpers/PrintHelper.cs @@ -27,6 +27,7 @@ using System; using System.Drawing; using System.Drawing.Printing; using System.Windows.Forms; +using Autofac.Features.OwnedInstances; using Greenshot.Forms; using Dapplo.Log; using Dapplo.Windows.Common.Extensions; @@ -52,6 +53,7 @@ namespace Greenshot.Helpers private static readonly LogSource Log = new LogSource(); private readonly ICoreConfiguration _coreConfig; private readonly IGreenshotLanguage _greenshotLanguage; + private readonly Func> _printOptionsDialogFactory; private readonly ICaptureDetails _captureDetails; private PrintDialog _printDialog = new PrintDialog(); private PrintDocument _printDocument = new PrintDocument(); @@ -61,11 +63,13 @@ namespace Greenshot.Helpers public PrintHelper( ICoreConfiguration coreConfiguration, IGreenshotLanguage greenshotLanguage, + Func> printOptionsDialogFactory, ISurface surface, ICaptureDetails captureDetails) { _coreConfig = coreConfiguration; _greenshotLanguage = greenshotLanguage; + _printOptionsDialogFactory = printOptionsDialogFactory; _surface = surface; _captureDetails = captureDetails; @@ -83,16 +87,6 @@ namespace Greenshot.Helpers public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); - } - - /** - * Destructor - */ - - ~PrintHelper() - { - Dispose(false); } /** @@ -149,29 +143,30 @@ namespace Greenshot.Helpers /// printer settings if actually printed, or null if print was cancelled or has failed public PrinterSettings PrintWithDialog() { - PrinterSettings returnPrinterSettings = null; - if (_printDialog.ShowDialog() == DialogResult.OK) - { - var printOptionsResult = ShowPrintOptionsDialog(); - try - { - if (printOptionsResult == null || printOptionsResult == DialogResult.OK) - { - if (!IsColorPrint()) - { - _printDocument.DefaultPageSettings.Color = false; - } - _printDocument.Print(); - returnPrinterSettings = _printDialog.PrinterSettings; - } - } - catch (Exception e) - { - Log.Error().WriteLine(e, "An error ocurred while trying to print"); - MessageBox.Show(Language.GetString(LangKey.print_error), Language.GetString(LangKey.error)); - } - } - return returnPrinterSettings; + if (_printDialog.ShowDialog() != DialogResult.OK) + { + return null; + } + PrinterSettings returnPrinterSettings = null; + var printOptionsResult = ShowPrintOptionsDialog(); + try + { + if (printOptionsResult == null || printOptionsResult == DialogResult.OK) + { + if (!IsColorPrint()) + { + _printDocument.DefaultPageSettings.Color = false; + } + _printDocument.Print(); + returnPrinterSettings = _printDialog.PrinterSettings; + } + } + catch (Exception e) + { + Log.Error().WriteLine(e, "An error ocurred while trying to print"); + MessageBox.Show(Language.GetString(LangKey.print_error), Language.GetString(LangKey.error)); + } + return returnPrinterSettings; } private bool IsColorPrint() @@ -190,9 +185,9 @@ namespace Greenshot.Helpers return null; } - using (var printOptionsDialog = new PrintOptionsDialog(_coreConfig, _greenshotLanguage)) + using (var ownedPrintOptionsDialog = _printOptionsDialogFactory()) { - return printOptionsDialog.ShowDialog(); + return ownedPrintOptionsDialog.Value.ShowDialog(); } } diff --git a/src/Greenshot/Helpers/UpdateHelper.cs b/src/Greenshot/Helpers/UpdateHelper.cs deleted file mode 100644 index 0f11f79b4..000000000 --- a/src/Greenshot/Helpers/UpdateHelper.cs +++ /dev/null @@ -1,223 +0,0 @@ -#region Greenshot GNU General Public License - -// Greenshot - a free and open source screenshot tool -// Copyright (C) 2007-2018 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 . - -#endregion - -#region Usings - -using System; -using System.Diagnostics; -using System.Reflection; -using System.Windows.Forms; -using Dapplo.Ini; -using Dapplo.Log; -using Greenshot.Addons.Core; -using Greenshot.Addons.Core.Enums; -using Greenshot.Configuration; -using Greenshot.Forms; - -#endregion - -namespace Greenshot.Helpers -{ - /// - /// Description of RssFeedHelper. - /// - public static class UpdateHelper - { - private const string StableDownloadLink = "https://getgreenshot.org/downloads/"; - private const string VersionHistoryLink = "https://getgreenshot.org/version-history/"; - private static readonly LogSource Log = new LogSource(); - private static readonly ICoreConfiguration CoreConfig = IniConfig.Current.Get(); - 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; - } - var checkTime = CoreConfig.LastUpdateCheck; - checkTime = checkTime.AddDays(CoreConfig.UpdateCheckInterval); - if (DateTime.Now.CompareTo(checkTime) < 0) - { - Log.Debug().WriteLine("No need to check RSS feed for updates, feed check will be after {0}", checkTime); - return false; - } - Log.Debug().WriteLine("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.Debug().WriteLine("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) - { - var currentVersion = Assembly.GetExecutingAssembly().GetName().Version; - // Test like this: - // currentVersion = new Version("0.8.1.1198"); - - try - { - _latestGreenshot = null; - ProcessRssInfo(currentVersion); - if (_latestGreenshot != null) - { - MainForm.Instance.NotifyIcon.BalloonTipClicked += HandleBalloonTipClick; - MainForm.Instance.NotifyIcon.BalloonTipClosed += CleanupBalloonTipClick; - MainForm.Instance.NotifyIcon.ShowBalloonTip(10000, "Greenshot", Language.GetFormattedString(LangKey.update_found, "'" + _latestGreenshot.File + "'"), - ToolTipIcon.Info); - } - CoreConfig.LastUpdateCheck = DateTime.Now; - } - catch (Exception e) - { - Log.Error().WriteLine(e, "An error occured while checking for updates, the error will be ignored: "); - } - } - } - - private static void CleanupBalloonTipClick(object sender, EventArgs e) - { - MainForm.Instance.NotifyIcon.BalloonTipClicked -= HandleBalloonTipClick; - MainForm.Instance.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 - var rssFiles = RssHelper.ReadRss(); - - if (rssFiles == null) - { - return; - } - - // Retrieve the current and latest greenshot - foreach (var 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.Debug().WriteLine("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.IsAlpha) - { - // Skip if we shouldn't check unstables - if (CoreConfigurationExtensions.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 (CoreConfigurationExtensions.BuildState == BuildStates.RELEASE && !CoreConfig.CheckForUnstable) - { - continue; - } - } - - // Compare versions - var versionCompare = rssFile.Version.CompareTo(currentVersion); - if (versionCompare > 0) - { - Log.Debug().WriteLine("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.IsAlpha) - { - _downloadLink = VersionHistoryLink; - } - else - { - _downloadLink = StableDownloadLink; - } - } - } - else if (versionCompare < 0) - { - Log.Debug().WriteLine("Skipping older greenshot with version {0}", rssFile.Version); - } - else if (versionCompare == 0) - { - Log.Debug().WriteLine("Found current version as exe {0} with version {1} published at {2} : {3}", rssFile.File, rssFile.Version, rssFile.Pubdate.ToLocalTime(), - rssFile.Link); - } - } - } - } - } -} \ No newline at end of file