From d383f0ef1cbfc51a37a930caa3edcc3eb70be97f Mon Sep 17 00:00:00 2001 From: Holger Boerchers Date: Wed, 21 Mar 2018 20:07:40 +0100 Subject: [PATCH] Initial commit --- .gitignore | 116 ++++ AppStub/App.config | 6 + AppStub/AppStub.csproj | 53 ++ AppStub/Program.cs | 78 +++ AppStub/Properties/AssemblyInfo.cs | 35 ++ AppStub/SemanticVersion.cs | 268 +++++++++ AppStubEx/App.config | 6 + AppStubEx/AppEntry.cs | 19 + AppStubEx/AppStubEx.csproj | 70 +++ AppStubEx/IniFile.cs | 50 ++ AppStubEx/InstallerUx.cs | 73 +++ AppStubEx/InstallerUx.designer.cs | 149 +++++ AppStubEx/InstallerUx.resx | 126 +++++ AppStubEx/Program.cs | 102 ++++ AppStubEx/Properties/AssemblyInfo.cs | 42 ++ AppStubEx/SemanticVersion.cs | 268 +++++++++ AppStubEx/Taskbar.cs | 117 ++++ Creator/App.config | 26 + Creator/ApplicationArguments.cs | 62 +++ Creator/AssemblyFileInfo.cs | 93 ++++ Creator/Helper/Log.cs | 51 ++ Creator/Helper/SimpleCommandLineParser.cs | 166 ++++++ Creator/Helper/Utils.cs | 102 ++++ Creator/IconExtractor/IconExtractor.cs | 283 ++++++++++ Creator/IconExtractor/IconUtil.cs | 202 +++++++ Creator/IconExtractor/NativeMethods.cs | 81 +++ Creator/KattekerCreator.csproj | 102 ++++ Creator/PhysicalFile.cs | 14 + Creator/Program.cs | 213 +++++++ Creator/Properties/AssemblyInfo.cs | 36 ++ Creator/SemanticVersion.cs | 270 +++++++++ Creator/Setup.nsi | 103 ++++ Creator/SetupTmpl.cs | 522 ++++++++++++++++++ Creator/SetupTmpl.tt | 121 ++++ Creator/SetupTmplCode.cs | 84 +++ Creator/contrib/LoadingBar_Icon.exe | Bin 0 -> 6144 bytes Creator/contrib/uninstall.ico | Bin 0 -> 18502 bytes Creator/packages.config | 4 + Creator/reference.nuspec | 19 + Example/App.config | 6 + Example/App.xaml | 9 + Example/App.xaml.cs | 11 + Example/Example.csproj | 120 ++++ Example/MainWindow.xaml | 17 + Example/MainWindow.xaml.cs | 32 ++ Example/Properties/AssemblyInfo.cs | 53 ++ Example/Properties/Resources.Designer.cs | 63 +++ Example/Properties/Resources.resx | 117 ++++ Example/Properties/Settings.Designer.cs | 26 + Example/Properties/Settings.settings | 7 + Example/changelog.md | 5 + Example/nsis3-install.ico | Bin 0 -> 11697 bytes Katteker.Gui/ChangelogHelper.cs | 61 ++ Katteker.Gui/Katteker.Gui.csproj | 95 ++++ Katteker.Gui/Katteker.Gui.nuspec | 18 + Katteker.Gui/Properties/AssemblyInfo.cs | 36 ++ Katteker.Gui/Properties/Resources.Designer.cs | 144 +++++ Katteker.Gui/Properties/Resources.resx | 147 +++++ Katteker.Gui/UpdateWindow.Designer.cs | 314 +++++++++++ Katteker.Gui/UpdateWindow.cs | 146 +++++ Katteker.Gui/UpdateWindow.resx | 197 +++++++ Katteker.Gui/Wrapper.cs | 60 ++ Katteker.Gui/app.config | 23 + Katteker.Gui/packages.config | 4 + Katteker.sln | 49 ++ Katteker/Constants.cs | 30 + Katteker/Katteker.csproj | 53 ++ Katteker/KattekerConfig.cs | 38 ++ Katteker/Properties/AssemblyInfo.cs | 35 ++ Katteker/ReleaseEntry.cs | 92 +++ Katteker/Releases.cs | 79 +++ Katteker/SemanticVersion.cs | 511 +++++++++++++++++ Katteker/UpdateInfo.cs | 29 + Katteker/UpdateManager.cs | 181 ++++++ Katteker/Utility.cs | 41 ++ Katteker/VersionExtension.cs | 34 ++ README.md | 35 ++ 77 files changed, 7050 insertions(+) create mode 100644 .gitignore create mode 100644 AppStub/App.config create mode 100644 AppStub/AppStub.csproj create mode 100644 AppStub/Program.cs create mode 100644 AppStub/Properties/AssemblyInfo.cs create mode 100644 AppStub/SemanticVersion.cs create mode 100644 AppStubEx/App.config create mode 100644 AppStubEx/AppEntry.cs create mode 100644 AppStubEx/AppStubEx.csproj create mode 100644 AppStubEx/IniFile.cs create mode 100644 AppStubEx/InstallerUx.cs create mode 100644 AppStubEx/InstallerUx.designer.cs create mode 100644 AppStubEx/InstallerUx.resx create mode 100644 AppStubEx/Program.cs create mode 100644 AppStubEx/Properties/AssemblyInfo.cs create mode 100644 AppStubEx/SemanticVersion.cs create mode 100644 AppStubEx/Taskbar.cs create mode 100644 Creator/App.config create mode 100644 Creator/ApplicationArguments.cs create mode 100644 Creator/AssemblyFileInfo.cs create mode 100644 Creator/Helper/Log.cs create mode 100644 Creator/Helper/SimpleCommandLineParser.cs create mode 100644 Creator/Helper/Utils.cs create mode 100644 Creator/IconExtractor/IconExtractor.cs create mode 100644 Creator/IconExtractor/IconUtil.cs create mode 100644 Creator/IconExtractor/NativeMethods.cs create mode 100644 Creator/KattekerCreator.csproj create mode 100644 Creator/PhysicalFile.cs create mode 100644 Creator/Program.cs create mode 100644 Creator/Properties/AssemblyInfo.cs create mode 100644 Creator/SemanticVersion.cs create mode 100644 Creator/Setup.nsi create mode 100644 Creator/SetupTmpl.cs create mode 100644 Creator/SetupTmpl.tt create mode 100644 Creator/SetupTmplCode.cs create mode 100644 Creator/contrib/LoadingBar_Icon.exe create mode 100644 Creator/contrib/uninstall.ico create mode 100644 Creator/packages.config create mode 100644 Creator/reference.nuspec create mode 100644 Example/App.config create mode 100644 Example/App.xaml create mode 100644 Example/App.xaml.cs create mode 100644 Example/Example.csproj create mode 100644 Example/MainWindow.xaml create mode 100644 Example/MainWindow.xaml.cs create mode 100644 Example/Properties/AssemblyInfo.cs create mode 100644 Example/Properties/Resources.Designer.cs create mode 100644 Example/Properties/Resources.resx create mode 100644 Example/Properties/Settings.Designer.cs create mode 100644 Example/Properties/Settings.settings create mode 100644 Example/changelog.md create mode 100644 Example/nsis3-install.ico create mode 100644 Katteker.Gui/ChangelogHelper.cs create mode 100644 Katteker.Gui/Katteker.Gui.csproj create mode 100644 Katteker.Gui/Katteker.Gui.nuspec create mode 100644 Katteker.Gui/Properties/AssemblyInfo.cs create mode 100644 Katteker.Gui/Properties/Resources.Designer.cs create mode 100644 Katteker.Gui/Properties/Resources.resx create mode 100644 Katteker.Gui/UpdateWindow.Designer.cs create mode 100644 Katteker.Gui/UpdateWindow.cs create mode 100644 Katteker.Gui/UpdateWindow.resx create mode 100644 Katteker.Gui/Wrapper.cs create mode 100644 Katteker.Gui/app.config create mode 100644 Katteker.Gui/packages.config create mode 100644 Katteker.sln create mode 100644 Katteker/Constants.cs create mode 100644 Katteker/Katteker.csproj create mode 100644 Katteker/KattekerConfig.cs create mode 100644 Katteker/Properties/AssemblyInfo.cs create mode 100644 Katteker/ReleaseEntry.cs create mode 100644 Katteker/Releases.cs create mode 100644 Katteker/SemanticVersion.cs create mode 100644 Katteker/UpdateInfo.cs create mode 100644 Katteker/UpdateManager.cs create mode 100644 Katteker/Utility.cs create mode 100644 Katteker/VersionExtension.cs create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0fc261 --- /dev/null +++ b/.gitignore @@ -0,0 +1,116 @@ +# ---> C Sharp +# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) +[Bb]in/ +[Oo]bj/ + +# mstest test results +TestResults + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.log +*.vspscc +*.vssscc +.builds + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper* + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +packages + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +[Bb]in +[Oo]bj +sql +TestResults +[Tt]est[Rr]esult* +*.Cache +ClientBin +[Ss]tyle[Cc]op.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + +/App.exe +/AppStub.exe +/AppStub.exe.config +.vs +/Katteker/Creator/SetupTmpl.cs +/Creator/*.DotSettings diff --git a/AppStub/App.config b/AppStub/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/AppStub/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AppStub/AppStub.csproj b/AppStub/AppStub.csproj new file mode 100644 index 0000000..da49065 --- /dev/null +++ b/AppStub/AppStub.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {E746AE0F-BEEA-4C18-9ED8-2E708ED00A5B} + WinExe + AppStub + AppStub + v4.5 + 512 + + + AnyCPU + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + SemanticVersion.cs + + + + + + + + + \ No newline at end of file diff --git a/AppStub/Program.cs b/AppStub/Program.cs new file mode 100644 index 0000000..0954e0c --- /dev/null +++ b/AppStub/Program.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Windows.Forms; +using Katteker; + +namespace AppStub +{ + internal static class Program + { + [STAThread] + private static void Main(string[] args) + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + try + { + var commandline = string.Join(" ", args); + var location = new FileInfo(Assembly.GetExecutingAssembly().Location); + var directory = location.Directory; + if (directory == null) return; + var files = directory.EnumerateFiles(location.Name, SearchOption.AllDirectories) + .Where(x => x.Directory?.Name.StartsWith("app-") == true); + + var entries = new SortedList(); + + foreach (var file in files) + { + var version = SemanticVersion.TryParse(file.Directory?.Name.Replace("app-", ""), out var value) + ? value + : new SemanticVersion(0); + entries.Add(version, file); + } + + var latestVersion = entries.LastOrDefault().Value; + if (latestVersion == null) throw new FileNotFoundException(); + DeleteOldVersionDirectories(entries.Where(x => x.Value != latestVersion)); + using (var process = new Process()) + { + process.StartInfo = + new ProcessStartInfo(latestVersion.FullName) + { + Arguments = commandline, + WorkingDirectory = latestVersion.DirectoryName ?? Assembly.GetExecutingAssembly().Location, + UseShellExecute = false + }; + process.Start(); + process.WaitForExit(); + } + } + catch (Exception e) + { + MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static void DeleteOldVersionDirectories(IEnumerable> dirEntries) + { +#if !DEBUG + foreach (var directoryInfo in dirEntries) + { + try + { + directoryInfo.Value.Directory?.Delete(true); + } + catch + { + // silently ignore + } + } +#endif + } + } +} \ No newline at end of file diff --git a/AppStub/Properties/AssemblyInfo.cs b/AppStub/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..30a0dee --- /dev/null +++ b/AppStub/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("AppStub")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AppStub")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("e746ae0f-beea-4c18-9ed8-2e708ed00a5b")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AppStub/SemanticVersion.cs b/AppStub/SemanticVersion.cs new file mode 100644 index 0000000..6c27aae --- /dev/null +++ b/AppStub/SemanticVersion.cs @@ -0,0 +1,268 @@ +using System; +using System.Text.RegularExpressions; + +namespace AppStub +{ + /// + /// A hybrid implementation of SemVer that supports semantic versioning as described at http://semver.org while not + /// strictly enforcing it to + /// allow older 4-digit versioning schemes to continue working. + /// + [Serializable] + //[TypeConverter(typeof(SemanticVersionTypeConverter))] + public sealed class SemanticVersion : IComparable, IComparable, IEquatable + { + private const RegexOptions Flags = + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + + private static readonly Regex SemanticVersionRegex = + new Regex(@"^(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?$", Flags); + + private static readonly Regex StrictSemanticVersionRegex = + new Regex(@"^(?\d+(\.\d+){2})(?-[a-z][0-9a-z-]*)?$", Flags); + + private readonly string _originalString; + + public SemanticVersion(string version) + : this(Parse(version)) + { + // The constructor normalizes the version string so that it we do not need to normalize it every time we need to operate on it. + // The original string represents the original form in which the version is represented to be used when printing. + _originalString = version; + } + + public SemanticVersion(int major, int minor, int build, int revision) + : this(new Version(major, minor, build, revision)) + { + } + + public SemanticVersion(int major, int minor, int build, string specialVersion) + : this(new Version(major, minor, build), specialVersion) + { + } + + public SemanticVersion(Version version) + : this(version, string.Empty) + { + } + + public SemanticVersion(Version version, string specialVersion) + : this(version, specialVersion, null) + { + } + + private SemanticVersion(Version version, string specialVersion, string originalString) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + Version = NormalizeVersionValue(version); + SpecialVersion = specialVersion ?? string.Empty; + _originalString = string.IsNullOrEmpty(originalString) + ? version + (!string.IsNullOrEmpty(specialVersion) ? '-' + specialVersion : null) + : originalString; + } + + internal SemanticVersion(SemanticVersion semVer) + { + _originalString = semVer.ToString(); + Version = semVer.Version; + SpecialVersion = semVer.SpecialVersion; + } + + /// + /// Gets the normalized version portion. + /// + public Version Version { get; } + + /// + /// Gets the optional special version. + /// + public string SpecialVersion { get; } + + public int CompareTo(object obj) + { + if (ReferenceEquals(obj, null)) + return 1; + var other = obj as SemanticVersion; + if (other == null) + throw new ArgumentException("Type Must Be A Semantic Version", nameof(obj)); + return CompareTo(other); + } + + public int CompareTo(SemanticVersion other) + { + if (ReferenceEquals(other, null)) + return 1; + + var result = Version.CompareTo(other.Version); + + if (result != 0) + return result; + + var empty = string.IsNullOrEmpty(SpecialVersion); + var otherEmpty = string.IsNullOrEmpty(other.SpecialVersion); + if (empty && otherEmpty) + return 0; + if (empty) + return 1; + if (otherEmpty) + return -1; + return StringComparer.OrdinalIgnoreCase.Compare(SpecialVersion, other.SpecialVersion); + } + + public bool Equals(SemanticVersion other) + { + return !ReferenceEquals(null, other) && + Version.Equals(other.Version) && + SpecialVersion.Equals(other.SpecialVersion, StringComparison.OrdinalIgnoreCase); + } + + public string[] GetOriginalVersionComponents() + { + if (!string.IsNullOrEmpty(_originalString)) + { + // search the start of the SpecialVersion part, if any + var dashIndex = _originalString.IndexOf('-'); + var original = dashIndex != -1 ? _originalString.Substring(0, dashIndex) : _originalString; + + return SplitAndPadVersionString(original); + } + return SplitAndPadVersionString(Version.ToString()); + } + + private static string[] SplitAndPadVersionString(string version) + { + var a = version.Split('.'); + if (a.Length == 4) + { + return a; + } + // if 'a' has less than 4 elements, we pad the '0' at the end + // to make it 4. + var b = new string[4] {"0", "0", "0", "0"}; + Array.Copy(a, 0, b, 0, a.Length); + return b; + } + + /// + /// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an + /// optional special version. + /// + public static SemanticVersion Parse(string version) + { + if (string.IsNullOrEmpty(version)) + throw new ArgumentException(nameof(version)); + + if (!TryParse(version, out var semVer)) + throw new ArgumentException("Invalid Version String", nameof(version)); + return semVer; + } + + /// + /// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an + /// optional special version. + /// + public static bool TryParse(string version, out SemanticVersion value) + { + return TryParseInternal(version, SemanticVersionRegex, out value); + } + + /// + /// Parses a version string using strict semantic versioning rules that allows exactly 3 components and an optional + /// special version. + /// + public static bool TryParseStrict(string version, out SemanticVersion value) + { + return TryParseInternal(version, StrictSemanticVersionRegex, out value); + } + + private static bool TryParseInternal(string version, Regex regex, out SemanticVersion semVer) + { + semVer = null; + if (string.IsNullOrEmpty(version)) + return false; + + var match = regex.Match(version.Trim()); + if (!match.Success || !Version.TryParse(match.Groups["Version"].Value, out var versionValue)) + return false; + + semVer = new SemanticVersion(NormalizeVersionValue(versionValue), + match.Groups["Release"].Value.TrimStart('-'), version.Replace(" ", "")); + return true; + } + + /// + /// Attempts to parse the version token as a SemanticVersion. + /// + /// An instance of SemanticVersion if it parses correctly, null otherwise. + public static SemanticVersion ParseOptionalVersion(string version) + { + TryParse(version, out var semVer); + return semVer; + } + + private static Version NormalizeVersionValue(Version version) + { + return new Version(version.Major, + version.Minor, + Math.Max(version.Build, 0), + Math.Max(version.Revision, 0)); + } + + public static bool operator ==(SemanticVersion version1, SemanticVersion version2) + { + if (ReferenceEquals(version1, null)) + return ReferenceEquals(version2, null); + return version1.Equals(version2); + } + + public static bool operator !=(SemanticVersion version1, SemanticVersion version2) + { + return !(version1 == version2); + } + + public static bool operator <(SemanticVersion version1, SemanticVersion version2) + { + if (version1 == null) + throw new ArgumentNullException(nameof(version1)); + return version1.CompareTo(version2) < 0; + } + + public static bool operator <=(SemanticVersion version1, SemanticVersion version2) + { + return version1 == version2 || version1 < version2; + } + + public static bool operator >(SemanticVersion version1, SemanticVersion version2) + { + if (version1 == null) + throw new ArgumentNullException(nameof(version1)); + return version2 < version1; + } + + public static bool operator >=(SemanticVersion version1, SemanticVersion version2) + { + return version1 == version2 || version1 > version2; + } + + public override string ToString() + { + return _originalString; + } + + public override bool Equals(object obj) + { + var semVer = obj as SemanticVersion; + return !ReferenceEquals(null, semVer) && Equals(semVer); + } + + public override int GetHashCode() + { + var hashCode = Version.GetHashCode(); + if (SpecialVersion != null) + hashCode = hashCode * 4567 + SpecialVersion.GetHashCode(); + + return hashCode; + } + } +} \ No newline at end of file diff --git a/AppStubEx/App.config b/AppStubEx/App.config new file mode 100644 index 0000000..e707b32 --- /dev/null +++ b/AppStubEx/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/AppStubEx/AppEntry.cs b/AppStubEx/AppEntry.cs new file mode 100644 index 0000000..cb51529 --- /dev/null +++ b/AppStubEx/AppEntry.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace Katteker.AppStub +{ + public class AppEntry : IComparable + { + public AppEntry(DirectoryInfo info) + { + DirInfo = info; + Version = SemanticVersion.Parse(info.Name.Replace("app-", "")); + } + + public SemanticVersion Version { get; } + public DirectoryInfo DirInfo { get; } + + public int CompareTo(AppEntry other) => Version.CompareTo(other.Version); + } +} \ No newline at end of file diff --git a/AppStubEx/AppStubEx.csproj b/AppStubEx/AppStubEx.csproj new file mode 100644 index 0000000..f947275 --- /dev/null +++ b/AppStubEx/AppStubEx.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {AF7579CC-C0B2-4E5A-B052-00D2991DC715} + WinExe + Katteker.AppStubEx + AppStubEx + v4.5 + 512 + + + + AnyCPU + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + + + + + + Form + + + InstallerUx.cs + + + + + + + + + + + + InstallerUx.cs + + + + \ No newline at end of file diff --git a/AppStubEx/IniFile.cs b/AppStubEx/IniFile.cs new file mode 100644 index 0000000..dc24985 --- /dev/null +++ b/AppStubEx/IniFile.cs @@ -0,0 +1,50 @@ +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace Katteker.AppStub +{ + public class IniFile // revision 11 + { + private readonly FileInfo _path; + private readonly string _exe = Assembly.GetExecutingAssembly().GetName().Name; + + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern long WritePrivateProfileString(string section, string key, string value, string filePath); + + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileString(string section, string key, string Default, StringBuilder retVal, int size, string filePath); + + public IniFile(string iniPath = null) + { + _path = new FileInfo(iniPath ?? _exe + ".ini"); + } + + public string Read(string key, string section = null) + { + var retVal = new StringBuilder(255); + GetPrivateProfileString(section ?? _exe, key, "", retVal, 255, _path.FullName); + return retVal.ToString(); + } + + public void Write(string key, string value, string section = null) + { + WritePrivateProfileString(section ?? _exe, key, value, _path.FullName); + } + + public void DeleteKey(string key, string section = null) + { + Write(key, null, section ?? _exe); + } + + public void DeleteSection(string section = null) + { + Write(null, null, section ?? _exe); + } + + public bool Exists => _path.Exists; + + public bool KeyExists(string key, string section = null) => Read(key, section).Length > 0; + } +} \ No newline at end of file diff --git a/AppStubEx/InstallerUx.cs b/AppStubEx/InstallerUx.cs new file mode 100644 index 0000000..6bf8986 --- /dev/null +++ b/AppStubEx/InstallerUx.cs @@ -0,0 +1,73 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace Katteker.AppStub +{ + public sealed partial class InstallerUx : Form + { + private int _autoCloseCountdown = 10; + + public InstallerUx(string appName) + { + Font = SystemFonts.MessageBoxFont; + InitializeComponent(); + SetLocationAndShow(); + this.appName.Text = appName; + } + + private void SetLocationAndShow() + { + var taskBar = new Taskbar(); + switch (taskBar.Position) + { + case TaskbarPosition.Unknown: + break; + case TaskbarPosition.Left: + Location = new Point(taskBar.Bounds.Left + taskBar.Bounds.Width, taskBar.Bounds.Bottom - Size.Height); + break; + case TaskbarPosition.Top: + Location = new Point(taskBar.Bounds.Right - Size.Width, taskBar.Bounds.Bottom); + break; + case TaskbarPosition.Right: + Location = new Point(taskBar.Bounds.Left - Size.Width, taskBar.Bounds.Bottom - Size.Height); + break; + case TaskbarPosition.Bottom: + Location = new Point(taskBar.Bounds.Right - Size.Width, taskBar.Bounds.Top - Size.Height); + break; + default: + break; + } + Show(); + } + + private void closeBtn_Click(object sender, EventArgs e) + { + Close(); + } + + private void installBtn_Click(object sender, EventArgs e) + { + _autoCloseCountdown = 0; + MessageBox.Show("Hallo Welt"); + } + + private void autoCloseTimer_Tick(object sender, EventArgs e) + { + switch (_autoCloseCountdown) + { + case 1: + Close(); + break; + case 0: + countDownLbl.Text = string.Empty; + autoCloseTimer.Enabled = false; + break; + default: + countDownLbl.Tag = --_autoCloseCountdown; + countDownLbl.Text = _autoCloseCountdown.ToString(); + break; + } + } + } +} \ No newline at end of file diff --git a/AppStubEx/InstallerUx.designer.cs b/AppStubEx/InstallerUx.designer.cs new file mode 100644 index 0000000..e47e9a4 --- /dev/null +++ b/AppStubEx/InstallerUx.designer.cs @@ -0,0 +1,149 @@ +namespace Katteker.AppStub +{ + sealed partial class InstallerUx + { + /// + /// Erforderliche Designervariable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Verwendete Ressourcen bereinigen. + /// + /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Windows Form-Designer generierter Code + + /// + /// Erforderliche Methode für die Designerunterstützung. + /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.closeBtn = new System.Windows.Forms.Button(); + this.installBtn = new System.Windows.Forms.Button(); + this.messageLbl = new System.Windows.Forms.Label(); + this.appName = new System.Windows.Forms.Label(); + this.countDownLbl = new System.Windows.Forms.Label(); + this.autoCloseTimer = new System.Windows.Forms.Timer(this.components); + this.SuspendLayout(); + // + // closeBtn + // + this.closeBtn.AutoSize = true; + this.closeBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.closeBtn.FlatAppearance.BorderSize = 0; + this.closeBtn.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); + this.closeBtn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(65))))); + this.closeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.closeBtn.Font = new System.Drawing.Font("Marlett", 10F); + this.closeBtn.Location = new System.Drawing.Point(249, 0); + this.closeBtn.Margin = new System.Windows.Forms.Padding(0); + this.closeBtn.Name = "closeBtn"; + this.closeBtn.Size = new System.Drawing.Size(31, 26); + this.closeBtn.TabIndex = 1; + this.closeBtn.TabStop = false; + this.closeBtn.Text = "r"; + this.closeBtn.UseVisualStyleBackColor = true; + this.closeBtn.Click += new System.EventHandler(this.closeBtn_Click); + // + // installBtn + // + this.installBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.installBtn.FlatAppearance.BorderSize = 0; + this.installBtn.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204))))); + this.installBtn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(65))))); + this.installBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.installBtn.Location = new System.Drawing.Point(192, 59); + this.installBtn.Margin = new System.Windows.Forms.Padding(0); + this.installBtn.Name = "installBtn"; + this.installBtn.Size = new System.Drawing.Size(77, 30); + this.installBtn.TabIndex = 2; + this.installBtn.TabStop = false; + this.installBtn.Text = "Install"; + this.installBtn.UseVisualStyleBackColor = true; + this.installBtn.Click += new System.EventHandler(this.installBtn_Click); + // + // messageLbl + // + this.messageLbl.AutoSize = true; + this.messageLbl.Location = new System.Drawing.Point(11, 68); + this.messageLbl.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.messageLbl.Name = "messageLbl"; + this.messageLbl.Size = new System.Drawing.Size(93, 13); + this.messageLbl.TabIndex = 3; + this.messageLbl.Text = "Upgrade available"; + // + // appName + // + this.appName.AutoSize = true; + this.appName.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.appName.Location = new System.Drawing.Point(13, 13); + this.appName.Name = "appName"; + this.appName.Size = new System.Drawing.Size(80, 20); + this.appName.TabIndex = 4; + this.appName.Text = "AppName"; + // + // countDownLbl + // + this.countDownLbl.AutoSize = true; + this.countDownLbl.BackColor = System.Drawing.Color.Transparent; + this.countDownLbl.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.countDownLbl.ForeColor = System.Drawing.SystemColors.ControlDark; + this.countDownLbl.Location = new System.Drawing.Point(229, 5); + this.countDownLbl.Name = "countDownLbl"; + this.countDownLbl.Size = new System.Drawing.Size(24, 17); + this.countDownLbl.TabIndex = 5; + this.countDownLbl.Tag = 10; + this.countDownLbl.Text = "10"; + this.countDownLbl.TextAlign = System.Drawing.ContentAlignment.TopRight; + // + // autoCloseTimer + // + this.autoCloseTimer.Enabled = true; + this.autoCloseTimer.Interval = 1000; + this.autoCloseTimer.Tick += new System.EventHandler(this.autoCloseTimer_Tick); + // + // InstallerUx + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48))))); + this.ClientSize = new System.Drawing.Size(278, 98); + this.ControlBox = false; + this.Controls.Add(this.countDownLbl); + this.Controls.Add(this.appName); + this.Controls.Add(this.messageLbl); + this.Controls.Add(this.installBtn); + this.Controls.Add(this.closeBtn); + this.ForeColor = System.Drawing.SystemColors.Control; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Location = new System.Drawing.Point(30, 0); + this.Name = "InstallerUx"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.TopMost = true; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.Button closeBtn; + private System.Windows.Forms.Button installBtn; + private System.Windows.Forms.Label messageLbl; + private System.Windows.Forms.Label appName; + private System.Windows.Forms.Label countDownLbl; + private System.Windows.Forms.Timer autoCloseTimer; + } +} + diff --git a/AppStubEx/InstallerUx.resx b/AppStubEx/InstallerUx.resx new file mode 100644 index 0000000..22570e0 --- /dev/null +++ b/AppStubEx/InstallerUx.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 167, 17 + + + 87 + + \ No newline at end of file diff --git a/AppStubEx/Program.cs b/AppStubEx/Program.cs new file mode 100644 index 0000000..f2c6178 --- /dev/null +++ b/AppStubEx/Program.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Katteker.AppStub +{ + internal class Program + { + private Program() + { + CurrentDir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory); + ConfigFile = new IniFile(Path.Combine(CurrentDir.FullName, "app.ini")); + } + + private DirectoryInfo CurrentDir { get; } + + private IniFile ConfigFile { get; } + + private Process MainProcess { get; set; } + + [STAThread] + private static void Main(string[] args) + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + var p = new Program(); + p.Run(args); + } + + private void Run(string[] args) + { + StartExecutable(args); + StartUpdateInfo(); + } + + private static void StartUpdateInfo() + { + Application.Run(new InstallerUx("TestProgramm")); + } + + private void StartExecutable(string[] args) + { + var dirEntries = CurrentDir.EnumerateDirectories().Where(x => x.Name.StartsWith("app-")) + .Select(x => new AppEntry(x)).ToList(); + + var latestVersion = dirEntries.OrderByDescending(x => x).FirstOrDefault(); + + if (latestVersion != null && ConfigFile.Exists) + { + try + { + var executable = ConfigFile.Read("Executable", "Main"); + + var a = args.Aggregate(string.Empty, (current, s) => current + s + " ").TrimEnd(' '); + MainProcess = new Process + { + StartInfo = + new ProcessStartInfo($"{latestVersion.DirInfo.FullName}\\{executable}") + { + Arguments = a, + WorkingDirectory = latestVersion.DirInfo.FullName, + UseShellExecute = false + } + }; + MainProcess.Start(); + Task.Run(() => + DeleteOldVersionDirectories(dirEntries.Where(x => + x.DirInfo.Name != latestVersion.DirInfo.Name))); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + else + { + MessageBox.Show("Error, File not found", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private static void DeleteOldVersionDirectories(IEnumerable dirEntries) + { +#if !DEBUG + foreach (var directoryInfo in dirEntries) + { + try + { + directoryInfo.DirInfo.Delete(true); + } + catch (Exception) + { + // silently ignore + } + } +#endif + } + } +} \ No newline at end of file diff --git a/AppStubEx/Properties/AssemblyInfo.cs b/AppStubEx/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4670df4 --- /dev/null +++ b/AppStubEx/Properties/AssemblyInfo.cs @@ -0,0 +1,42 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +//[assembly: AssemblyTitle("AppStub")] +//[assembly: AssemblyDescription("")] +//[assembly: AssemblyConfiguration("")] +//[assembly: AssemblyCompany("")] +//[assembly: AssemblyProduct("AppStub")] +//[assembly: AssemblyCopyright("Copyright © 2016")] +//[assembly: AssemblyTrademark("")] +//[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("af7579cc-c0b2-4e5a-b052-00d2991dc715")] +[assembly: AssemblyTitle("Katteker.AppStubEx")] +[assembly: AssemblyDescription("Katteker.AppStubEx")] +[assembly: AssemblyCompany("Holger Börchers")] +[assembly: AssemblyProduct("Katteker.AppStubEx")] +[assembly: AssemblyCopyright("Holger Börchers")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden. +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +//[assembly: AssemblyVersion("1.0.0.0")] +//[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AppStubEx/SemanticVersion.cs b/AppStubEx/SemanticVersion.cs new file mode 100644 index 0000000..ce4b946 --- /dev/null +++ b/AppStubEx/SemanticVersion.cs @@ -0,0 +1,268 @@ +using System; +using System.Text.RegularExpressions; + +namespace Katteker.AppStub +{ + /// + /// A hybrid implementation of SemVer that supports semantic versioning as described at http://semver.org while not + /// strictly enforcing it to + /// allow older 4-digit versioning schemes to continue working. + /// + [Serializable] + //[TypeConverter(typeof(SemanticVersionTypeConverter))] + public sealed class SemanticVersion : IComparable, IComparable, IEquatable + { + private const RegexOptions Flags = + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + + private static readonly Regex SemanticVersionRegex = + new Regex(@"^(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?$", Flags); + + private static readonly Regex StrictSemanticVersionRegex = + new Regex(@"^(?\d+(\.\d+){2})(?-[a-z][0-9a-z-]*)?$", Flags); + + private readonly string _originalString; + + public SemanticVersion(string version) + : this(Parse(version)) + { + // The constructor normalizes the version string so that it we do not need to normalize it every time we need to operate on it. + // The original string represents the original form in which the version is represented to be used when printing. + _originalString = version; + } + + public SemanticVersion(int major, int minor, int build, int revision) + : this(new Version(major, minor, build, revision)) + { + } + + public SemanticVersion(int major, int minor, int build, string specialVersion) + : this(new Version(major, minor, build), specialVersion) + { + } + + public SemanticVersion(Version version) + : this(version, string.Empty) + { + } + + public SemanticVersion(Version version, string specialVersion) + : this(version, specialVersion, null) + { + } + + private SemanticVersion(Version version, string specialVersion, string originalString) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + Version = NormalizeVersionValue(version); + SpecialVersion = specialVersion ?? string.Empty; + _originalString = string.IsNullOrEmpty(originalString) + ? version + (!string.IsNullOrEmpty(specialVersion) ? '-' + specialVersion : null) + : originalString; + } + + internal SemanticVersion(SemanticVersion semVer) + { + _originalString = semVer.ToString(); + Version = semVer.Version; + SpecialVersion = semVer.SpecialVersion; + } + + /// + /// Gets the normalized version portion. + /// + public Version Version { get; } + + /// + /// Gets the optional special version. + /// + public string SpecialVersion { get; } + + public int CompareTo(object obj) + { + if (ReferenceEquals(obj, null)) + return 1; + var other = obj as SemanticVersion; + if (other == null) + throw new ArgumentException("Type Must Be A Semantic Version", nameof(obj)); + return CompareTo(other); + } + + public int CompareTo(SemanticVersion other) + { + if (ReferenceEquals(other, null)) + return 1; + + var result = Version.CompareTo(other.Version); + + if (result != 0) + return result; + + var empty = string.IsNullOrEmpty(SpecialVersion); + var otherEmpty = string.IsNullOrEmpty(other.SpecialVersion); + if (empty && otherEmpty) + return 0; + if (empty) + return 1; + if (otherEmpty) + return -1; + return StringComparer.OrdinalIgnoreCase.Compare(SpecialVersion, other.SpecialVersion); + } + + public bool Equals(SemanticVersion other) + { + return !ReferenceEquals(null, other) && + Version.Equals(other.Version) && + SpecialVersion.Equals(other.SpecialVersion, StringComparison.OrdinalIgnoreCase); + } + + public string[] GetOriginalVersionComponents() + { + if (!string.IsNullOrEmpty(_originalString)) + { + // search the start of the SpecialVersion part, if any + var dashIndex = _originalString.IndexOf('-'); + var original = dashIndex != -1 ? _originalString.Substring(0, dashIndex) : _originalString; + + return SplitAndPadVersionString(original); + } + return SplitAndPadVersionString(Version.ToString()); + } + + private static string[] SplitAndPadVersionString(string version) + { + var a = version.Split('.'); + if (a.Length == 4) + { + return a; + } + // if 'a' has less than 4 elements, we pad the '0' at the end + // to make it 4. + var b = new string[4] {"0", "0", "0", "0"}; + Array.Copy(a, 0, b, 0, a.Length); + return b; + } + + /// + /// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an + /// optional special version. + /// + public static SemanticVersion Parse(string version) + { + if (string.IsNullOrEmpty(version)) + throw new ArgumentException(nameof(version)); + + if (!TryParse(version, out var semVer)) + throw new ArgumentException("Invalid Version String", nameof(version)); + return semVer; + } + + /// + /// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an + /// optional special version. + /// + public static bool TryParse(string version, out SemanticVersion value) + { + return TryParseInternal(version, SemanticVersionRegex, out value); + } + + /// + /// Parses a version string using strict semantic versioning rules that allows exactly 3 components and an optional + /// special version. + /// + public static bool TryParseStrict(string version, out SemanticVersion value) + { + return TryParseInternal(version, StrictSemanticVersionRegex, out value); + } + + private static bool TryParseInternal(string version, Regex regex, out SemanticVersion semVer) + { + semVer = null; + if (string.IsNullOrEmpty(version)) + return false; + + var match = regex.Match(version.Trim()); + if (!match.Success || !Version.TryParse(match.Groups["Version"].Value, out var versionValue)) + return false; + + semVer = new SemanticVersion(NormalizeVersionValue(versionValue), + match.Groups["Release"].Value.TrimStart('-'), version.Replace(" ", "")); + return true; + } + + /// + /// Attempts to parse the version token as a SemanticVersion. + /// + /// An instance of SemanticVersion if it parses correctly, null otherwise. + public static SemanticVersion ParseOptionalVersion(string version) + { + TryParse(version, out var semVer); + return semVer; + } + + private static Version NormalizeVersionValue(Version version) + { + return new Version(version.Major, + version.Minor, + Math.Max(version.Build, 0), + Math.Max(version.Revision, 0)); + } + + public static bool operator ==(SemanticVersion version1, SemanticVersion version2) + { + if (ReferenceEquals(version1, null)) + return ReferenceEquals(version2, null); + return version1.Equals(version2); + } + + public static bool operator !=(SemanticVersion version1, SemanticVersion version2) + { + return !(version1 == version2); + } + + public static bool operator <(SemanticVersion version1, SemanticVersion version2) + { + if (version1 == null) + throw new ArgumentNullException(nameof(version1)); + return version1.CompareTo(version2) < 0; + } + + public static bool operator <=(SemanticVersion version1, SemanticVersion version2) + { + return version1 == version2 || version1 < version2; + } + + public static bool operator >(SemanticVersion version1, SemanticVersion version2) + { + if (version1 == null) + throw new ArgumentNullException(nameof(version1)); + return version2 < version1; + } + + public static bool operator >=(SemanticVersion version1, SemanticVersion version2) + { + return version1 == version2 || version1 > version2; + } + + public override string ToString() + { + return _originalString; + } + + public override bool Equals(object obj) + { + var semVer = obj as SemanticVersion; + return !ReferenceEquals(null, semVer) && Equals(semVer); + } + + public override int GetHashCode() + { + var hashCode = Version.GetHashCode(); + if (SpecialVersion != null) + hashCode = hashCode * 4567 + SpecialVersion.GetHashCode(); + + return hashCode; + } + } +} \ No newline at end of file diff --git a/AppStubEx/Taskbar.cs b/AppStubEx/Taskbar.cs new file mode 100644 index 0000000..8696f9c --- /dev/null +++ b/AppStubEx/Taskbar.cs @@ -0,0 +1,117 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace Katteker.AppStub +{ + public enum TaskbarPosition + { + Unknown = -1, + Left, + Top, + Right, + Bottom + } + + public sealed class Taskbar + { + private const string ClassName = "Shell_TrayWnd"; + + public Taskbar() + { + var taskbarHandle = User32.FindWindow(ClassName, null); + + var data = new APPBARDATA + { + cbSize = (uint) Marshal.SizeOf(typeof(APPBARDATA)), + hWnd = taskbarHandle + }; + var result = Shell32.SHAppBarMessage(ABM.GetTaskbarPos, ref data); + if (result == IntPtr.Zero) + throw new InvalidOperationException(); + + Position = (TaskbarPosition) data.uEdge; + Bounds = Rectangle.FromLTRB(data.rc.left, data.rc.top, data.rc.right, data.rc.bottom); + + data.cbSize = (uint) Marshal.SizeOf(typeof(APPBARDATA)); + result = Shell32.SHAppBarMessage(ABM.GetState, ref data); + var state = result.ToInt32(); + AlwaysOnTop = (state & ABS.AlwaysOnTop) == ABS.AlwaysOnTop; + AutoHide = (state & ABS.Autohide) == ABS.Autohide; + } + + public Rectangle Bounds { get; } + + public TaskbarPosition Position { get; private set; } + + public Point Location => Bounds.Location; + + public Size Size => Bounds.Size; + + //Always returns false under Windows 7 + public bool AlwaysOnTop { get; private set; } + + public bool AutoHide { get; private set; } + } + + public enum ABM : uint + { + New = 0x00000000, + Remove = 0x00000001, + QueryPos = 0x00000002, + SetPos = 0x00000003, + GetState = 0x00000004, + GetTaskbarPos = 0x00000005, + Activate = 0x00000006, + GetAutoHideBar = 0x00000007, + SetAutoHideBar = 0x00000008, + WindowPosChanged = 0x00000009, + SetState = 0x0000000A + } + + public enum ABE : uint + { + Left = 0, + Top = 1, + Right = 2, + Bottom = 3 + } + + public static class ABS + { + public const int Autohide = 0x0000001; + public const int AlwaysOnTop = 0x0000002; + } + + public static class Shell32 + { + [DllImport("shell32.dll", SetLastError = true)] + public static extern IntPtr SHAppBarMessage(ABM dwMessage, [In] ref APPBARDATA pData); + } + + public static class User32 + { + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + } + + [StructLayout(LayoutKind.Sequential)] + public struct APPBARDATA + { + public uint cbSize; + public IntPtr hWnd; + public uint uCallbackMessage; + public ABE uEdge; + public RECT rc; + public int lParam; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } +} \ No newline at end of file diff --git a/Creator/App.config b/Creator/App.config new file mode 100644 index 0000000..68ab0eb --- /dev/null +++ b/Creator/App.config @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Creator/ApplicationArguments.cs b/Creator/ApplicationArguments.cs new file mode 100644 index 0000000..b782395 --- /dev/null +++ b/Creator/ApplicationArguments.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +using System.IO; + +namespace KattekerCreator +{ + public class ApplicationArguments + { + private string _changeLog; + private string _channel; + private string _outputDir; + private string _programFile; + private string _publishDir; + + [DisplayName("Program")] + [Description("Path to the program file")] + public string ProgramFile + { + get => _programFile; + set => SetPropertyIfNotDefault(ref _programFile, Path.GetFullPath(value)); + } + + [DisplayName("Changelog")] + [Description("Filename of the changelog file")] + public string ChangeLog + { + get => _changeLog; + set => SetPropertyIfNotDefault(ref _changeLog, value); + } + + [DisplayName("Channel")] + [Description("Channel of releasing.")] + public string Channel + { + get => _channel; + set => SetPropertyIfNotDefault(ref _channel, value); + } + + [DisplayName("OutDir")] + [Description("Directory for output")] + public string OutputDir + { + get => _outputDir; + set => SetPropertyIfNotDefault(ref _outputDir, value); + } + + [DisplayName("PublishDir")] + [Description("Path for output from the point of view of the application.")] + public string PublishDir + { + get => _publishDir; + set => SetPropertyIfNotDefault(ref _publishDir, value); + } + + private static bool SetPropertyIfNotDefault(ref T field, T value) + { + if (Equals(value, field)) return false; + if (!Equals(field, default(T))) return false; + field = value; + return true; + } + } +} \ No newline at end of file diff --git a/Creator/AssemblyFileInfo.cs b/Creator/AssemblyFileInfo.cs new file mode 100644 index 0000000..c28d01d --- /dev/null +++ b/Creator/AssemblyFileInfo.cs @@ -0,0 +1,93 @@ +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using Katteker; +using TsudaKageyu; +using Vestris.ResourceLib; + +namespace KattekerCreator +{ + public class AssemblyFileInfo + { + private readonly string _company; + private readonly string _copyright; + private readonly string _description; + private readonly string _productName; + + public AssemblyFileInfo(string fileName, string tempDir) + { + FileInfo = new FileInfo(fileName); + using (var resourceInfo = new ResourceInfo()) + { + resourceInfo.Load(fileName); + var versionResource = (VersionResource) resourceInfo[Kernel32.ResourceTypes.RT_VERSION].First(); + var stringFileInfo = ((StringFileInfo) versionResource[nameof(StringFileInfo)]).Strings.FirstOrDefault() + .Value; + AssemblyIconPath = GetAssemblyIcon(fileName, tempDir); + if (stringFileInfo.Strings.ContainsKey("CompanyName")) + _company = stringFileInfo.Strings["CompanyName"].StringValue; + if (stringFileInfo.Strings.ContainsKey("FileDescription")) + _description = stringFileInfo.Strings["FileDescription"].StringValue; + if (stringFileInfo.Strings.ContainsKey("LegalCopyright")) + _copyright = stringFileInfo.Strings["LegalCopyright"].StringValue; + if (stringFileInfo.Strings.ContainsKey("ProductName")) + _productName = stringFileInfo.Strings["ProductName"].StringValue; + if (!stringFileInfo.Strings.ContainsKey("ProductVersion")) return; + AssemblyVersion = GetSemanticVersion(stringFileInfo.Strings["ProductVersion"].StringValue); + } + } + + public string AssemblyIconPath { get; } + + public SemanticVersion AssemblyVersion { get; private set; } + + public string Company => _company ?? string.Empty; + + public string Copyright => _copyright ?? string.Empty; + + public string Description => _description ?? string.Empty; + + public FileInfo FileInfo { get; } + + public string ProductName => _productName ?? Description; + + private static string GetAssemblyIcon(string programFile, string tempDir) + { + var applicationIcon = Path.Combine(tempDir, "application.ico"); + if (string.IsNullOrWhiteSpace(programFile)) throw new ApplicationException("Program not set."); + if (!File.Exists(programFile)) throw new ApplicationException("Program not found."); + if (File.Exists(applicationIcon)) File.Delete(applicationIcon); + using (var fileStream = new FileStream(applicationIcon, FileMode.CreateNew)) + { + try + { + var ie = new IconExtractor(programFile); + using (var icon = ie.GetIcon(0)) + { + icon?.Save(fileStream); + } + } + catch (Exception) + { + using (var icon = Icon.ExtractAssociatedIcon(programFile)) + { + icon?.Save(fileStream); + } + } + } + + return applicationIcon; + } + + private static SemanticVersion GetSemanticVersion(string productVersion) + { + if (!SemanticVersion.TryParse(productVersion, out var semanticVersion)) + { + Version.TryParse(productVersion, out var version); + return SemanticVersion.Parse(version.ToString(3)); + } + return semanticVersion?.Change(build: string.Empty); + } + } +} \ No newline at end of file diff --git a/Creator/Helper/Log.cs b/Creator/Helper/Log.cs new file mode 100644 index 0000000..07647e4 --- /dev/null +++ b/Creator/Helper/Log.cs @@ -0,0 +1,51 @@ +using System; + +namespace KattekerCreator.Helper +{ + public static class Log + { + private const string Info = "INFO"; + private const string Error = "ERROR"; + + public static void WriteInfoLine(string text) + { + using (var c = new ConsoleWithOtherColor()) + { + c.WriteLine($"{Info} [{DateTime.Now:G}] {text}"); + } + } + + public static void WriteErrorLine(string text) + { + using (var c = new ConsoleWithOtherColor(ConsoleColor.DarkRed)) + { + c.WriteLine($"{Error} [{DateTime.Now:G}] {text}"); + } + } + } + + public class ConsoleWithOtherColor : IDisposable + { + public ConsoleWithOtherColor() + { + } + + private readonly ConsoleColor? _defaultColor; + + public ConsoleWithOtherColor(ConsoleColor color) + { + _defaultColor = Console.ForegroundColor; + Console.ForegroundColor = color; + } + + public void WriteLine(string text) + { + Console.WriteLine(text); + } + + public void Dispose() + { + if (_defaultColor != null) Console.ForegroundColor = _defaultColor.Value; + } + } +} \ No newline at end of file diff --git a/Creator/Helper/SimpleCommandLineParser.cs b/Creator/Helper/SimpleCommandLineParser.cs new file mode 100644 index 0000000..655f111 --- /dev/null +++ b/Creator/Helper/SimpleCommandLineParser.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace KattekerCreator.Helper +{ + public class SimpleCommandLineParser where TObject : new() + { + private TObject _parsedObject; + + public SimpleCommandLineParser(IEnumerable args) + { + Parse(args); + } + + private IDictionary Arguments { get; } = new Dictionary(); + + public bool Contains(string name) => Arguments.ContainsKey(name); + + public TObject GetObject() + { + if (!EqualityComparer.Default.Equals(_parsedObject, default(TObject))) + return _parsedObject; + _parsedObject = new TObject(); + try + { + LoopProperties(pi => + { + var displayName = GetDisplayName(pi); + if (displayName == null) return; + if (!Arguments.TryGetValue(displayName.ToLowerInvariant(), out var argument)) return; + if (pi.PropertyType == typeof(bool)) + { + pi.SetValue(_parsedObject, true); + return; + } + if (pi.PropertyType.IsArray) + pi.SetValue(_parsedObject, argument); + else + pi.SetValue(_parsedObject, argument.FirstOrDefault()); + }); + } + catch (Exception e) + { + Log.WriteErrorLine("Error parsing commandline arguments: " + e.Message); + ShowHelp(); + } + return _parsedObject; + } + + public void ShowHelp(TextWriter textWriter = null) + { + if (textWriter == null) textWriter = Console.Out; + var executable = Assembly.GetExecutingAssembly().GetName().Name + ".exe"; + var assemblyTitle = ((AssemblyTitleAttribute) Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyTitleAttribute), false))?.Title ?? executable; + var assemblyDescription = ((AssemblyDescriptionAttribute) Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyDescriptionAttribute), false))?.Description; + + textWriter.WriteLine(); + textWriter.WriteLine(assemblyTitle); + textWriter.WriteLine(); + textWriter.WriteLine(assemblyDescription); + textWriter.WriteLine(); + textWriter.WriteLine(executable + " "); + textWriter.WriteLine(); + var dict = new Dictionary(); + LoopProperties(pi => + { + var displayName = GetDisplayName(pi); + if (displayName == null) return; + var description = GetDescription(pi); + if (pi.PropertyType == typeof(bool)) description = "(Switch) " + description; + dict.Add(displayName, description); + }); + var longestWord = dict.Keys.Max(x => x.Length); + foreach (var kv in dict) + { + textWriter.Write("-" + kv.Key.PadRight(longestWord + 3, ' ')); + textWriter.WriteLine(kv.Value); + } + textWriter.WriteLine(); + textWriter.WriteLine("A switch will be set to true, if it will called."); + textWriter.WriteLine($"Example: {executable} "); + textWriter.WriteLine(); + textWriter.WriteLine("A parameter will be set."); + textWriter.WriteLine($"Example: {executable} \"Value\""); + } + + /// + /// Shows the current set options. + /// + /// If not set Console.Out will be set. + public void ShowProgramArguments(TextWriter textWriter = null) + { + if (textWriter == null) textWriter = Console.Out; + LoopProperties(pi => + { + var displayName = GetDisplayName(pi) ?? pi.Name; + var value = pi.GetValue(_parsedObject); + textWriter.Write(displayName.PadRight(20, ' ')); + if (value == null) + { + textWriter.WriteLine(""); + } + else + { + if (value.GetType().IsArray) + { + var arrayAsString = ((IEnumerable) value) + .Cast() + .Aggregate(string.Empty, (current, x) => current + x?.ToString() + ", "); + textWriter.WriteLine(arrayAsString); + } + else + { + textWriter.WriteLine(value.ToString()); + } + } + }); + } + + private static TAttribute GetAttribute(MemberInfo memberInfo) where TAttribute : Attribute => + (TAttribute) Attribute.GetCustomAttribute(memberInfo, typeof(TAttribute)); + + private static string GetDescription(MemberInfo memberInfo) => GetAttribute(memberInfo)?.Description; + + private static string GetDisplayName(MemberInfo memberInfo) => GetAttribute(memberInfo)?.DisplayName; + + private static void LoopProperties(Action action) + { + var properties = typeof(TObject).GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var propertyInfo in properties) + action.Invoke(propertyInfo); + } + + private void Parse(IEnumerable args) + { + var currentName = ""; + var values = new List(); + foreach (var arg in args) + { + if (arg.StartsWith("-", StringComparison.Ordinal)) + { + if (!string.IsNullOrWhiteSpace(currentName)) + Arguments[currentName.ToLowerInvariant()] = values.ToArray(); + values.Clear(); + currentName = arg.Substring(1); + } + else if (string.IsNullOrWhiteSpace(currentName)) + { + Arguments[arg] = new string[0]; + } + else + { + values.Add(arg); + } + } + + if (currentName != "") + Arguments[currentName.ToLowerInvariant()] = values.ToArray(); + } + } +} \ No newline at end of file diff --git a/Creator/Helper/Utils.cs b/Creator/Helper/Utils.cs new file mode 100644 index 0000000..2b75f8c --- /dev/null +++ b/Creator/Helper/Utils.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Vestris.ResourceLib; + +namespace KattekerCreator.Helper +{ + public static class Utils + { + public static bool CheckFileExistens(string filePath) + { + if (File.Exists(filePath)) return true; + var file = filePath.Replace(Directory.GetParent(filePath) + "\\", ""); + Console.WriteLine("Checking the prerequisites has failed."); + Console.WriteLine($"{file} is missing. Program exits now."); + Console.ReadKey(); + return false; + } + + public static string CreateTempDirectory() + { + string result; + do + { + result = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "temp", Environment.UserName, + Path.GetRandomFileName()); + } while (Directory.Exists(result)); + + Directory.CreateDirectory(result); + return result; + } + + public static bool IsFilesizeWrong(string filename) + { + var file = new FileInfo(filename); + return !file.Exists || file.Length < 524288; + } + + public static void CopyResources(string inFile, string outFile) + { + using (var ri = new ResourceInfo()) + { + ri.Load(inFile); + var versionResource = (VersionResource) ri[Kernel32.ResourceTypes.RT_VERSION].First(); + if (ri.ResourceTypes.Any(x => x.ResourceType == Kernel32.ResourceTypes.RT_GROUP_ICON)) + { + var groupIcon = ri[Kernel32.ResourceTypes.RT_GROUP_ICON]; + var iconDictionary = groupIcon.FirstOrDefault() as IconDirectoryResource; + iconDictionary?.SaveTo(outFile); + } + versionResource.Language = 0; + versionResource.SaveTo(outFile); + } + } + + + public static void DeleteTempDir(string tempDir) + { +#if !DEBUG + try + { + Directory.Delete(tempDir, true); + } + catch (Exception) + { + //silently ignore + } +#endif + } + + public static IEnumerable EnumerateFiles(string programFile, IEnumerable userdefinedFileExclusions = null) + { + if (string.IsNullOrWhiteSpace(programFile)) return null; + var directoryName = Path.GetDirectoryName(programFile); + if (directoryName == null) return null; + var files = new DirectoryInfo(directoryName).EnumerateFiles("*.*", SearchOption.AllDirectories); + var filter = FileExclusions(userdefinedFileExclusions).ToArray(); + return files.Where(x => !filter.Any(y => x.Name.EndsWith(y, StringComparison.Ordinal))); + } + + private static IEnumerable FileExclusions(IEnumerable userdefinedfileExclusions = null) + { + yield return ".pdb"; + yield return ".tmp"; + yield return ".obj"; + yield return ".pch"; + yield return ".vshost.exe"; + yield return ".vshost.exe.config"; + yield return ".vshost.exe.manifest"; + yield return "squirrelHelperInfo.json"; + yield return "Katteker.config"; + + if (userdefinedfileExclusions == null) yield break; + foreach (var fileExclusion in userdefinedfileExclusions) + { + yield return fileExclusion; + } + } + } +} \ No newline at end of file diff --git a/Creator/IconExtractor/IconExtractor.cs b/Creator/IconExtractor/IconExtractor.cs new file mode 100644 index 0000000..58b7a4d --- /dev/null +++ b/Creator/IconExtractor/IconExtractor.cs @@ -0,0 +1,283 @@ +/* + * IconExtractor/IconUtil for .NET + * Copyright (C) 2014 Tsuda Kageyu. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace TsudaKageyu +{ + public class IconExtractor + { + //////////////////////////////////////////////////////////////////////// + // Constants + + // Flags for LoadLibraryEx(). + + private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002; + + // Resource types for EnumResourceNames(). + + private readonly static IntPtr RT_ICON = (IntPtr)3; + private readonly static IntPtr RT_GROUP_ICON = (IntPtr)14; + + private const int MAX_PATH = 260; + + //////////////////////////////////////////////////////////////////////// + // Fields + + private byte[][] iconData = null; // Binary data of each icon. + + //////////////////////////////////////////////////////////////////////// + // Public properties + + /// + /// Gets the full path of the associated file. + /// + public string FileName + { + get; + private set; + } + + /// + /// Gets the count of the icons in the associated file. + /// + public int Count + { + get { return iconData.Length; } + } + + /// + /// Initializes a new instance of the IconExtractor class from the specified file name. + /// + /// The file to extract icons from. + public IconExtractor(string fileName) + { + Initialize(fileName); + } + + /// + /// Extracts an icon from the file. + /// + /// Zero based index of the icon to be extracted. + /// A System.Drawing.Icon object. + /// Always returns new copy of the Icon. It should be disposed by the user. + public Icon GetIcon(int index) + { + if (index < 0 || Count <= index) + throw new ArgumentOutOfRangeException("index"); + + // Create an Icon from the .ico file in memory. + + using (var ms = new MemoryStream(iconData[index])) + { + return new Icon(ms); + } + } + + /// + /// Extracts all the icons from the file. + /// + /// An array of System.Drawing.Icon objects. + /// Always returns new copies of the Icons. They should be disposed by the user. + public Icon[] GetAllIcons() + { + var icons = new List(); + for (int i = 0; i < Count; ++i) + icons.Add(GetIcon(i)); + + return icons.ToArray(); + } + + /// + /// Save an icon to the specified output Stream. + /// + /// Zero based index of the icon to be saved. + /// The Stream to save to. + public void Save(int index, Stream outputStream) + { + if (index < 0 || Count <= index) + throw new ArgumentOutOfRangeException("index"); + + if (outputStream == null) + throw new ArgumentNullException("outputStream"); + + var data = iconData[index]; + outputStream.Write(data, 0, data.Length); + } + + private void Initialize(string fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + + IntPtr hModule = IntPtr.Zero; + try + { + hModule = NativeMethods.LoadLibraryEx(fileName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); + if (hModule == IntPtr.Zero) + throw new Win32Exception(); + + FileName = GetFileName(hModule); + + // Enumerate the icon resource and build .ico files in memory. + + var tmpData = new List(); + + ENUMRESNAMEPROC callback = (h, t, name, l) => + { + // Refer to the following URL for the data structures used here: + // http://msdn.microsoft.com/en-us/library/ms997538.aspx + + // RT_GROUP_ICON resource consists of a GRPICONDIR and GRPICONDIRENTRY's. + + var dir = GetDataFromResource(hModule, RT_GROUP_ICON, name); + + // Calculate the size of an entire .icon file. + + int count = BitConverter.ToUInt16(dir, 4); // GRPICONDIR.idCount + int len = 6 + 16 * count; // sizeof(ICONDIR) + sizeof(ICONDIRENTRY) * count + for (int i = 0; i < count; ++i) + len += BitConverter.ToInt32(dir, 6 + 14 * i + 8); // GRPICONDIRENTRY.dwBytesInRes + + using (var dst = new BinaryWriter(new MemoryStream(len))) + { + // Copy GRPICONDIR to ICONDIR. + + dst.Write(dir, 0, 6); + + int picOffset = 6 + 16 * count; // sizeof(ICONDIR) + sizeof(ICONDIRENTRY) * count + + for (int i = 0; i < count; ++i) + { + // Load the picture. + + ushort id = BitConverter.ToUInt16(dir, 6 + 14 * i + 12); // GRPICONDIRENTRY.nID + var pic = GetDataFromResource(hModule, RT_ICON, (IntPtr)id); + + // Copy GRPICONDIRENTRY to ICONDIRENTRY. + + dst.Seek(6 + 16 * i, SeekOrigin.Begin); + + dst.Write(dir, 6 + 14 * i, 8); // First 8bytes are identical. + dst.Write(pic.Length); // ICONDIRENTRY.dwBytesInRes + dst.Write(picOffset); // ICONDIRENTRY.dwImageOffset + + // Copy a picture. + + dst.Seek(picOffset, SeekOrigin.Begin); + dst.Write(pic, 0, pic.Length); + + picOffset += pic.Length; + } + + tmpData.Add(((MemoryStream)dst.BaseStream).ToArray()); + } + + return true; + }; + NativeMethods.EnumResourceNames(hModule, RT_GROUP_ICON, callback, IntPtr.Zero); + + iconData = tmpData.ToArray(); + } + finally + { + if (hModule != IntPtr.Zero) + NativeMethods.FreeLibrary(hModule); + } + } + + private byte[] GetDataFromResource(IntPtr hModule, IntPtr type, IntPtr name) + { + // Load the binary data from the specified resource. + + IntPtr hResInfo = NativeMethods.FindResource(hModule, name, type); + if (hResInfo == IntPtr.Zero) + throw new Win32Exception(); + + IntPtr hResData = NativeMethods.LoadResource(hModule, hResInfo); + if (hResData == IntPtr.Zero) + throw new Win32Exception(); + + IntPtr pResData = NativeMethods.LockResource(hResData); + if (pResData == IntPtr.Zero) + throw new Win32Exception(); + + uint size = NativeMethods.SizeofResource(hModule, hResInfo); + if (size == 0) + throw new Win32Exception(); + + byte[] buf = new byte[size]; + Marshal.Copy(pResData, buf, 0, buf.Length); + + return buf; + } + + private string GetFileName(IntPtr hModule) + { + // Alternative to GetModuleFileName() for the module loaded with + // LOAD_LIBRARY_AS_DATAFILE option. + + // Get the file name in the format like: + // "\\Device\\HarddiskVolume2\\Windows\\System32\\shell32.dll" + + string fileName; + { + var buf = new StringBuilder(MAX_PATH); + int len = NativeMethods.GetMappedFileName( + NativeMethods.GetCurrentProcess(), hModule, buf, buf.Capacity); + if (len == 0) + throw new Win32Exception(); + + fileName = buf.ToString(); + } + + // Convert the device name to drive name like: + // "C:\\Windows\\System32\\shell32.dll" + + for (char c = 'A'; c <= 'Z'; ++c) + { + var drive = c + ":"; + var buf = new StringBuilder(MAX_PATH); + int len = NativeMethods.QueryDosDevice(drive, buf, buf.Capacity); + if (len == 0) + continue; + + var devPath = buf.ToString(); + if (fileName.StartsWith(devPath)) + return (drive + fileName.Substring(devPath.Length)); + } + + return fileName; + } + } +} \ No newline at end of file diff --git a/Creator/IconExtractor/IconUtil.cs b/Creator/IconExtractor/IconUtil.cs new file mode 100644 index 0000000..bded74f --- /dev/null +++ b/Creator/IconExtractor/IconUtil.cs @@ -0,0 +1,202 @@ +/* + * IconExtractor/IconUtil for .NET + * Copyright (C) 2014 Tsuda Kageyu. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; + +namespace TsudaKageyu +{ + public static class IconUtil + { + private delegate byte[] GetIconDataDelegate(Icon icon); + + static GetIconDataDelegate getIconData; + + static IconUtil() + { + // Create a dynamic method to access Icon.iconData private field. + + var dm = new DynamicMethod( + "GetIconData", typeof(byte[]), new Type[] { typeof(Icon) }, typeof(Icon)); + var fi = typeof(Icon).GetField( + "iconData", BindingFlags.Instance | BindingFlags.NonPublic); + var gen = dm.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, fi); + gen.Emit(OpCodes.Ret); + + getIconData = (GetIconDataDelegate)dm.CreateDelegate(typeof(GetIconDataDelegate)); + } + + /// + /// Split an Icon consists of multiple icons into an array of Icon each + /// consists of single icons. + /// + /// A System.Drawing.Icon to be split. + /// An array of System.Drawing.Icon. + public static Icon[] Split(Icon icon) + { + if (icon == null) + throw new ArgumentNullException("icon"); + + // Get an .ico file in memory, then split it into separate icons. + + var src = GetIconData(icon); + + var splitIcons = new List(); + { + int count = BitConverter.ToUInt16(src, 4); + + for (int i = 0; i < count; i++) + { + int length = BitConverter.ToInt32(src, 6 + 16 * i + 8); // ICONDIRENTRY.dwBytesInRes + int offset = BitConverter.ToInt32(src, 6 + 16 * i + 12); // ICONDIRENTRY.dwImageOffset + + using (var dst = new BinaryWriter(new MemoryStream(6 + 16 + length))) + { + // Copy ICONDIR and set idCount to 1. + + dst.Write(src, 0, 4); + dst.Write((short)1); + + // Copy ICONDIRENTRY and set dwImageOffset to 22. + + dst.Write(src, 6 + 16 * i, 12); // ICONDIRENTRY except dwImageOffset + dst.Write(22); // ICONDIRENTRY.dwImageOffset + + // Copy a picture. + + dst.Write(src, offset, length); + + // Create an icon from the in-memory file. + + dst.BaseStream.Seek(0, SeekOrigin.Begin); + splitIcons.Add(new Icon(dst.BaseStream)); + } + } + } + + return splitIcons.ToArray(); + } + + /// + /// Converts an Icon to a GDI+ Bitmap preserving the transparent area. + /// + /// An System.Drawing.Icon to be converted. + /// A System.Drawing.Bitmap Object. + public static Bitmap ToBitmap(Icon icon) + { + if (icon == null) + throw new ArgumentNullException("icon"); + + // Quick workaround: Create an .ico file in memory, then load it as a Bitmap. + + using (var ms = new MemoryStream()) + { + icon.Save(ms); + using (var bmp = (Bitmap)Image.FromStream(ms)) + { + return new Bitmap(bmp); + } + } + } + + /// + /// Gets the bit depth of an Icon. + /// + /// An System.Drawing.Icon object. + /// Bit depth of the icon. + /// + /// This method takes into account the PNG header. + /// If the icon has multiple variations, this method returns the bit + /// depth of the first variation. + /// + public static int GetBitCount(Icon icon) + { + if (icon == null) + throw new ArgumentNullException("icon"); + + // Get an .ico file in memory, then read the header. + + var data = GetIconData(icon); + if (data.Length >= 51 + && data[22] == 0x89 && data[23] == 0x50 && data[24] == 0x4e && data[25] == 0x47 + && data[26] == 0x0d && data[27] == 0x0a && data[28] == 0x1a && data[29] == 0x0a + && data[30] == 0x00 && data[31] == 0x00 && data[32] == 0x00 && data[33] == 0x0d + && data[34] == 0x49 && data[35] == 0x48 && data[36] == 0x44 && data[37] == 0x52) + { + // The picture is PNG. Read IHDR chunk. + + switch (data[47]) + { + case 0: + return data[46]; + case 2: + return data[46] * 3; + case 3: + return data[46]; + case 4: + return data[46] * 2; + case 6: + return data[46] * 4; + default: + // NOP + break; + } + } + else if (data.Length >= 22) + { + // The picture is not PNG. Read ICONDIRENTRY structure. + + return BitConverter.ToUInt16(data, 12); + } + + throw new ArgumentException("The icon is corrupt. Couldn't read the header.", "icon"); + } + + private static byte[] GetIconData(Icon icon) + { + var data = getIconData(icon); + if (data != null) + { + return data; + } + else + { + using (var ms = new MemoryStream()) + { + icon.Save(ms); + return ms.ToArray(); + } + } + } + } +} \ No newline at end of file diff --git a/Creator/IconExtractor/NativeMethods.cs b/Creator/IconExtractor/NativeMethods.cs new file mode 100644 index 0000000..c151497 --- /dev/null +++ b/Creator/IconExtractor/NativeMethods.cs @@ -0,0 +1,81 @@ +/* + * IconExtractor/IconUtil for .NET + * Copyright (C) 2014 Tsuda Kageyu. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace TsudaKageyu +{ + internal static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags); + + [DllImport("kernel32.dll", SetLastError = true)] + [SuppressUnmanagedCodeSecurity] + public static extern bool FreeLibrary(IntPtr hModule); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + public static extern bool EnumResourceNames(IntPtr hModule, IntPtr lpszType, ENUMRESNAMEPROC lpEnumFunc, IntPtr lParam); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + public static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType); + + [DllImport("kernel32.dll", SetLastError = true)] + [SuppressUnmanagedCodeSecurity] + public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + [SuppressUnmanagedCodeSecurity] + public static extern IntPtr LockResource(IntPtr hResData); + + [DllImport("kernel32.dll", SetLastError = true)] + [SuppressUnmanagedCodeSecurity] + public static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + [SuppressUnmanagedCodeSecurity] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + public static extern int QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); + + [DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + public static extern int GetMappedFileName(IntPtr hProcess, IntPtr lpv, StringBuilder lpFilename, int nSize); + } + + [UnmanagedFunctionPointer(CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal delegate bool ENUMRESNAMEPROC(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam); +} \ No newline at end of file diff --git a/Creator/KattekerCreator.csproj b/Creator/KattekerCreator.csproj new file mode 100644 index 0000000..e81ba0b --- /dev/null +++ b/Creator/KattekerCreator.csproj @@ -0,0 +1,102 @@ + + + + + Debug + AnyCPU + {53D065EB-8818-4F60-9EBE-75705E1BB00D} + Exe + Properties + KattekerCreator + KattekerCreator + v4.5 + 512 + ..\ + true + + + + + x64 + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + ..\packages\Vestris.ResourceLib.1.6.422\lib\Vestris.ResourceLib.dll + + + + + + + + + + + + + + + + True + True + SetupTmpl.tt + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + TextTemplatingFilePreprocessor + SetupTmpl.cs + + + + + + + + {a45e1c59-ba9e-452c-a5e2-50de49d53e92} + Katteker + + + + + \ No newline at end of file diff --git a/Creator/PhysicalFile.cs b/Creator/PhysicalFile.cs new file mode 100644 index 0000000..ff99905 --- /dev/null +++ b/Creator/PhysicalFile.cs @@ -0,0 +1,14 @@ +namespace KattekerCreator +{ + public class PhysicalFile + { + public PhysicalFile(string sourcePath, string targetPath) + { + SourcePath = sourcePath; + TargetPath = targetPath; + } + + public string SourcePath { get; } + public string TargetPath { get; } + } +} \ No newline at end of file diff --git a/Creator/Program.cs b/Creator/Program.cs new file mode 100644 index 0000000..7fd006f --- /dev/null +++ b/Creator/Program.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Katteker; +using KattekerCreator.Helper; + +namespace KattekerCreator +{ + public class Program + { + private const string _executable = @"C:\Program Files (x86)\NSIS\makensis.exe"; + private readonly string _baseDirectory = AppDomain.CurrentDomain.BaseDirectory; + private ApplicationArguments _appArguments; + private AssemblyFileInfo _assemblyFileInfo; + private string _tempDir; + private Releases _releases; + + private static int Main(string[] args) + { + var program = new Program(); + try + { + return program.Run(args); + } + catch (Exception ex) + { + Log.WriteErrorLine(ex.Message); + if (ex.InnerException != null) + Log.WriteErrorLine(ex.InnerException.Message); +#if DEBUG + throw; +#else + return -1; +#endif + } + } + + private int Run(string[] args) + { + //Parse App-Arguments + var parser = new SimpleCommandLineParser(args); + + if (args.Length == 0) + { + parser.ShowHelp(); + return 1924; + } + + _appArguments = parser.GetObject(); + //Create tempdir + _tempDir = Utils.CreateTempDirectory(); + _releases = new Releases(_appArguments.OutputDir); + parser.ShowProgramArguments(); + //Modify AppStub + var appStubFile = ModifyAppStub(); + //Acquire infos from Executable. + _assemblyFileInfo = new AssemblyFileInfo(_appArguments.ProgramFile, _tempDir); + var configFile = CreateConfigFile(); + + //Generate NSIS-Script + var additionalFiles = new[] + { + new PhysicalFile(appStubFile, Path.GetFileName(_appArguments.ProgramFile)), + new PhysicalFile(configFile, Path.Combine($"app-{_assemblyFileInfo.AssemblyVersion}", Path.GetFileName(configFile))) + }; + var templateFile = GenerateNsisTemplate(additionalFiles); + //Start makensis.exe + var setupFilePath = CompileSetupScript(templateFile); + //Copy to Output-Folder + if (CopyToOutputFolder(setupFilePath)) + { + //Create/Modify RELEASE File + var releaseEntry = AddPackageToReleaseFile(setupFilePath); + //Copy installer as setup.exe + CopyAsSetup(setupFilePath, releaseEntry); + } + + return 0; + } + + private void CopyAsSetup(string setupFilePath, ReleaseEntry releaseEntry) + { + if (!_releases.IsLatestEntry(releaseEntry)) return; + var target = Path.Combine(_appArguments.OutputDir, Constants.SetupFile); + File.Delete(target); + File.Copy(setupFilePath, target); + } + + private ReleaseEntry AddPackageToReleaseFile(string setupFilePath) + { + try + { + var result = _releases.Add(setupFilePath, _assemblyFileInfo.AssemblyVersion); + _releases.WriteReleaseFile(); + return result; + } + catch (Exception e) + { + Log.WriteErrorLine(e.Message); + throw; + } + } + + private bool CopyToOutputFolder(string setupFilePath) + { + try + { + if (setupFilePath == null) throw new ArgumentNullException(nameof(setupFilePath)); + var setupFile = Path.GetFileName(setupFilePath); + if (string.IsNullOrEmpty(setupFile)) throw new ArgumentException(); + if (!File.Exists(setupFilePath)) throw new FileNotFoundException(setupFile); + if (!string.IsNullOrEmpty(_appArguments.ChangeLog)) + { + + var changeLogPath = Path.Combine(Path.GetDirectoryName(_appArguments.ProgramFile), _appArguments.ChangeLog); + if (!File.Exists(changeLogPath)) throw new FileNotFoundException(changeLogPath); + File.Copy(changeLogPath, Path.Combine(_appArguments.OutputDir, Path.GetFileName(_appArguments.ChangeLog) ?? throw new InvalidOperationException()), true); + } + + if (!Directory.Exists(_appArguments.OutputDir)) Directory.CreateDirectory(_appArguments.OutputDir); + File.Copy(setupFilePath, Path.Combine(_appArguments.OutputDir, setupFile), true); + return true; + } + catch (Exception e) + { + Log.WriteErrorLine(e.Message); + return false; + } + } + + private static string CompileSetupScript(string templateFile) + { + if (!File.Exists(_executable)) throw new FileNotFoundException("NSIS not installed properly."); + int exitcode; + using (var p = new Process()) + { + p.StartInfo = new ProcessStartInfo + { + FileName = _executable, + Arguments = templateFile, + UseShellExecute = false + }; + var sw = Stopwatch.StartNew(); + p.Start(); + p.WaitForExit(); + exitcode = p.ExitCode; + Log.WriteInfoLine($"{Path.GetFileName(_executable)} has exited with Exitcode: {exitcode} (Took: {sw.ElapsedMilliseconds}ms)"); + } + + if (exitcode != 0) throw new Exception($"{Path.GetFileName(_executable)} has exited with Exitcode: {exitcode}"); + return Path.ChangeExtension(templateFile, "exe"); + } + + private string CreateConfigFile() + { + var pathToFile = Path.Combine(_tempDir, Constants.KattekerConfig); + var kattekerConfig = new KattekerConfig + { + PublishDir = _appArguments.PublishDir ?? _appArguments.OutputDir, + Changelog = _appArguments.ChangeLog + }; + kattekerConfig.WriteToFile(pathToFile); + return pathToFile; + } + + private string ModifyAppStub() + { + var baseFile = new FileInfo(Path.Combine(_baseDirectory, Constants.ExecutionStub)); + var targetFile = baseFile.CopyTo(Path.Combine(_tempDir, Constants.ExecutionStub)); + Utils.CopyResources(_appArguments.ProgramFile, targetFile.FullName); + return targetFile.FullName; + } + + private static string GenerateFilename(string name, string version, string extension, bool isDelta = false) => $"{name}-{version}-{(isDelta ? "delta" : "full")}.{extension}"; + + private string GenerateNsisTemplate(IEnumerable additionalFiles) + { + try + { + var outFile = Path.Combine(_tempDir, GenerateFilename(_assemblyFileInfo.ProductName, _assemblyFileInfo.AssemblyVersion.ToString(), "nsi")); + var setupTmpl = new SetupTmpl + { + Executable = _assemblyFileInfo.FileInfo.Name, + AppName = _assemblyFileInfo.ProductName, + CompanyName = _assemblyFileInfo.Company, + Description = _assemblyFileInfo.Description, + IconPath = _assemblyFileInfo.AssemblyIconPath, + Version = _assemblyFileInfo.AssemblyVersion, + HelpUrl = "http://example.org", //Not used at the moment. + UserInterface = Path.Combine(_baseDirectory, "contrib", "LoadingBar_Icon.exe"), + UninstallIcon = Path.Combine(_baseDirectory, "contrib", "uninstall.ico"), + OutFile = Path.GetFileName(Path.ChangeExtension(outFile, "exe")), + ReleaseChannel = _appArguments.Channel + }; + var path = Path.GetDirectoryName(_appArguments.ProgramFile) ?? string.Empty; + setupTmpl.Files.AddRange(additionalFiles); + var files = Utils.EnumerateFiles(_appArguments.ProgramFile).ToArray(); + setupTmpl.InstallSize = (long) Math.Floor(files.Sum(x => x.Length) / 1024f); + setupTmpl.Files.AddRange(files.Select(x => new PhysicalFile(x.FullName, x.FullName.Replace(path, $"app-{_assemblyFileInfo.AssemblyVersion}")))); + var transformText = setupTmpl.TransformText(); + File.WriteAllText(outFile, transformText); + return outFile; + } + catch (Exception e) + { + Log.WriteErrorLine(e.Message); + throw; + } + } + } +} \ No newline at end of file diff --git a/Creator/Properties/AssemblyInfo.cs b/Creator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f8a3af7 --- /dev/null +++ b/Creator/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("KattekerCreator")] +[assembly: AssemblyDescription("Creates Installation and Update packages.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Holger Börchers")] +[assembly: AssemblyProduct("KattekerCreator")] +[assembly: AssemblyCopyright("Copyright © Holger Börchers")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("ef6af08f-1e5f-44c5-9be8-973b130f8086")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + diff --git a/Creator/SemanticVersion.cs b/Creator/SemanticVersion.cs new file mode 100644 index 0000000..dd84f50 --- /dev/null +++ b/Creator/SemanticVersion.cs @@ -0,0 +1,270 @@ +using System; +using System.Text.RegularExpressions; + +namespace KattekerCreator +{ + /// + /// A hybrid implementation of SemVer that supports semantic versioning as described at http://semver.org while not + /// strictly enforcing it to + /// allow older 4-digit versioning schemes to continue working. + /// + [Serializable] +//[TypeConverter(typeof(SemanticVersionTypeConverter))] + public sealed class SemanticVersion : IComparable, IComparable, IEquatable + { + private const RegexOptions Flags = + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + + private static readonly Regex SemanticVersionRegex = + new Regex(@"^(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?$", Flags); + + private static readonly Regex StrictSemanticVersionRegex = + new Regex(@"^(?\d+(\.\d+){2})(?-[a-z][0-9a-z-]*)?$", Flags); + + private readonly string _originalString; + + public SemanticVersion(string version) + : this(Parse(version)) + { + // The constructor normalizes the version string so that it we do not need to normalize it every time we need to operate on it. + // The original string represents the original form in which the version is represented to be used when printing. + _originalString = version; + } + + public SemanticVersion(int major, int minor, int build, int revision) + : this(new Version(major, minor, build, revision)) + { + } + + public SemanticVersion(int major, int minor, int build, string specialVersion) + : this(new Version(major, minor, build), specialVersion) + { + } + + public SemanticVersion(Version version) + : this(version, string.Empty) + { + } + + public SemanticVersion(Version version, string specialVersion) + : this(version, specialVersion, null) + { + } + + private SemanticVersion(Version version, string specialVersion, string originalString) + { + if (version == null) + throw new ArgumentNullException(nameof(version)); + Version = NormalizeVersionValue(version); + SpecialVersion = specialVersion ?? string.Empty; + _originalString = string.IsNullOrEmpty(originalString) + ? version + (!string.IsNullOrEmpty(specialVersion) ? '-' + specialVersion : null) + : originalString; + } + + internal SemanticVersion(SemanticVersion semVer) + { + _originalString = semVer.ToString(); + Version = semVer.Version; + SpecialVersion = semVer.SpecialVersion; + } + + /// + /// Gets the normalized version portion. + /// + public Version Version { get; } + + /// + /// Gets the optional special version. + /// + public string SpecialVersion { get; } + + public int CompareTo(object obj) + { + if (ReferenceEquals(obj, null)) + return 1; + var other = obj as SemanticVersion; + if (other == null) + throw new ArgumentException("Type Must Be A Semantic Version", nameof(obj)); + return CompareTo(other); + } + + public int CompareTo(SemanticVersion other) + { + if (ReferenceEquals(other, null)) + return 1; + + var result = Version.CompareTo(other.Version); + + if (result != 0) + return result; + + var empty = string.IsNullOrEmpty(SpecialVersion); + var otherEmpty = string.IsNullOrEmpty(other.SpecialVersion); + if (empty && otherEmpty) + return 0; + if (empty) + return 1; + if (otherEmpty) + return -1; + return StringComparer.OrdinalIgnoreCase.Compare(SpecialVersion, other.SpecialVersion); + } + + public bool Equals(SemanticVersion other) + { + return !ReferenceEquals(null, other) && + Version.Equals(other.Version) && + SpecialVersion.Equals(other.SpecialVersion, StringComparison.OrdinalIgnoreCase); + } + + public string[] GetOriginalVersionComponents() + { + if (!string.IsNullOrEmpty(_originalString)) + { + // search the start of the SpecialVersion part, if any + var dashIndex = _originalString.IndexOf('-'); + var original = dashIndex != -1 ? _originalString.Substring(0, dashIndex) : _originalString; + + return SplitAndPadVersionString(original); + } + + return SplitAndPadVersionString(Version.ToString()); + } + + private static string[] SplitAndPadVersionString(string version) + { + var a = version.Split('.'); + if (a.Length == 4) + { + return a; + } + + // if 'a' has less than 4 elements, we pad the '0' at the end + // to make it 4. + var b = new string[4] {"0", "0", "0", "0"}; + Array.Copy(a, 0, b, 0, a.Length); + return b; + } + + /// + /// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an + /// optional special version. + /// + public static SemanticVersion Parse(string version) + { + if (string.IsNullOrEmpty(version)) + throw new ArgumentException(nameof(version)); + + if (!TryParse(version, out var semVer)) + throw new ArgumentException("Invalid Version String", nameof(version)); + return semVer; + } + + /// + /// Parses a version string using loose semantic versioning rules that allows 2-4 version components followed by an + /// optional special version. + /// + public static bool TryParse(string version, out SemanticVersion value) + { + return TryParseInternal(version, SemanticVersionRegex, out value); + } + + /// + /// Parses a version string using strict semantic versioning rules that allows exactly 3 components and an optional + /// special version. + /// + public static bool TryParseStrict(string version, out SemanticVersion value) + { + return TryParseInternal(version, StrictSemanticVersionRegex, out value); + } + + private static bool TryParseInternal(string version, Regex regex, out SemanticVersion semVer) + { + semVer = null; + if (string.IsNullOrEmpty(version)) + return false; + + var match = regex.Match(version.Trim()); + if (!match.Success || !Version.TryParse(match.Groups["Version"].Value, out var versionValue)) + return false; + + semVer = new SemanticVersion(NormalizeVersionValue(versionValue), + match.Groups["Release"].Value.TrimStart('-'), version.Replace(" ", "")); + return true; + } + + /// + /// Attempts to parse the version token as a SemanticVersion. + /// + /// An instance of SemanticVersion if it parses correctly, null otherwise. + public static SemanticVersion ParseOptionalVersion(string version) + { + TryParse(version, out var semVer); + return semVer; + } + + private static Version NormalizeVersionValue(Version version) + { + return new Version(version.Major, + version.Minor, + Math.Max(version.Build, 0), + Math.Max(version.Revision, 0)); + } + + public static bool operator ==(SemanticVersion version1, SemanticVersion version2) + { + if (ReferenceEquals(version1, null)) + return ReferenceEquals(version2, null); + return version1.Equals(version2); + } + + public static bool operator !=(SemanticVersion version1, SemanticVersion version2) + { + return !(version1 == version2); + } + + public static bool operator <(SemanticVersion version1, SemanticVersion version2) + { + if (version1 == null) + throw new ArgumentNullException(nameof(version1)); + return version1.CompareTo(version2) < 0; + } + + public static bool operator <=(SemanticVersion version1, SemanticVersion version2) + { + return version1 == version2 || version1 < version2; + } + + public static bool operator >(SemanticVersion version1, SemanticVersion version2) + { + if (version1 == null) + throw new ArgumentNullException(nameof(version1)); + return version2 < version1; + } + + public static bool operator >=(SemanticVersion version1, SemanticVersion version2) + { + return version1 == version2 || version1 > version2; + } + + public override string ToString() + { + return _originalString; + } + + public override bool Equals(object obj) + { + var semVer = obj as SemanticVersion; + return !ReferenceEquals(null, semVer) && Equals(semVer); + } + + public override int GetHashCode() + { + var hashCode = Version.GetHashCode(); + if (SpecialVersion != null) + hashCode = hashCode * 4567 + SpecialVersion.GetHashCode(); + + return hashCode; + } + } +} \ No newline at end of file diff --git a/Creator/Setup.nsi b/Creator/Setup.nsi new file mode 100644 index 0000000..899b8aa --- /dev/null +++ b/Creator/Setup.nsi @@ -0,0 +1,103 @@ +!define APPNAME "Shutdown8" +!define EXECUTABLE "Shutdown8.exe" +!define COMPANYNAME "Bandisoft.com" +!define DESCRIPTION "NoDescription" +!define VERSION 1.8.0 +!define HELPURL https://srv-lsimctrl01.enercon.de +!define INSTALLSIZE 155573 +!define ISMANAGED 1 +; exampleCmd: makensis.exe /DVERSION=1.0.0.5 /DNAME=WpfApp1 Setup.nsi + +Name "${APPNAME}" +OutFile "${APPNAME}-${VERSION}.exe" +InstallDir "$LOCALAPPDATA\${APPNAME}" +RequestExecutionLevel user +SetCompressor /SOLID lzma +SilentUnInstall silent +; Subcaption 3 " " +XPStyle on +AutoCloseWindow true +ChangeUI all "${NSISDIR}\Contrib\UIs\sdbarker_tiny.exe" +Icon "${NSISDIR}\Contrib\Graphics\Icons\nsis3-install.ico" +UninstallIcon "${NSISDIR}\Contrib\Graphics\Icons\nsis3-uninstall.ico" +ShowInstDetails nevershow +ShowUninstDetails nevershow +BrandingText "${COMPANYNAME}" + +;-------------------------------- +; The stuff to install +Section "install" ;No components page, name is not important + DetailPrint 'Installing ${APPNAME}. Please wait...' + SetShellVarContext current + SetDetailsPrint None + SetOutPath $INSTDIR\app-${VERSION} + ; Put file there + File /r /x *.pdb /x *.obj /x *.pch /x *.vshost.exe /x *.vshost.exe.* Release\*.* + SetOutPath $INSTDIR + File "/oname=${Executable}" AppStub.exe + File Rabbit.Shared.dll + WriteINIStr $INSTDIR\app.ini Main Executable "${EXECUTABLE}" + WriteINIStr $INSTDIR\app.ini Main AppName "${APPNAME}" + WriteINIStr $INSTDIR\app.ini Main IsManaged ${ISMANAGED} + + WriteUninstaller "$INSTDIR\uninstall.exe" + ; Desktop + CreateShortCut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${Executable}" + + # Start Menu + CreateDirectory "$SMPROGRAMS\${COMPANYNAME}" + CreateShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" "$INSTDIR\${Executable}" + + # Update pinned Taskbar + IfFileExists "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\${APPNAME}.lnk" 0 +2 + CreateShortCut "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\${APPNAME}.lnk" "$INSTDIR\${Executable}" + SetOutPath $INSTDIR\app-${VERSION} + StrCpy $0 ${EXECUTABLE} -4 + IfFileExists "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$0.lnk" 0 +2 + CreateShortCut "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$0.lnk" "$INSTDIR\app-${VERSION}\${EXECUTABLE}" + + # Registry information for add/remove programs + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "InstallLocation" "$\"$INSTDIR$\"" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$\"$INSTDIR\app-${VERSION}\${EXECUTABLE}$\"" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "${COMPANYNAME}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "HelpLink" "${HELPURL}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${VERSION}" + # There is no option for modifying or repairing the install + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "NoModify" 1 + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "NoRepair" 1 + # Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "EstimatedSize" ${INSTALLSIZE} +SectionEnd ; end the section + +Function .onInstSuccess + IfSilent +2 + Exec '"$INSTDIR\${Executable}"' +FunctionEnd + +Section "uninstall" + DetailPrint 'Please wait...' + SetShellVarContext current + SetDetailsPrint None + SetAutoClose true + + # Remove Start Menu launcher + Delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" + Delete "$DESKTOP\${APPNAME}.lnk" + StrCpy $0 ${EXECUTABLE} -4 + Delete "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\${APPNAME}.lnk" + Delete "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$0.lnk" + # Try to remove the Start Menu folder - this will only happen if it is empty + RMDir "$SMPROGRAMS\${COMPANYNAME}" + + # Always delete uninstaller as the last action + delete $INSTDIR\uninstall.exe + + # Try to remove the install directory - this will only happen if it is empty + RMDir /r /REBOOTOK $INSTDIR + + # Remove uninstaller information from the registry + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" +sectionEnd \ No newline at end of file diff --git a/Creator/SetupTmpl.cs b/Creator/SetupTmpl.cs new file mode 100644 index 0000000..66460f2 --- /dev/null +++ b/Creator/SetupTmpl.cs @@ -0,0 +1,522 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 15.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace KattekerCreator +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + + #line 1 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "15.0.0.0")] + public partial class SetupTmpl : SetupTmplBase + { +#line hidden + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("Unicode true\r\nManifestDPIAware true\r\n!define APPNAME \""); + + #line 8 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(AppName)); + + #line default + #line hidden + this.Write("\"\r\n!define EXECUTABLE \""); + + #line 9 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(Executable)); + + #line default + #line hidden + this.Write("\"\r\n!define SOURCEPATH \""); + + #line 10 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(SourcePath)); + + #line default + #line hidden + this.Write("\"\r\n!define COMPANYNAME \""); + + #line 11 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(CompanyName)); + + #line default + #line hidden + this.Write("\"\r\n!define DESCRIPTION \""); + + #line 12 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(Description)); + + #line default + #line hidden + this.Write("\"\r\n!define VERSION "); + + #line 13 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(Version.ToString())); + + #line default + #line hidden + this.Write("\r\n!define HELPURL "); + + #line 14 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(HelpUrl)); + + #line default + #line hidden + this.Write("\r\n!define INSTALLSIZE "); + + #line 15 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(InstallSize)); + + #line default + #line hidden + this.Write("\r\n!define ISMANAGED "); + + #line 16 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(IsManaged ? 1 : 0)); + + #line default + #line hidden + this.Write("\r\n; exampleCmd: makensis.exe /DVERSION=1.0.0.5 /DNAME=WpfApp1 Setup.nsi\r\n\r\nName \"" + + "${APPNAME}\"\r\nOutFile \""); + + #line 20 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(OutFile)); + + #line default + #line hidden + this.Write("\"\r\nInstallDir \"$LOCALAPPDATA\\${APPNAME}\"\r\nRequestExecutionLevel user\r\nSetCompress" + + "or /SOLID lzma\r\nSilentUnInstall silent\r\n; Subcaption 3 \" \"\r\nXPStyle on\r\nAutoClos" + + "eWindow true\r\nChangeUI all \""); + + #line 28 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(UserInterface)); + + #line default + #line hidden + this.Write("\"\r\nIcon \""); + + #line 29 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(IconPath)); + + #line default + #line hidden + this.Write("\"\r\nUninstallIcon \""); + + #line 30 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(UninstallIcon)); + + #line default + #line hidden + this.Write("\"\r\nShowInstDetails nevershow \r\nShowUninstDetails nevershow \r\nBrandingText \"${COMP" + + "ANYNAME}\"\r\n\r\nVIProductVersion "); + + #line 35 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(LegacyVersion.ToString(4))); + + #line default + #line hidden + this.Write(@" +VIAddVersionKey ProductName ""${APPNAME}"" +VIAddVersionKey Comments """" +VIAddVersionKey CompanyName ""${COMPANYNAME}"" +VIAddVersionKey LegalCopyright ""${COMPANYNAME}"" +VIAddVersionKey FileDescription ""${DESCRIPTION}"" +VIAddVersionKey FileVersion ""${VERSION}"" +VIAddVersionKey ProductVersion ""${VERSION}"" +VIAddVersionKey OriginalFilename """); + + #line 43 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(OutFile)); + + #line default + #line hidden + this.Write(@""" + +;-------------------------------- +; The stuff to install +Section ""install"" ;No components page, name is not important + DetailPrint 'Installing ${APPNAME}. Please wait...' + SetShellVarContext current + SetDetailsPrint None + IfSilent +2 + RMDir /r $INSTDIR + SetOutPath $INSTDIR + + ; Create sub-directories +"); + + #line 56 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + foreach(var directory in Directories) { + + #line default + #line hidden + + #line 56 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture($" CreateDirectory \"$INSTDIR\\{directory}\"{Environment.NewLine}")); + + #line default + #line hidden + + #line 56 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + } + + #line default + #line hidden + this.Write("\r\n ; Put file there\r\n"); + + #line 59 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" +foreach(var file in Files) { + + #line default + #line hidden + + #line 59 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + this.Write(this.ToStringHelper.ToStringWithCulture($" File \"/oname={file.TargetPath}\" \"{file.SourcePath}\"{Environment.NewLine}")); + + #line default + #line hidden + + #line 59 "C:\Strukturberechnung_del\Repos\SquirrelKiller\Katteker\Creator\SetupTmpl.tt" + } + + #line default + #line hidden + this.Write("\r\n WriteUninstaller \"$INSTDIR\\uninstall.exe\"\r\n ; Desktop\r\n CreateShortCu" + + "t \"$DESKTOP\\${APPNAME}.lnk\" \"$INSTDIR\\${EXECUTABLE}\"\r\n \r\n # Start Menu\r\n " + + " CreateDirectory \"$SMPROGRAMS\\${COMPANYNAME}\"\r\n CreateShortCut \"$SMPROGRAMS\\" + + "${COMPANYNAME}\\${APPNAME}.lnk\" \"$INSTDIR\\${EXECUTABLE}\"\r\n\r\n # Update pinned T" + + "askbar\r\n IfFileExists \"$APPDATA\\Microsoft\\Internet Explorer\\Quick Launch\\User" + + " Pinned\\TaskBar\\${APPNAME}.lnk\" 0 +2\r\n CreateShortCut \"$APPDATA\\Microsoft\\Int" + + "ernet Explorer\\Quick Launch\\User Pinned\\TaskBar\\${APPNAME}.lnk\" \"$INSTDIR\\${EXEC" + + "UTABLE}\"\r\n SetOutPath $INSTDIR\\app-${VERSION}\r\n StrCpy $0 ${EXECUTABLE} -4" + + "\r\n IfFileExists \"$APPDATA\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinne" + + "d\\TaskBar\\$0.lnk\" 0 +2\r\n CreateShortCut \"$APPDATA\\Microsoft\\Internet Explorer" + + "\\Quick Launch\\User Pinned\\TaskBar\\$0.lnk\" \"$INSTDIR\\app-${VERSION}\\${EXECUTABLE}" + + "\"\r\n \r\n # Registry information for add/remove programs\r\n WriteRegStr HKC" + + "U \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"DisplayName\"" + + " \"${APPNAME}\"\r\n WriteRegStr HKCU \"Software\\Microsoft\\Windows\\CurrentVersion\\U" + + "ninstall\\${APPNAME}\" \"UninstallString\" \"$\\\"$INSTDIR\\uninstall.exe$\\\"\"\r\n Write" + + "RegStr HKCU \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"Qu" + + "ietUninstallString\" \"$\\\"$INSTDIR\\uninstall.exe$\\\" /S\"\r\n WriteRegStr HKCU \"Sof" + + "tware\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"InstallLocation\" \"" + + "$\\\"$INSTDIR$\\\"\"\r\n WriteRegStr HKCU \"Software\\Microsoft\\Windows\\CurrentVersion" + + "\\Uninstall\\${APPNAME}\" \"DisplayIcon\" \"$\\\"$INSTDIR\\app-${VERSION}\\${EXECUTABLE}$\\" + + "\"\"\r\n WriteRegStr HKCU \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${" + + "APPNAME}\" \"Publisher\" \"${COMPANYNAME}\"\r\n ; WriteRegStr HKCU \"Software\\Microso" + + "ft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"HelpLink\" \"${HELPURL}\"\r\n Wri" + + "teRegStr HKCU \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"" + + "DisplayVersion\" \"${VERSION}\"\r\n # There is no option for modifying or repairin" + + "g the install\r\n WriteRegDWORD HKCU \"Software\\Microsoft\\Windows\\CurrentVersion" + + "\\Uninstall\\${APPNAME}\" \"NoModify\" 1\r\n WriteRegDWORD HKCU \"Software\\Microsoft\\" + + "Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"NoRepair\" 1\r\n # Set the INSTALL" + + "SIZE constant (!defined at the top of this script) so Add/Remove Programs can ac" + + "curately report the size\r\n WriteRegDWORD HKCU \"Software\\Microsoft\\Windows\\Cur" + + "rentVersion\\Uninstall\\${APPNAME}\" \"EstimatedSize\" ${INSTALLSIZE}\r\nSectionEnd ; e" + + "nd the section\r\n\r\nFunction .onInstSuccess\r\n IfSilent +2\r\n Exec \'\"$INSTDIR\\" + + "${EXECUTABLE}\"\'\r\nFunctionEnd\r\n\r\nSection \"uninstall\"\r\n DetailPrint \'Please wai" + + "t...\'\r\n SetShellVarContext current\r\n SetDetailsPrint None\r\n SetAutoClos" + + "e true\r\n \r\n # Remove Start Menu launcher\r\n Delete \"$SMPROGRAMS\\${COMPAN" + + "YNAME}\\${APPNAME}.lnk\"\r\n Delete \"$DESKTOP\\${APPNAME}.lnk\"\r\n StrCpy $0 ${EX" + + "ECUTABLE} -4\r\n Delete \"$APPDATA\\Microsoft\\Internet Explorer\\Quick Launch\\User" + + " Pinned\\TaskBar\\${APPNAME}.lnk\"\r\n Delete \"$APPDATA\\Microsoft\\Internet Explore" + + "r\\Quick Launch\\User Pinned\\TaskBar\\$0.lnk\"\r\n # Try to remove the Start Menu f" + + "older - this will only happen if it is empty\r\n RMDir \"$SMPROGRAMS\\${COMPANYNA" + + "ME}\"\r\n\r\n # Always delete uninstaller as the last action\r\n delete $INSTDIR\\" + + "uninstall.exe\r\n\r\n # Try to remove the install directory - this will only happ" + + "en if it is empty\r\n RMDir /r /REBOOTOK $INSTDIR\r\n\r\n # Remove uninstaller i" + + "nformation from the registry\r\n DeleteRegKey HKCU \"Software\\Microsoft\\Windows\\" + + "CurrentVersion\\Uninstall\\${APPNAME}\"\r\nsectionEnd"); + return this.GenerationEnvironment.ToString(); + } + } + + #line default + #line hidden + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "15.0.0.0")] + public class SetupTmplBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + protected System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/Creator/SetupTmpl.tt b/Creator/SetupTmpl.tt new file mode 100644 index 0000000..a78a8f0 --- /dev/null +++ b/Creator/SetupTmpl.tt @@ -0,0 +1,121 @@ +<#@ template language="C#" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +Unicode true +ManifestDPIAware true +!define APPNAME "<#= AppName #>" +!define EXECUTABLE "<#= Executable #>" +!define SOURCEPATH "<#= SourcePath #>" +!define COMPANYNAME "<#= CompanyName #>" +!define DESCRIPTION "<#= Description #>" +!define VERSION <#= Version.ToString() #> +!define HELPURL <#= HelpUrl #> +!define INSTALLSIZE <#= InstallSize #> +!define ISMANAGED <#= IsManaged ? 1 : 0 #> +; exampleCmd: makensis.exe /DVERSION=1.0.0.5 /DNAME=WpfApp1 Setup.nsi + +Name "${APPNAME}" +OutFile "<#= OutFile #>" +InstallDir "$LOCALAPPDATA\${APPNAME}" +RequestExecutionLevel user +SetCompressor /SOLID lzma +SilentUnInstall silent +; Subcaption 3 " " +XPStyle on +AutoCloseWindow true +ChangeUI all "<#= UserInterface #>" +Icon "<#= IconPath #>" +UninstallIcon "<#= UninstallIcon #>" +ShowInstDetails nevershow +ShowUninstDetails nevershow +BrandingText "${COMPANYNAME}" + +VIProductVersion <#= LegacyVersion.ToString(4) #> +VIAddVersionKey ProductName "${APPNAME}" +VIAddVersionKey Comments "" +VIAddVersionKey CompanyName "${COMPANYNAME}" +VIAddVersionKey LegalCopyright "${COMPANYNAME}" +VIAddVersionKey FileDescription "${DESCRIPTION}" +VIAddVersionKey FileVersion "${VERSION}" +VIAddVersionKey ProductVersion "${VERSION}" +VIAddVersionKey OriginalFilename "<#= OutFile #>" + +;-------------------------------- +; The stuff to install +Section "install" ;No components page, name is not important + DetailPrint 'Installing ${APPNAME}. Please wait...' + SetShellVarContext current + SetDetailsPrint None + IfSilent +2 + RMDir /r $INSTDIR + SetOutPath $INSTDIR + + ; Create sub-directories +<# foreach(var directory in Directories) { #><#= $" CreateDirectory \"$INSTDIR\\{directory}\"{Environment.NewLine}" #><# } #> + + ; Put file there +<#foreach(var file in Files) { #><#= $" File \"/oname={file.TargetPath}\" \"{file.SourcePath}\"{Environment.NewLine}" #><# } #> + + WriteUninstaller "$INSTDIR\uninstall.exe" + ; Desktop + CreateShortCut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${EXECUTABLE}" + + # Start Menu + CreateDirectory "$SMPROGRAMS\${COMPANYNAME}" + CreateShortCut "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" "$INSTDIR\${EXECUTABLE}" + + # Update pinned Taskbar + IfFileExists "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\${APPNAME}.lnk" 0 +2 + CreateShortCut "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\${APPNAME}.lnk" "$INSTDIR\${EXECUTABLE}" + SetOutPath $INSTDIR\app-${VERSION} + StrCpy $0 ${EXECUTABLE} -4 + IfFileExists "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$0.lnk" 0 +2 + CreateShortCut "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$0.lnk" "$INSTDIR\app-${VERSION}\${EXECUTABLE}" + + # Registry information for add/remove programs + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "InstallLocation" "$\"$INSTDIR$\"" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$\"$INSTDIR\app-${VERSION}\${EXECUTABLE}$\"" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "Publisher" "${COMPANYNAME}" + ; WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "HelpLink" "${HELPURL}" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${VERSION}" + # There is no option for modifying or repairing the install + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "NoModify" 1 + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "NoRepair" 1 + # Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size + WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "EstimatedSize" ${INSTALLSIZE} +SectionEnd ; end the section + +Function .onInstSuccess + IfSilent +2 + Exec '"$INSTDIR\${EXECUTABLE}"' +FunctionEnd + +Section "uninstall" + DetailPrint 'Please wait...' + SetShellVarContext current + SetDetailsPrint None + SetAutoClose true + + # Remove Start Menu launcher + Delete "$SMPROGRAMS\${COMPANYNAME}\${APPNAME}.lnk" + Delete "$DESKTOP\${APPNAME}.lnk" + StrCpy $0 ${EXECUTABLE} -4 + Delete "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\${APPNAME}.lnk" + Delete "$APPDATA\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\$0.lnk" + # Try to remove the Start Menu folder - this will only happen if it is empty + RMDir "$SMPROGRAMS\${COMPANYNAME}" + + # Always delete uninstaller as the last action + delete $INSTDIR\uninstall.exe + + # Try to remove the install directory - this will only happen if it is empty + RMDir /r /REBOOTOK $INSTDIR + + # Remove uninstaller information from the registry + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" +sectionEnd \ No newline at end of file diff --git a/Creator/SetupTmplCode.cs b/Creator/SetupTmplCode.cs new file mode 100644 index 0000000..a72118b --- /dev/null +++ b/Creator/SetupTmplCode.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Katteker; + +namespace KattekerCreator +{ + public partial class SetupTmpl + { + public string AppName + { + get => string.IsNullOrWhiteSpace(ReleaseChannel) ? _appName : $"{_appName}_{ReleaseChannel}"; + set => _appName = value; + } + + public string Executable { get; set; } + public string CompanyName { get; set; } + public string Description { get; set; } + public SemanticVersion Version { get; set; } + public Version LegacyVersion => Version.ToSystemVersion(); + public string HelpUrl { get; set; } + public long InstallSize { get; set; } + public bool IsManaged { get; set; } + public string IconPath { get; set; } + public string SourcePath { get; set; } = string.Empty; + public List Files { get; set; } = new List(); + private IEnumerable Directories => DirectoriesToCreate(); + public string UserInterface { get; set; } + public string OutFile { get; set; } + public string UninstallIcon { get; set; } + public string ReleaseChannel { get; set; } + + private readonly List _directoriesList = new List(); + private string _appName; + + private IEnumerable DirectoriesToCreate() + { + foreach (var physicalFile in Files) + { + var dirSplit = new DirSplit(physicalFile.TargetPath); + if (dirSplit.SplitCount > 1 && !_directoriesList.Contains(dirSplit)) + { + _directoriesList.Add(dirSplit); + } + } + + return _directoriesList.Select(x=> x.ToString()); + } + + private class DirSplit + { + private const char SplitChar = '\\'; + + private readonly string[] _splits; + + public int SplitCount => _splits.Length; + + public DirSplit(string path) + { + _splits = path.Split(SplitChar); + } + + public override string ToString() + { + return string.Join(SplitChar.ToString(), _splits.Take(_splits.Length - 1)); + } + + public override bool Equals(object obj) + { + return string.Equals(ToString(), obj?.ToString()); + } + + protected bool Equals(DirSplit other) + { + return Equals(_splits, other._splits); + } + + public override int GetHashCode() + { + return (_splits != null ? _splits.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/Creator/contrib/LoadingBar_Icon.exe b/Creator/contrib/LoadingBar_Icon.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1f3d3f547370485690d0ca7985cf4946f623d3c GIT binary patch literal 6144 zcmeHLeQaA-75_aaZeEh6ai@4qS=nAk1x2(`Sz1xikG&@L>IkRv;(U|=E1vxtJGl1r z{IaH~liV*b@d9(m%WpQ2LGO>WE>RVS=DPD8mt84JX_BS_P z){<{-9L*Oh#uIjV(#{+)vYAq;>==(*hCN*}3MC`hf40N!pVF`e#q7h)+Y2G#wxEVgZ&4f0+uG*y<`5uyPR5wgqI?Lv4Y?Hoq zrv`j*?Vh<0u6`;k%-16ro4tPOdSo`fdVFhy((cpmaY~8qGeseO6ptjF4T*%)+kbQ1 zoz~sxPTCuHquYrVhN#j%clgv(5v}{hxo2Z=wXi{}$2whw7dBJj)E;4l?M}B#8Vfz- zU97Uze{y%6%ulo%B?TPizrF^NCGDggjy@taN7g$O!B<6;|H*LiWk8 zWfanGG(q`Pn8O|VO8?wrKjm_aPqnzh3wQzbm+0$CLQlM@;{CMufeJedGGz+zA$={tyNmq{(sKdccJg%6mjk@L;!}VowFn=F->)D( zQ*cstIcrraII8WpoZ)hAx@aXbrCib4#{({uEN61b!sB+vK1TXW@sR`ZWd8$wxndFc zVkm8ua>MMLnH23aq@9UEDOWx^%G;To7eeWL`KZSNPS)kAa)sAyD4n$}t2A03EEcR1 zuX9Iy@YF}Ftb?^e$BL6fj&%eW!=P4M{XhAW7da$YB#hu0V=&-ez$((#enj6Um+Fknwt@^256St=e#52cMKExS@E zmqc07V-?FbhDwfQAI@YgW2-UBY%G+-zI?mKbG6)3=I>OuRRlDI=})hJC)8bQhvdl< z=+*o*w{W0sXfIxo-k=gzr?wXAvR~0UHAJ zmi@pRi&EeQY59h&!5fLC;~gL3VskL1>+lD55S<{j>KAFFm!lW(F6ARAAj>i1 zd?^>;-~m1`_xT-``|bI;%x`UFzpFTr((!rxn2*-KDW6URU1D2H{fy&&fsfS%C9f`# zleOZ!`5dOsH_@)qOFuS2BQ4NO9sBVlAH_EsjN$b!tNyN%FO6G0uJ<>LYa7Sc@M-AU zz4&{;{_+@7kMQruumu}U8|!h7yZ z&H>rK%->zOb&S?GjL|ygfsN1LJ9rd2*C>r+sPLZ@n;CCmocU+rDEgU$9t2}IucOL; zjR^D1e-A$DzG+3slpY=0-B3@?WqSf>F~5iBIPMyWBlk>I)B~WUe(TFMavRCpe0ivW zJi@rQ;c0Yg-i(&-Ne!WcyYPaFyF65D^5FdC+>rY~=HH_*ztI(5dH?YWNcHj*dIg=h z&Iul2)U({{MdCxeNo=m@q*{dvIraGa!^rf{I7c_xcCFhl)zbhg&BzAh^%Sw}!to70Ea6lOg%~NY$7YMq#bZ=OMaEPiy zQ6%x^zrk{<;v9A4K_%w<8fZM0vkysDMM@Uj*#^*GHyRDh4$v5iRB}qTx3@u0$hAu3 z&mt&`*8g}?R|HrpP{?f1<_?se|zZ1E2EAY>V z+_}oK=U=-Za-|cw#x`+5E!sVS??q`3%p=*`BYn8wObL_Ip{<5@q!`3utJ%Xj3`r8{!v%3T?c?_$jEEp@+gz-2hI zsK4xM#yhXLI@_7XH9Uc(Qb$p62X@1f;*J!co z8a`?!qY0lP=r^8M-E_h#JeCCFqaA>|Zsb}myJqC@a6`gl3jE2WshVaR@-55t6x4Ol z;~T8p1fUjAyB)93aDfk;Hf-`$H=F=ncSAlFRlh^IY9`(uJWz+McU|Czj-egEXg~VJ zC#l)nO_7HU!Cvrp2RqF!9?*)f(A8$xHAucabhYamm=oHgcN{>$?rMnc`;X%FK~0Er zK_4F2{?C^^FY;w^sv8W2!&4msG+=-dnx;Y8p$heCEWdWWE(}LEMXxOp<612}6S@6V zwLLz(bbWmIhd+%E`M33Ce7JGoFXs72-Z$Tj4=-L!awebAqmT|nHtagu z9~>PWY#edj)#AE^i|fEe>GI{fx}M;=f@_K|zIY_>y?0+e`sjhY|NedX;Df*G8e?bY zo_zfA1Nr2W2N=|Qy7pM4cgS7X>2qAX4;Qz1C)HvKIe_>v0uk?BN*A&e^{sE3=OAxlCi0 zzQ^^7*W+lwuQ0i&ZiX#d3<;|?to0Z`LTCr8){wALvx2j zMw5xvPjtDc%S5jH5M&-*cR-GczBWU0l%=TiuQ+AwLvI~cnm7^RIY>fI0O*W z1lwE-Q=kFNWIy~fauqr>X<>1)AO3-Z&>@UsX+Iic1;zy5(Pr2))J9pRw~5Sw@fe zKx6;nwO29E{V=D+_qbkUKg_-FWO7eF|NN2ei9h`CfxP?fJzV#7ugv{0~ves7s!kzD|{&1f#rvjaEnq{mFMO=b?t@;KQo zQ*T$3z@S9i7Bc~6bN@B`=K0u_!MP9#fN}He;m`yD_Av^~!TXU^0q+tM=*Qc@L%=*3 z1p4tYguwF^pRgrovZ7I6n+GL4$A>27;%5a6K2LuPT9n%7>n{YN_C@&mob@T6vsPy` z^cN(0CJr2VhBF%R89Wy-oKeFU@JzsPMgx8Zo&*eM)Z^p(4`LWsr8_YV|U+y+^L&&SsW=SWBR0(`t3F?43-g7Hd_B4__lQdAkvXu<3$7|v)9q^L5S(Sq4iFx+cN=SIZxj)dqLJMgCC z(;#F)Jm1%2KQIZaxfs-24UWxE?!)8S*ZWksm3v2ecM+Z0pW1$P+5Xh{i}tCv(DWXz zR~`>V6MRPjA4H1~TH5h}J~SQD!0jL|*)Z;i#E|ExH6E$Hy}$q$Ac&PB%$XvB-(KGh5fi+qC>YWfn8lnw$&lU3MH@t_yP=hvL zOX(m4kkvKXK4MLXK^6All?b|qnrS)wgmP=vi&T)|%LoikBCJ@UkcFYK2xl}5;XqLB z7nSAxt7tFvA=b#KB-#ssQxuRYxmsm{*JsqnF9yi15w$5B9@i6DPQk=PNT%8Afb?#!`tw6?}@G2L+Hts6B7LrhfrZ z1p~})wwH}BDTd?wg$&A&iEI` zr|t`e5%V>Z=l$bf?ir|(ih!sN z@5K1_l#TfWds@Y*UoZ`uQLdlUwqrCN_xMBxJGhe@;4M{f{?u*P2q~4 zQE*K7Wc=(EDJt?Q<5&K5sq#DLn@mnKzP!=|@A7%k;2yv*(32PjUSWm|bl~AXICzSg z8GRV|dSC!K-ZV(bjiCpF;4;oIq;v=p_mEwiJ(4fKd@SF6_mh0~*+V@W z`Rc33a^uEheK+v>^~ZRh(7#i7$LX3+5@kp2Y&yK1-$bxLd%G1Fy$Wi%7X zvlo}i86W=^*O!dFoc2j$HNSFN%lCO;W%t#6(xv||yj`5%4c@+xYhNmle~!!#02zkM z6)cmdc&5Nd@iGNVP+;Pjg2nQ`No+VViOX-zhY679j9X{_&9I$` zHxZCarF>+k6Q{%!$xfg0>Xc9Nxdp~gtsg(!@R&^gxPn}Kh50wDpb%e0zEn_(uOwfG zs0#Zw%DaZV`J|*JC>yvq6Ae+yS;@Fc3-MV6CQ;H{KD;KF0LAT`s`FeXe@wizAeTz{ zB;O-0b_z{YlF4&XJ`+Em-wP3tOQpQA?NAyVyqMpoAj#x8lTQz5J^qWwx9JYbrBdGH zWB0;ghgp#Riy+D5xhNkL=u7IX^a_%AY#^5|e!t1cRufc^i7%H2BAm;6qfQm#E6CR~ zM^@noHgvJH+e(4C0x^k_t{}K>Up-Lz%}Q(kt?B{uSV>ovp7;A#YJ0Ov$If+^IU~L) z;`_OlU*@nG{;%x6Vz$lM#a0DqQK2}4H^WyKN5syA5`7-=9tHEn87^GMi5YL+`oD}5 F{|9Uu2*>~c literal 0 HcmV?d00001 diff --git a/Creator/packages.config b/Creator/packages.config new file mode 100644 index 0000000..b0cfae7 --- /dev/null +++ b/Creator/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Creator/reference.nuspec b/Creator/reference.nuspec new file mode 100644 index 0000000..769d6cb --- /dev/null +++ b/Creator/reference.nuspec @@ -0,0 +1,19 @@ + + + + $id$ + $version$ + $id$ + $author$ + $author$ + false + $description$ + $copyright$ + + + + + + + + \ No newline at end of file diff --git a/Example/App.config b/Example/App.config new file mode 100644 index 0000000..d1428ad --- /dev/null +++ b/Example/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Example/App.xaml b/Example/App.xaml new file mode 100644 index 0000000..bdff48f --- /dev/null +++ b/Example/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Example/App.xaml.cs b/Example/App.xaml.cs new file mode 100644 index 0000000..845426b --- /dev/null +++ b/Example/App.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace Example +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Example/Example.csproj b/Example/Example.csproj new file mode 100644 index 0000000..1013e13 --- /dev/null +++ b/Example/Example.csproj @@ -0,0 +1,120 @@ + + + + + Debug + AnyCPU + {DE9CFDEB-40B8-447D-9959-31BA78512CED} + WinExe + Example + Example + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + + x64 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + x64 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + nsis3-install.ico + + + Example.App + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + PreserveNewest + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {07e2de31-80a0-43da-b307-1ca47cd930a1} + Katteker.Gui + + + {a45e1c59-ba9e-452c-a5e2-50de49d53e92} + Katteker + + + + + + + \ No newline at end of file diff --git a/Example/MainWindow.xaml b/Example/MainWindow.xaml new file mode 100644 index 0000000..927a3c1 --- /dev/null +++ b/Example/MainWindow.xaml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/Example/MainWindow.xaml.cs b/Example/MainWindow.xaml.cs new file mode 100644 index 0000000..2f29a4e --- /dev/null +++ b/Example/MainWindow.xaml.cs @@ -0,0 +1,32 @@ +using System; +using System.Reflection; +using System.Windows; +using Katteker.Gui; + +namespace Example +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow + { + public MainWindow() + { + InitializeComponent(); + var version = GetType().Assembly.GetName().Version.ToString(); + var a = Assembly.GetExecutingAssembly(); + a.ManifestModule.GetPEKind(out var peKind, out var machine); + Title = $"{version} | {peKind} | {machine}"; + } + + private async void MainWindow_OnContentRendered(object sender, EventArgs e) + { + await Wrapper.CheckForUpdateAsync(true).ConfigureAwait(false); + } + + private async void ButtonBase_OnClick(object sender, RoutedEventArgs e) + { + await Wrapper.CheckForUpdateAsync().ConfigureAwait(false); + } + } +} diff --git a/Example/Properties/AssemblyInfo.cs b/Example/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2ed501b --- /dev/null +++ b/Example/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Example")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Enercon GmbH")] +[assembly: AssemblyProduct("Example")] +[assembly: AssemblyCopyright("Copyright © Enercon GmbH 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.36.0")] +[assembly: AssemblyFileVersion("1.0.36.0")] diff --git a/Example/Properties/Resources.Designer.cs b/Example/Properties/Resources.Designer.cs new file mode 100644 index 0000000..f8266ff --- /dev/null +++ b/Example/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Example.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Example.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Example/Properties/Resources.resx b/Example/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Example/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Example/Properties/Settings.Designer.cs b/Example/Properties/Settings.Designer.cs new file mode 100644 index 0000000..8dbb4db --- /dev/null +++ b/Example/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Example.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.6.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Example/Properties/Settings.settings b/Example/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/Example/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Example/changelog.md b/Example/changelog.md new file mode 100644 index 0000000..028a658 --- /dev/null +++ b/Example/changelog.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0 + +- Hello World \ No newline at end of file diff --git a/Example/nsis3-install.ico b/Example/nsis3-install.ico new file mode 100644 index 0000000000000000000000000000000000000000..9d9409cf755210cbc2e2ff062365991eda9ef1fd GIT binary patch literal 11697 zcmeI13s@7!+Q;8rRtQQV5tX2U1ZWi%G~Tsd5;aJ*)=O*E;)Qr?QHva@6>SlgDjEd@ z+j^^2W34UqM%7ZPAf&dUN3Bp>wF-&~RumB;UbqDIduKNZ31UC{oadbLeb3{_?#|5r z_r5dp&LlG%0Vr?;g+h!^21;pww*f#b7Rvqkl7EGs*-C;)-2Tm6KkaFakSJU$26Lz<46A(5neq4{QI({|i`-LArSe;PvVz05= zPFF;peH@+lxGrYj#bb4l>kA_u zc*PE5l#Y~ATUzbz%kB`V=gc~>T}-(Qg)XQ1G^`NmyVK)F-XpG-iP@H;h^x{y;NNvH z(OS{^SLUNnLl;K9`Vnz3N5p;lDfWT4(tDyy7G|HjjIg)^by9yGn^kn9m9Lkfm3lOzWxA8hAV#Z*AejJ03uz=H zWAvRWNXCWHukyzX8_Vc-hLKDJqhFp%GNFt%zLaFfGFs={*fx^NRS;mPzU4<}i`fBE zE>dG~qm|2zQNx*N1*}pw#OTy@BE#^+SgCJ*Us{nE8{nJ2zmn3LmWt#0#=AmsUV`Ym zPItHe-f(v1)SSWVHx?avvu=E0&AF=^rfx3WQ!g#mhI9AN*QLv8wbu;J@!O1}-M)_M zJ5ROJvqC=lP(uu7s@%CZM}A7wfz5nlojiK!*t1&;ch~#h8+~u|%~q{J!LJ<6qBhU!MdK_B`b zw@uDVulE}0>wEwA1AS_4PZ}$sf}$7cKmRG}L(yYu)8if4?{&)1u|0iLLf5*@%5*gx zbnvBXb?PC9hpajt4o75SNq#Vsd%DK?-EI!~w^$uJ#8)}>Uf%TgJy;(n4f8+}*Dxyj z&XGNkI6&d6PZlkjbf;`$A)_y&!Xx$ung)=%y%X;zT$sw}^QdL-zPAuGI`s=0?SXTl z2QEj$D4~UIUm9=TdCf^*LP@A)nrl7wUECek7uxeJP**&5&+Q4HkwVZ~_R*dReLUFq z-HU^;n5a(5yfOjiWv`S=@;y4!>Gu}*qQ0%9w`tU&D@4C%6}-k6j^QfO=e@_wD+;fo zQkSW##MClxKkfAeTU^O^%Xd>@*V&as4AQU9oE8ZCWcgBTGhX!FqKrL^ewRc01X1Z8 z&$tWszW)G12I^%xHoLtf%(!5WqyD(Vsd!Q8Z;9$XQB%;;d)yeb9KnQS7j&V*YS>6W zx^~nE{lm;@gJ7S0oHNE?wP?u?1wjtiuCt?XD3K!!r>}Ht2i=_}w3g&=^rN@kTf7dI zysSTmjr5RIF3vdQWGJBKD(Kpk8bk4wv+W_-=}T;~%$-V$8aM^Ayi+>jFkfW`6_p22 zmC@?UXgEhw8Iuw0WT>OgtC8f zvpD0k+Ohj~Ped^L&xt=4F^j^2S>wfDr}CZNhfRF&nG5Ok>o3!ZIs4ysPz+&ong@+B{{q9G<1E=x>@a#b4MkH+Ek>QAxFKo+qIszMd?H*0MEW&YRoSwsv zYpJBVc!#LaBrnc56VBIM5@kKeO$t^$u2&V#G_4H35W}8(o#_@GS@&!5YxhQX9kOoh z_JS*)%7;95UV1yJL(ju?)zQDHLI-7bEvhxW@%hmn^XoRIT}`VO!<~QBcLerkVUDvh zudBzl4fR`-E+=0AkA#%0&8#qIHtm|dk)_Zv=j<|!hBk4Ag=0NKjC z7_FmZQ}>I6cc3;c7s=Lth}&^$G6bj@Z-26X{~iPzbl!325LXYzEihG*-yV0~I|}g9 zB(P-9)+fWFHZRfXx#)Zvqj##1q>|0O)CUGHO=8#M9;|kvq=qTXUb6Y$l(2DG8p9nl z9M_JL?szDgEJuH*e84ZgecVKJ64z6tcH9@FPba&#icT5XRTB0-_UdE;%$_}%-beQP z8nW@@*AjY3aiaVUAxsu=UMyjU?bR5__?51df9hf;i_kww*kkj86yy-0bfL!Sz2eB$ zu8ze+$3*4}5+I!Hpk03AW!OyVT0EV!rN|Cnx)LU&48)Jz5Z`axQ>iabV#qeH9YBx! z^$%R%3v`B=jQ-#b@^Kc&)reSX8Lrn=KJ-{T7?Cw9UnP)kS&64*k_n4KA5I4|fW!xzr~&baIJE%#Kht9QLT@$E2|V`_Qt@&(*W$bw*2Zu6#;Yi2_1yg1vkD>K=H1QQ1X>Ol%xj2&E37==APd0=iYvBD@_fz4h(}^ z2ZNyW;0P%F?p3(`{W!S&!vwgKITgx|&Vai)Z^PXm=Rw7(XsG-p9*ne3TXTk!|f-MEboDF5DWy~25*ihxY>Z`ZSiJ!0T&DiZ-+n0TX15aK;9mIf^Wppz{!(pf;Z)D zaWufiNEU%jYO)?$*d+(#?J8QduwTw?z3dY4?_pQtT$O|~% zANv*rUcejsW5zAKO$$8l=p)3;;@^T>d9I};tb+emf`OU0;#S^+qRa;7{tE^I--ugy z#4QG9p5hI7-Uc`GJZ?6y@Vo)xZE=A&G$wkLBNH-0^GvafLk;kxFr*T`(U#9Jyr(AU%m(>yE;M1*Imr-u|4n|b^yM=YM}Jc zNdDcmG<^)*Ir0{iWlaZ;)dHuR58T>F;69E5ZsRh1mz~4E%a;HA9=^}U0QXrOaC#kZ zsUHKkZ!>TQwgGoY58UCefy>?xm1kFiF+UOR|GploE^g)DdC7b3qvFHBB=w{hYcj?V>1VG&)+v-H-fzE+e*+v&+0?50No(rjQeExyvSmW#5PHsu=#P<`)tCu--eL@l{$S^Tk z+J2Jafv3xSp#Dlm;ICzzizi$C-)jEb--6_vBF0OBm-Y8_^Y7Ix zZs5-1`Qh3hz!et)_h%7a*MKX%0p#4kRg?i&RSDd~Dj??voLb;&tNAD2FPqy`)c(IG zGQQ{Oc?AA1Mu4nM(X%9lLLtISVP%D~$fg2y@@Gz%;VmphqS!gGfaEEVR^H;<>d&GR TviW4&HJ*4H_@w#2IxqhNqhBe> literal 0 HcmV?d00001 diff --git a/Katteker.Gui/ChangelogHelper.cs b/Katteker.Gui/ChangelogHelper.cs new file mode 100644 index 0000000..2adff46 --- /dev/null +++ b/Katteker.Gui/ChangelogHelper.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; + +namespace Katteker.Gui +{ + internal static class ChangelogHelper + { + private static string GenerateHtmlifyChangelog(string text, string extension) + { + string result; + switch (extension) + { + case ".txt": + var plainText = WebUtility.HtmlEncode(text); + result = plainText.Replace(Environment.NewLine, "
"); + break; + case ".md": + result = CommonMark.CommonMarkConverter.Convert(text); + break; + default: + result = text; + break; + } + + return result; + } + + internal static async Task LoadChangelogAsync(string filename, string path) + { + if (!string.IsNullOrEmpty(filename) || !string.IsNullOrEmpty(path)) + { + if (!path.EndsWith("/", StringComparison.Ordinal)) + path += "/"; + var webReq = WebRequest.Create(path + filename); + try + { + using (var response = await webReq.GetResponseAsync().ConfigureAwait(false)) + using (var sr = new StreamReader(response.GetResponseStream())) + { + return GenerateHtmlifyChangelog(await sr.ReadToEndAsync().ConfigureAwait(false), + Path.GetExtension(filename)); + } + } + catch (WebException) + { + var changelogFilename = Path.GetFileName(filename); + if (changelogFilename == null) return GenerateHtmlifyChangelog("Changelog not found", ".txt"); + var currentChangelogPath = Path.Combine(Environment.CurrentDirectory, changelogFilename); + if (File.Exists(currentChangelogPath)) + { + return GenerateHtmlifyChangelog(File.ReadAllText(currentChangelogPath), Path.GetExtension(filename)); + } + } + } + + return GenerateHtmlifyChangelog("Changelog not found", ".txt"); + } + } +} \ No newline at end of file diff --git a/Katteker.Gui/Katteker.Gui.csproj b/Katteker.Gui/Katteker.Gui.csproj new file mode 100644 index 0000000..943f9da --- /dev/null +++ b/Katteker.Gui/Katteker.Gui.csproj @@ -0,0 +1,95 @@ + + + + + Debug + AnyCPU + {07E2DE31-80A0-43DA-B307-1CA47CD930A1} + Library + Properties + Katteker.Gui + Katteker.Gui + v4.5 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\Katteker.Gui.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Katteker.Gui.xml + + + + ..\packages\CommonMark.NET.0.15.1\lib\net45\CommonMark.dll + + + + + + + + + + + + + + True + True + Resources.resx + + + Form + + + UpdateWindow.cs + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + UpdateWindow.cs + + + + + {a45e1c59-ba9e-452c-a5e2-50de49d53e92} + Katteker + + + + + + + + + \ No newline at end of file diff --git a/Katteker.Gui/Katteker.Gui.nuspec b/Katteker.Gui/Katteker.Gui.nuspec new file mode 100644 index 0000000..249af5b --- /dev/null +++ b/Katteker.Gui/Katteker.Gui.nuspec @@ -0,0 +1,18 @@ + + + + $id$ + $version$ + $id$ + $author$ + $author$ + false + $description$ + Copyright 2017 + + + + + + + \ No newline at end of file diff --git a/Katteker.Gui/Properties/AssemblyInfo.cs b/Katteker.Gui/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6c30bf7 --- /dev/null +++ b/Katteker.Gui/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("Katteker.Gui")] +[assembly: AssemblyDescription("Helps Applications to integrate Katteker")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("WRD GmbH")] +[assembly: AssemblyProduct("Katteker.Gui")] +[assembly: AssemblyCopyright("Copyright © Enercon GmbH 2015-2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("4877208f-00bf-42b7-a56b-bf8bf4a0d3c8")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersion("1.0.0")] diff --git a/Katteker.Gui/Properties/Resources.Designer.cs b/Katteker.Gui/Properties/Resources.Designer.cs new file mode 100644 index 0000000..22775ea --- /dev/null +++ b/Katteker.Gui/Properties/Resources.Designer.cs @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Katteker.Gui.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Katteker.Gui.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Installed new Version! Would you like to restart the Application?. + /// + internal static string Installed_new_Version { + get { + return ResourceManager.GetString("Installed_new_Version", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New Version. + /// + internal static string New_Version { + get { + return ResourceManager.GetString("New_Version", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No update available.. + /// + internal static string No_update_available { + get { + return ResourceManager.GetString("No_update_available", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't find configuration File. Ask your local distributor.. + /// + internal static string SquirrelWrapper_CheckForUpdate { + get { + return ResourceManager.GetString("SquirrelWrapper_CheckForUpdate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ask your local distributor.. + /// + internal static string SquirrelWrapper_CheckForUpdate_AskYourLocalDistributor { + get { + return ResourceManager.GetString("SquirrelWrapper_CheckForUpdate_AskYourLocalDistributor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error with Configuration-File. + /// + internal static string SquirrelWrapper_CheckForUpdate_Error { + get { + return ResourceManager.GetString("SquirrelWrapper_CheckForUpdate_Error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error with Update. + /// + internal static string SquirrelWrapper_CheckForUpdate_ErrorWithUpdate { + get { + return ResourceManager.GetString("SquirrelWrapper_CheckForUpdate_ErrorWithUpdate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You can update to Version: . + /// + internal static string You_can_update_to_Version { + get { + return ResourceManager.GetString("You_can_update_to_Version", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You're up to date.. + /// + internal static string You_re_up_to_date { + get { + return ResourceManager.GetString("You_re_up_to_date", resourceCulture); + } + } + } +} diff --git a/Katteker.Gui/Properties/Resources.resx b/Katteker.Gui/Properties/Resources.resx new file mode 100644 index 0000000..5451a9a --- /dev/null +++ b/Katteker.Gui/Properties/Resources.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Installed new Version! Would you like to restart the Application? + + + New Version + + + You're up to date. + + + You can update to Version: + + + No update available. + + + Can't find configuration File. Ask your local distributor. + + + Error with Configuration-File + + + Error with Update + + + Ask your local distributor. + + \ No newline at end of file diff --git a/Katteker.Gui/UpdateWindow.Designer.cs b/Katteker.Gui/UpdateWindow.Designer.cs new file mode 100644 index 0000000..3325b58 --- /dev/null +++ b/Katteker.Gui/UpdateWindow.Designer.cs @@ -0,0 +1,314 @@ +using System; + +namespace Katteker.Gui +{ + /// + /// Shows the Update Window. + /// + sealed partial class UpdateWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + [STAThread] + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdateWindow)); + this.mainSplitContainer = new System.Windows.Forms.SplitContainer(); + this.titleLayoutPanel = new System.Windows.Forms.TableLayoutPanel(); + this.closeWindowBtn = new System.Windows.Forms.Button(); + this.maximizeBtn = new System.Windows.Forms.Button(); + this.titlebar = new System.Windows.Forms.Label(); + this.minimizeBtn = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.splitContainer3 = new System.Windows.Forms.SplitContainer(); + this.changelogBrowser = new System.Windows.Forms.WebBrowser(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.closeBtn = new System.Windows.Forms.Button(); + this.updBtn = new System.Windows.Forms.Button(); + this.panel1 = new System.Windows.Forms.Panel(); + ((System.ComponentModel.ISupportInitialize)(this.mainSplitContainer)).BeginInit(); + this.mainSplitContainer.Panel1.SuspendLayout(); + this.mainSplitContainer.Panel2.SuspendLayout(); + this.mainSplitContainer.SuspendLayout(); + this.titleLayoutPanel.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); + this.splitContainer3.Panel1.SuspendLayout(); + this.splitContainer3.Panel2.SuspendLayout(); + this.splitContainer3.SuspendLayout(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // mainSplitContainer + // + this.mainSplitContainer.BackColor = System.Drawing.SystemColors.Control; + this.mainSplitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.mainSplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; + this.mainSplitContainer.IsSplitterFixed = true; + this.mainSplitContainer.Location = new System.Drawing.Point(1, 1); + this.mainSplitContainer.Name = "mainSplitContainer"; + this.mainSplitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // mainSplitContainer.Panel1 + // + this.mainSplitContainer.Panel1.Controls.Add(this.titleLayoutPanel); + this.mainSplitContainer.Panel1MinSize = 30; + // + // mainSplitContainer.Panel2 + // + this.mainSplitContainer.Panel2.Controls.Add(this.splitContainer3); + this.mainSplitContainer.Size = new System.Drawing.Size(698, 438); + this.mainSplitContainer.SplitterDistance = 30; + this.mainSplitContainer.TabIndex = 7; + this.mainSplitContainer.TabStop = false; + // + // titleLayoutPanel + // + this.titleLayoutPanel.BackColor = System.Drawing.SystemColors.ControlDarkDark; + this.titleLayoutPanel.ColumnCount = 5; + this.titleLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 120F)); + this.titleLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.titleLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.titleLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.titleLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.titleLayoutPanel.Controls.Add(this.closeWindowBtn, 4, 0); + this.titleLayoutPanel.Controls.Add(this.maximizeBtn, 3, 0); + this.titleLayoutPanel.Controls.Add(this.titlebar, 1, 0); + this.titleLayoutPanel.Controls.Add(this.minimizeBtn, 2, 0); + this.titleLayoutPanel.Controls.Add(this.label1, 0, 0); + this.titleLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.titleLayoutPanel.Location = new System.Drawing.Point(0, 0); + this.titleLayoutPanel.Name = "titleLayoutPanel"; + this.titleLayoutPanel.RowCount = 1; + this.titleLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.titleLayoutPanel.Size = new System.Drawing.Size(698, 30); + this.titleLayoutPanel.TabIndex = 0; + this.titleLayoutPanel.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Titlebar_MouseDown); + // + // closeWindowBtn + // + this.closeWindowBtn.Dock = System.Windows.Forms.DockStyle.Fill; + this.closeWindowBtn.FlatAppearance.BorderSize = 0; + this.closeWindowBtn.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(192)))), ((int)(((byte)(192))))); + this.closeWindowBtn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.Red; + this.closeWindowBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.closeWindowBtn.Font = new System.Drawing.Font("Segoe UI Symbol", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.closeWindowBtn.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.closeWindowBtn.Location = new System.Drawing.Point(658, 0); + this.closeWindowBtn.Margin = new System.Windows.Forms.Padding(0); + this.closeWindowBtn.Name = "closeWindowBtn"; + this.closeWindowBtn.Size = new System.Drawing.Size(40, 30); + this.closeWindowBtn.TabIndex = 3; + this.closeWindowBtn.TabStop = false; + this.closeWindowBtn.Text = "╳"; + this.closeWindowBtn.UseVisualStyleBackColor = true; + this.closeWindowBtn.Click += new System.EventHandler(this.CloseBtn_Click); + // + // maximizeBtn + // + this.maximizeBtn.Dock = System.Windows.Forms.DockStyle.Fill; + this.maximizeBtn.FlatAppearance.BorderSize = 0; + this.maximizeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.maximizeBtn.Font = new System.Drawing.Font("Segoe UI Symbol", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.maximizeBtn.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.maximizeBtn.Location = new System.Drawing.Point(618, 0); + this.maximizeBtn.Margin = new System.Windows.Forms.Padding(0); + this.maximizeBtn.Name = "maximizeBtn"; + this.maximizeBtn.Size = new System.Drawing.Size(40, 30); + this.maximizeBtn.TabIndex = 2; + this.maximizeBtn.TabStop = false; + this.maximizeBtn.Text = "□"; + this.maximizeBtn.UseVisualStyleBackColor = true; + this.maximizeBtn.Click += new System.EventHandler(this.MaximizeBtn_Click); + // + // titlebar + // + this.titlebar.Dock = System.Windows.Forms.DockStyle.Fill; + this.titlebar.Font = new System.Drawing.Font("Segoe UI", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.titlebar.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.titlebar.Location = new System.Drawing.Point(123, 0); + this.titlebar.Name = "titlebar"; + this.titlebar.Size = new System.Drawing.Size(452, 30); + this.titlebar.TabIndex = 0; + this.titlebar.Text = "titleBar"; + this.titlebar.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.titlebar.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Titlebar_MouseDown); + // + // minimizeBtn + // + this.minimizeBtn.Dock = System.Windows.Forms.DockStyle.Fill; + this.minimizeBtn.FlatAppearance.BorderSize = 0; + this.minimizeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.minimizeBtn.Font = new System.Drawing.Font("Segoe UI Symbol", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.minimizeBtn.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.minimizeBtn.Location = new System.Drawing.Point(578, 0); + this.minimizeBtn.Margin = new System.Windows.Forms.Padding(0); + this.minimizeBtn.Name = "minimizeBtn"; + this.minimizeBtn.Size = new System.Drawing.Size(40, 30); + this.minimizeBtn.TabIndex = 1; + this.minimizeBtn.TabStop = false; + this.minimizeBtn.Text = "—"; + this.minimizeBtn.UseVisualStyleBackColor = true; + this.minimizeBtn.Click += new System.EventHandler(this.MinimizeBtn_Click); + // + // label1 + // + this.label1.Dock = System.Windows.Forms.DockStyle.Fill; + this.label1.Font = new System.Drawing.Font("Segoe UI Symbol", 18F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label1.ForeColor = System.Drawing.SystemColors.Window; + this.label1.Location = new System.Drawing.Point(3, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(114, 30); + this.label1.TabIndex = 4; + this.label1.Text = "⛽"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // splitContainer3 + // + this.splitContainer3.BackColor = System.Drawing.SystemColors.Control; + this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer3.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer3.IsSplitterFixed = true; + this.splitContainer3.Location = new System.Drawing.Point(0, 0); + this.splitContainer3.Name = "splitContainer3"; + this.splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer3.Panel1 + // + this.splitContainer3.Panel1.Controls.Add(this.changelogBrowser); + // + // splitContainer3.Panel2 + // + this.splitContainer3.Panel2.Controls.Add(this.progressBar1); + this.splitContainer3.Panel2.Controls.Add(this.closeBtn); + this.splitContainer3.Panel2.Controls.Add(this.updBtn); + this.splitContainer3.Size = new System.Drawing.Size(698, 404); + this.splitContainer3.SplitterDistance = 344; + this.splitContainer3.TabIndex = 7; + this.splitContainer3.TabStop = false; + // + // changelogBrowser + // + this.changelogBrowser.AllowNavigation = false; + this.changelogBrowser.AllowWebBrowserDrop = false; + this.changelogBrowser.Dock = System.Windows.Forms.DockStyle.Fill; + this.changelogBrowser.IsWebBrowserContextMenuEnabled = false; + this.changelogBrowser.Location = new System.Drawing.Point(0, 0); + this.changelogBrowser.MinimumSize = new System.Drawing.Size(19, 18); + this.changelogBrowser.Name = "changelogBrowser"; + this.changelogBrowser.ScriptErrorsSuppressed = true; + this.changelogBrowser.Size = new System.Drawing.Size(698, 344); + this.changelogBrowser.TabIndex = 7; + this.changelogBrowser.TabStop = false; + this.changelogBrowser.WebBrowserShortcutsEnabled = false; + // + // progressBar1 + // + this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.progressBar1.Location = new System.Drawing.Point(12, 14); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(487, 30); + this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Continuous; + this.progressBar1.TabIndex = 4; + this.progressBar1.Visible = false; + // + // closeBtn + // + this.closeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.closeBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(253)))), ((int)(((byte)(253)))), ((int)(((byte)(253))))); + this.closeBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.closeBtn.FlatAppearance.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(171)))), ((int)(((byte)(171))))); + this.closeBtn.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(146)))), ((int)(((byte)(192)))), ((int)(((byte)(224))))); + this.closeBtn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(205)))), ((int)(((byte)(230)))), ((int)(((byte)(247))))); + this.closeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.closeBtn.ForeColor = System.Drawing.SystemColors.ControlText; + this.closeBtn.Location = new System.Drawing.Point(507, 14); + this.closeBtn.Name = "closeBtn"; + this.closeBtn.Size = new System.Drawing.Size(179, 30); + this.closeBtn.TabIndex = 7; + this.closeBtn.Text = "Close"; + this.closeBtn.UseVisualStyleBackColor = false; + // + // updBtn + // + this.updBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.updBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(253)))), ((int)(((byte)(253)))), ((int)(((byte)(253))))); + this.updBtn.FlatAppearance.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(171)))), ((int)(((byte)(171)))), ((int)(((byte)(171))))); + this.updBtn.FlatAppearance.MouseDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(146)))), ((int)(((byte)(192)))), ((int)(((byte)(224))))); + this.updBtn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(205)))), ((int)(((byte)(230)))), ((int)(((byte)(247))))); + this.updBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.updBtn.Location = new System.Drawing.Point(13, 14); + this.updBtn.Name = "updBtn"; + this.updBtn.Size = new System.Drawing.Size(486, 30); + this.updBtn.TabIndex = 5; + this.updBtn.Text = "Update"; + this.updBtn.UseVisualStyleBackColor = false; + this.updBtn.Visible = false; + this.updBtn.Click += new System.EventHandler(this.UpdBtn_Click); + // + // panel1 + // + this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(131)))), ((int)(((byte)(131)))), ((int)(((byte)(131))))); + this.panel1.Controls.Add(this.mainSplitContainer); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Margin = new System.Windows.Forms.Padding(10); + this.panel1.Name = "panel1"; + this.panel1.Padding = new System.Windows.Forms.Padding(1); + this.panel1.Size = new System.Drawing.Size(700, 440); + this.panel1.TabIndex = 8; + // + // UpdateWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(700, 440); + this.ControlBox = false; + this.Controls.Add(this.panel1); + this.DoubleBuffered = true; + this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UpdateWindow"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Load += new System.EventHandler(this.UpdateWindow_Load); + this.mainSplitContainer.Panel1.ResumeLayout(false); + this.mainSplitContainer.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.mainSplitContainer)).EndInit(); + this.mainSplitContainer.ResumeLayout(false); + this.titleLayoutPanel.ResumeLayout(false); + this.splitContainer3.Panel1.ResumeLayout(false); + this.splitContainer3.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); + this.splitContainer3.ResumeLayout(false); + this.panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.SplitContainer mainSplitContainer; + private System.Windows.Forms.SplitContainer splitContainer3; + private System.Windows.Forms.WebBrowser changelogBrowser; + private System.Windows.Forms.Button closeBtn; + private System.Windows.Forms.Button updBtn; + private System.Windows.Forms.ProgressBar progressBar1; + private System.Windows.Forms.TableLayoutPanel titleLayoutPanel; + private System.Windows.Forms.Label titlebar; + private System.Windows.Forms.Button closeWindowBtn; + private System.Windows.Forms.Button maximizeBtn; + private System.Windows.Forms.Button minimizeBtn; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/Katteker.Gui/UpdateWindow.cs b/Katteker.Gui/UpdateWindow.cs new file mode 100644 index 0000000..2763d87 --- /dev/null +++ b/Katteker.Gui/UpdateWindow.cs @@ -0,0 +1,146 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Katteker.Gui.Properties; + +namespace Katteker.Gui +{ + public sealed partial class UpdateWindow : Form + { + private const int HTCAPTION = 0x2; + private const int WM_NCLBUTTONDOWN = 0xA1; + + /// + /// The Update Window + /// + public UpdateWindow() + { + Font = SystemFonts.MessageBoxFont; + Application.EnableVisualStyles(); + if (Environment.OSVersion.Version.Major >= 6) + SetProcessDPIAware(); + InitializeComponent(); + } + + private string Changelog => UpdateManager.ChangelogFilename; + + /// + /// Last release entry. + /// + public ReleaseEntry ReleaseEntry { get; set; } + + /// + /// Update manager + /// + public UpdateManager UpdateManager { get; set; } + + private string PublishPath => UpdateManager.UrlOrPath; + + /// + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing) + { + components?.Dispose(); + } + base.Dispose(disposing); + } + + [DllImport("User32.dll")] + private static extern bool ReleaseCapture(); + + [DllImport("User32.dll")] + private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); + + [DllImport("user32.dll")] + private static extern bool SetProcessDPIAware(); + private void CloseBtn_Click(object sender, EventArgs e) + { + Close(); + } + + private void MaximizeBtn_Click(object sender, EventArgs e) + { + WindowState = WindowState != FormWindowState.Maximized ? FormWindowState.Maximized : FormWindowState.Normal; + } + + private void MinimizeBtn_Click(object sender, EventArgs e) + { + WindowState = FormWindowState.Minimized; + } + + private void Titlebar_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) return; + ReleaseCapture(); + SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + + private async void UpdateWindow_Load(object sender, EventArgs e) + { + var changelog = await ChangelogHelper.LoadChangelogAsync(Changelog, PublishPath).ConfigureAwait(false); + Invoke(new Action(() => changelogBrowser.DocumentText = changelog)); + if (ReleaseEntry == null) + { + Invoke(new Action(() => { WriteTitle(Resources.No_update_available); })); + } + else + { + var latest = ReleaseEntry.Version; + Invoke(new Action(() => + { + WriteTitle(Resources.You_can_update_to_Version + latest); + updBtn.Visible = true; + })); + } + } + + private async void UpdBtn_Click(object sender, EventArgs e) + { + Invoke(new Action(() => progressBar1.Visible = true)); + + try + { + var progress = new Progress(x => Invoke(new Action(() => { progressBar1.Value = x; }))); + await UpdateManager.UpdateAppAsync(progress).ConfigureAwait(false); + + WriteTitle(Resources.You_re_up_to_date); + } + catch (Exception ex) + { + WriteTitle(ex.Message); + } + finally + { + Invoke(new Action(() => + { + progressBar1.Visible = false; + updBtn.Visible = false; + var messResult = MessageBox.Show( + Resources.Installed_new_Version, + Resources.New_Version, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + if (messResult == DialogResult.Yes) + { + DialogResult = DialogResult.OK; + Close(); + } + })); + } + } + private void WriteTitle(string text) + { + Invoke(new Action(() => + { + Text = text; + titlebar.Text = text; + })); + } + } +} \ No newline at end of file diff --git a/Katteker.Gui/UpdateWindow.resx b/Katteker.Gui/UpdateWindow.resx new file mode 100644 index 0000000..e69aef3 --- /dev/null +++ b/Katteker.Gui/UpdateWindow.resx @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYYGwFCSVQrTlloXjZEUYFIV2iiUmqFt1h2 + m8o1UHHcEShHPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhgcBiQuPs5khK7/fajd/3up + 4P94q+b/fa3p/0Jml/8NIkVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEB80eUdy + qv9+rOj/eqfg/3Sk3f96p+D/SG2f/xMZKWMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAMGS45OWCR/1yOzf9Gdar/SXyr/0uBtP85bqT/IUBk3ilBU2JzhZUaAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADZHWDwwUX3/Y5e//5vI1P+dvMX/k6+4/5avtf+Rtsb/i7jF/3ibsfJcdZmyHjpgYD5T + Wg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAB5g5YIZYiZuqzb6P+u1s//fIud/4CBsf+Pjsn/jZHE/4KKuf+JlbX/nb7F/7vq + 9P+WwNT/Wnqf126KkG+ZqqQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAOzUyC1Ntjqm56vP/ncDB/3Z4pP+snur/opvb/4mHwP+Cgq7/eniW/6Sb + 3v9eXYT/U11t/7/k5//S////jsDR/16Bnc5ufItFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLX22flMfc/7HVzv97d5//m5Hh/4KGof+coqb/yc3O//Xz + 7v/j4Nv/TE9g/y0uXf81KmL/Pj1n/6LLyv/i////w/L4/3mmv/txfJd7AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjqCWVX6owf/P+fn/coCa/7qx+v+Mhrr/ra+v//// + ///l5+j/4OPh//////+Qkpj/KiRl/zgyfv83LG7/Ojde/6rGyv/h////0v///4y7zf9pg5R2AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAImhuCldiqDm0P///5q4tv9WSnb/fn64/2ht + lf9ZVG3//Pv3//Lw8v+xsLT//////+jm4f9ANFX/JyJn/ycrX/8nHWL/PD5i/5m3tP+76ez/1P///3+f + rda5upINAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD0KwKaIOVv7Dj7f/f////XHF4/yki + Yv8vK2L/LS5m/ykjVP+yr7X//////5yan/+Mk5T/mqWq/3qMlP95lKL/gZqp/32Po/9JVWn/ZG9u/837 + /P/S////hay3/7i5lVEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHiOkn2YwtT/2////8fm + 6v85Q1//Ni15/zYzeP8yMX7/KiNe/0pMUv+To5n/k6+2/7vc3v+339f/xPXv/9r////Z////2f///6DG + w/+22d7/2v///8X4/f9ehZvPubuYAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACYnpg7apO398// + ///T////p8bM/y8sX/8/MH3/NTBx/zAmXP9bZHv/psbK/8z4+f/Y////jau2/3B7ov98gqf/hZWd/7/m + 4f/a////xe30/8L7/f/a////eqfG/3WEk4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGR3 + lbWw5/L/0v///9P///+Jp67/Lydl/zgubP8xOFv/kKmx/9T++//N////1v///6S8wf97d6r/trD//7Wv + //+gm+H/X3GM/7DY3v/N/Pn/1////4691/9Nboa3xMKtBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALTF + mw2Ln5NQfqPD/9P////N+f//2f///3WKlP8rImH/Nz5m/6zN0P/a////yfv//8z7///N9uj/cn6d/7Wx + +v+srPj/n5ve/3t3ov9JWoD/W4i0/2aax/9chrf/UnKM1K64lRoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAmpuYUnOEmKuv3+n/z/7//8z8/P/a////b3iN/yYpS/+iw87/1////8z8/P/O/P3/2P///5i9 + 0P9SXoT/doOz/19rkf9IS2D/Z2ls/5GWnP91gpr/ZJDE/0hytf+KlYiDAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABje5eGbY6k2cX9/v/P/f//yfj7/5/Awv8+SVL/bH2B/+D////J+f//0f///8Pz + +v9smLD/Pl59/2Jmdv9ecZT/ZX6f/6Ouuf////////////r49P+RoKv/UXiw/159lJwAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGF9nYltlq3cx////9D7///L+f3/v+Pm/6nDyP94nJn/vevn/9f/ + //+75+7/Tnmi/1Jpiv/V2df/9vr4/9Pc4/+CiY3/5ubn/8XHx/+YnJ///////+no4/9aeJz/P2abtgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmJyVU3yTl6u97vb/z/3//8z7/f/M////1f///8/5 + +//Q/f7/uefs/0l6rv9bbY7/4Nzi/////P/q7en//////+fm5f/HxMP/fHl4/zEvMf/4+Pz/7+7s/2J/ + mv9WdqDNs6yOAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC6uqAElp6UQoSqxf/Q////z/3//838 + ///N/v3/2f///7zm8f9HdqT/V4nE/3uFl///////kpeU/yIhJv/n4+H//////7a0s//v7u3/6enm//// + //+4t7X/X4Sn/1N8s/ylraAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADI0LgCdYqWmXeh + vP656/j/yvn//8n1/v+czNr/R3Sg/1aHyP94p+L/dYKP//v7+f/IyMv/gICE//Tx8///////f4SL/7q4 + uv/c4eP/qK+t/1ReZv9WdJj/Y47M/0VtlMNujZWShJiUL7O9tQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAjJOLS2mJmNFtj6XsaJCX6DpchPFAcrT/gqvi/3io4P9lhK3/r7K3/////////////////7Oz + q/9YfZ7/W4W2/0ZUcP9OXXT/Ynen/3Sj1f91ptv/WIbH/0Vwsf9EcbD+cImVtbu/ogoAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAwdKpC622sRSnv7ARpLOtJkRrltptoND/eaba/3Gj4f9ngKH/ipCW/5+n + rf93gIT/SlVz/1p1m/90qtv/ZY64/32k1P90ntD/XY3L/zpuj/9gh8L/bJ3W/3uk2/9Vicr/cIqQeZGm + kyoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiJ2VXE57tP99qd7/dKTe/3ur + 6P9Taoj/UG+S/0tjfv9ojsD/d5rU/3qm2f95ruP/b53k/059sv9WcJWdqa6gO2Z2h6BMfLP/dKTS/2aT + 1P9vhZawnKeWVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGxKsHVXSbx2+e + 1f91pdz/eKXe/3KbzP92pNj/d6XZ/3Wl2f97q+P/dJ7Z/1SDwP9Ca5XbfY2NVwAAAAAAAAAAw86YBYib + mYY/ZpTDSW2Pt7G1oyvOyqsEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB9kZRiVYO8/3um4f95p9n/d6bf/3Wk2/92nM3/YIrD/0R4sP5QdpjhY4KVc6S3oBMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAK60n0ZNd6z/fazh/0uAvP49ZpfGJ1mXwEFpmrV5j5eTcIeNM4qlsQ4AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAoa2ZS1KAsP9vo9f/WYG0/3SFi00AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUnJZOWoi4/2CKyP9jksv/V3+cxoykrQMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJadk0xUgLX/cp3a/3Wh2v9ReqLWcHuVBwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtr+jJENtlehZjdH/VH22/3uU + l38AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg6KaP3KG + jb2MnJd0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/wB///8Af///gH///4Af//+AA///AAD//gAAf/4AAD/8AAAf+AAAD/AA + AA/wAAAP4AAAH+AAAB+AAAA/gAAAf4AAAH+AAAB/gAAAP4AAAD/AAAAH8AAAA/gAAAH/gAAB/4ABgf/A + A///wA///8H////A////wP///8H////j//8= + + + \ No newline at end of file diff --git a/Katteker.Gui/Wrapper.cs b/Katteker.Gui/Wrapper.cs new file mode 100644 index 0000000..b999286 --- /dev/null +++ b/Katteker.Gui/Wrapper.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; +using Katteker.Gui.Properties; + +namespace Katteker.Gui +{ + /// + /// The wrapper to add Squirrel-capability to older Programs. + /// + public static class Wrapper + { + /// + /// Checks for Updates. + /// + /// Task + public static Task CheckForUpdateAsync() => CheckForUpdateAsync(false); + + /// + /// Checks for Updates. + /// + /// Is this Method called on Startup of the Program. + /// Task + public static async Task CheckForUpdateAsync(bool isStartup) + { + using (var window = new UpdateWindow()) + { + var dialogResult = DialogResult.Cancel; + if (!UpdateManager.TryCreate(out var manager) && !isStartup) + { + MessageBox.Show(Resources.SquirrelWrapper_CheckForUpdate, + Resources.SquirrelWrapper_CheckForUpdate_Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + var releases = (await manager.CheckForUpdateAsync().ConfigureAwait(true)).ToArray(); + window.UpdateManager = manager; + window.ReleaseEntry = releases.LastOrDefault(); + if (isStartup) + { + if (releases?.Any() == true) + { + dialogResult = window.ShowDialog(); + } + } + else + { + dialogResult = window.ShowDialog(); + } + + + if (dialogResult == DialogResult.OK) + { + manager.RestartApp(); + } + } + } + } +} \ No newline at end of file diff --git a/Katteker.Gui/app.config b/Katteker.Gui/app.config new file mode 100644 index 0000000..f2c1f9b --- /dev/null +++ b/Katteker.Gui/app.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Katteker.Gui/packages.config b/Katteker.Gui/packages.config new file mode 100644 index 0000000..8f5839b --- /dev/null +++ b/Katteker.Gui/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Katteker.sln b/Katteker.sln new file mode 100644 index 0000000..b0d1294 --- /dev/null +++ b/Katteker.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2036 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KattekerCreator", "Creator\KattekerCreator.csproj", "{53D065EB-8818-4F60-9EBE-75705E1BB00D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Katteker", "Katteker\Katteker.csproj", "{A45E1C59-BA9E-452C-A5E2-50DE49D53E92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppStub", "AppStub\AppStub.csproj", "{E746AE0F-BEEA-4C18-9ED8-2E708ED00A5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{DE9CFDEB-40B8-447D-9959-31BA78512CED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Katteker.Gui", "Katteker.Gui\Katteker.Gui.csproj", "{07E2DE31-80A0-43DA-B307-1CA47CD930A1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {53D065EB-8818-4F60-9EBE-75705E1BB00D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53D065EB-8818-4F60-9EBE-75705E1BB00D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53D065EB-8818-4F60-9EBE-75705E1BB00D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53D065EB-8818-4F60-9EBE-75705E1BB00D}.Release|Any CPU.Build.0 = Release|Any CPU + {A45E1C59-BA9E-452C-A5E2-50DE49D53E92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A45E1C59-BA9E-452C-A5E2-50DE49D53E92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A45E1C59-BA9E-452C-A5E2-50DE49D53E92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A45E1C59-BA9E-452C-A5E2-50DE49D53E92}.Release|Any CPU.Build.0 = Release|Any CPU + {E746AE0F-BEEA-4C18-9ED8-2E708ED00A5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E746AE0F-BEEA-4C18-9ED8-2E708ED00A5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E746AE0F-BEEA-4C18-9ED8-2E708ED00A5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E746AE0F-BEEA-4C18-9ED8-2E708ED00A5B}.Release|Any CPU.Build.0 = Release|Any CPU + {DE9CFDEB-40B8-447D-9959-31BA78512CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE9CFDEB-40B8-447D-9959-31BA78512CED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE9CFDEB-40B8-447D-9959-31BA78512CED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE9CFDEB-40B8-447D-9959-31BA78512CED}.Release|Any CPU.Build.0 = Release|Any CPU + {07E2DE31-80A0-43DA-B307-1CA47CD930A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E2DE31-80A0-43DA-B307-1CA47CD930A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E2DE31-80A0-43DA-B307-1CA47CD930A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E2DE31-80A0-43DA-B307-1CA47CD930A1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EAAA54E0-C857-4C5C-BD5D-14A21D462081} + EndGlobalSection +EndGlobal diff --git a/Katteker/Constants.cs b/Katteker/Constants.cs new file mode 100644 index 0000000..c5011ca --- /dev/null +++ b/Katteker/Constants.cs @@ -0,0 +1,30 @@ +namespace Katteker +{ + public static class Constants + { + /// + /// Filename of the configuration-file. + /// + public const string KattekerConfig = "Katteker.config"; + + /// + /// Filename of the setup-file + /// + public const string SetupFile = "setup.exe"; + + /// + /// Filename of the execution stub. + /// + public const string ExecutionStub = "AppStub.exe"; + + /// + /// File of the releases. + /// + public const string RELEASE = "RELEASE"; + + /// + /// Name of the + /// + public const string PackageFolder = "package"; + } +} \ No newline at end of file diff --git a/Katteker/Katteker.csproj b/Katteker/Katteker.csproj new file mode 100644 index 0000000..19fa3ab --- /dev/null +++ b/Katteker/Katteker.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {A45E1C59-BA9E-452C-A5E2-50DE49D53E92} + Library + Properties + Katteker + Katteker + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\Katteker.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Katteker/KattekerConfig.cs b/Katteker/KattekerConfig.cs new file mode 100644 index 0000000..a1fb781 --- /dev/null +++ b/Katteker/KattekerConfig.cs @@ -0,0 +1,38 @@ +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; + +namespace Katteker +{ + [DataContract] + public class KattekerConfig + { + [DataMember] + public string PublishDir { get; set; } + + [DataMember] + public string Changelog { get; set; } + + + public static KattekerConfig ReadFromFile(string path) + { + if (!File.Exists(path)) throw new FileNotFoundException(); + var dataContractJsonSerializer = new DataContractJsonSerializer(typeof(KattekerConfig)); + using (var fileStream = File.OpenRead(path)) + { + var obj = dataContractJsonSerializer.ReadObject(fileStream); + return (KattekerConfig) obj; + } + } + + public void WriteToFile(string path) + { + if (File.Exists(path)) File.Delete(path); + using (var fileStream = File.OpenWrite(path)) + { + var dataContractJsonSerializer = new DataContractJsonSerializer(typeof(KattekerConfig)); + dataContractJsonSerializer.WriteObject(fileStream, this); + } + } + } +} \ No newline at end of file diff --git a/Katteker/Properties/AssemblyInfo.cs b/Katteker/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9d91de6 --- /dev/null +++ b/Katteker/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("Katteker")] +[assembly: AssemblyDescription("Katteker Client")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Holger Börchers")] +[assembly: AssemblyProduct("Katteker")] +[assembly: AssemblyCopyright("Copyright © Holger Börchers 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("a45e1c59-ba9e-452c-a5e2-50de49d53e92")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Katteker/ReleaseEntry.cs b/Katteker/ReleaseEntry.cs new file mode 100644 index 0000000..4d82b16 --- /dev/null +++ b/Katteker/ReleaseEntry.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Katteker +{ + public class ReleaseEntry : IComparable, IComparable + { + public const string FilenameRegex = @"(^.*)-((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?).*-(full|delta)"; + + public ReleaseEntry(string filename, SemanticVersion version, long fileSize, bool isDelta, string sha1) + { + Filename = filename; + Version = version; + Filesize = fileSize; + IsDelta = isDelta; + SHA1 = sha1; + } + + public ReleaseEntry(string line) + { + var elements = line?.Split(' '); + if (elements?.Length == 3) + { + SHA1 = elements[0]; + Filename = elements[1]; + Filesize = long.Parse(elements[2]); + var fileSegments = Regex.Match(Filename, FilenameRegex); + if (fileSegments.Groups.Count < 3) throw new ArgumentOutOfRangeException("Filename is not compilant."); + ApplicationName = fileSegments.Groups[1].Value; + Version = SemanticVersion.Parse(fileSegments.Groups[2].Value); + IsDelta = fileSegments.Groups[3].Value != "full"; + } + } + + public ReleaseEntry(string applicationName, SemanticVersion version) + { + ApplicationName = applicationName; + Version = version; + } + + public SemanticVersion Version { get; } + public string SHA1 { get; } + public string Filename { get; } + public long Filesize { get; } + public bool IsDelta { get; } + public string ApplicationName { get; } + public string EntryAsString => $"{SHA1} {Filename} {Filesize}"; + + public override string ToString() => $"{ApplicationName} {Version}"; + public int CompareTo(object obj) => CompareTo(obj as ReleaseEntry); + + public int CompareTo(ReleaseEntry other) + { + if (ReferenceEquals(this, other)) return 0; + if (ReferenceEquals(null, other)) return 1; + var versionComparison = Comparer.Default.Compare(Version, other.Version); + if (versionComparison != 0) return versionComparison; + return string.Compare(Filename, other.Filename, StringComparison.Ordinal); + } + + public override bool Equals(object obj) => Equals(obj as ReleaseEntry); + + protected bool Equals(ReleaseEntry other) + { + return Equals(Version, other.Version) && string.Equals(SHA1, other.SHA1) && string.Equals(Filename, other.Filename) && Filesize == other.Filesize && IsDelta == other.IsDelta; + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Version != null ? Version.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (SHA1 != null ? SHA1.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (Filename != null ? Filename.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ Filesize.GetHashCode(); + hashCode = (hashCode * 397) ^ IsDelta.GetHashCode(); + return hashCode; + } + } + + public static bool operator ==(ReleaseEntry left, ReleaseEntry right) + { + return Equals(left, right); + } + + public static bool operator !=(ReleaseEntry left, ReleaseEntry right) + { + return !Equals(left, right); + } + } +} \ No newline at end of file diff --git a/Katteker/Releases.cs b/Katteker/Releases.cs new file mode 100644 index 0000000..b688b06 --- /dev/null +++ b/Katteker/Releases.cs @@ -0,0 +1,79 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Katteker +{ + public class Releases : IEnumerable + { + private readonly string _filePath; + + private SortedList ReleaseEntries { get; } + + private Releases() + { + ReleaseEntries = new SortedList(); + } + + public Releases(string path) : this() + { + _filePath = Path.Combine(path, Constants.RELEASE); + if (!File.Exists(_filePath)) return; + AddRange(File.ReadAllLines(_filePath)); + } + + public Releases(string[] content) : this() + { + AddRange(content); + } + + private void AddRange(IEnumerable content) + { + foreach (var line in content) + { + var entry = new ReleaseEntry(line); + ReleaseEntries.Add(entry.Version, entry); + } + } + + public void WriteReleaseFile() + { + File.WriteAllLines(_filePath, ReleaseEntries.Select(x => x.Value.EntryAsString)); + } + + /// + /// Adds new entry to file. + /// + /// Path to setup-file + /// Version of setup-file + /// Returns true if newest version. + public ReleaseEntry Add(string setupFilePath, SemanticVersion version) + { + var sha1 = Utility.ComputeFileHash(setupFilePath); + var setupFile = new FileInfo(setupFilePath); + var entry = new ReleaseEntry(setupFile.Name, version, setupFile.Length, false, sha1); + + if (!ReleaseEntries.ContainsValue(entry)) + { + if (ReleaseEntries.ContainsKey(version)) + { + ReleaseEntries.Remove(version); + } + ReleaseEntries.Add(version, entry); + } + return entry; + } + + public bool IsLatestEntry(ReleaseEntry entry) => entry.Equals(ReleaseEntries.LastOrDefault().Value); + public IEnumerator GetEnumerator() + { + return ReleaseEntries.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Katteker/SemanticVersion.cs b/Katteker/SemanticVersion.cs new file mode 100644 index 0000000..af009b5 --- /dev/null +++ b/Katteker/SemanticVersion.cs @@ -0,0 +1,511 @@ +using System; +using System.Globalization; +using System.Runtime.Serialization; +using System.Security.Permissions; +using System.Text.RegularExpressions; + +namespace Katteker +{ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + /// + /// A semantic version implementation. + /// Conforms to v2.0.0 of http://semver.org/ + /// + [Serializable] + public sealed class SemanticVersion : IComparable, IComparable, ISerializable + { + private static readonly Regex parseEx = + new Regex(@"^(?\d+)(\.(?\d+))?(\.(?\d+))?(\-(?
[0-9A-Za-z\-\.]+))?(\+(?[0-9A-Za-z\-\.]+))?$",
+                RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+
+        /// 
+        /// Initializes a new instance of the  class.
+        /// 
+        /// 
+        /// 
+        /// 
+        private SemanticVersion(SerializationInfo info, StreamingContext context)
+        {
+            if (info == null) throw new ArgumentNullException(nameof(info));
+            var semVersion = Parse(info.GetString("SemanticVersion"));
+            Major = semVersion.Major;
+            Minor = semVersion.Minor;
+            Patch = semVersion.Patch;
+            Prerelease = semVersion.Prerelease;
+            Build = semVersion.Build;
+        }
+
+        /// 
+        /// Initializes a new instance of the  class.
+        /// 
+        /// The major version.
+        /// The minor version.
+        /// The patch version.
+        /// The prerelease version (eg. "alpha").
+        /// The build eg ("nightly.232").
+        public SemanticVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
+        {
+            Major = major;
+            Minor = minor;
+            Patch = patch;
+
+            Prerelease = prerelease ?? "";
+            Build = build ?? "";
+        }
+
+        /// 
+        /// Initializes a new instance of the  class.
+        /// 
+        /// The  that is used to initialize 
+        /// the Major, Minor, Patch and Build properties.
+        public SemanticVersion(Version version)
+        {
+            if (version == null)
+                throw new ArgumentNullException(nameof(version));
+
+            Major = version.Major;
+            Minor = version.Minor;
+
+            if (version.Revision >= 0)
+            {
+                Patch = version.Revision;
+            }
+
+            Prerelease = string.Empty;
+
+            if (version.Build > 0)
+            {
+                Build = version.Build.ToString();
+            }
+            else
+            {
+                Build = string.Empty;
+            }
+        }
+
+        /// 
+        /// Parses the specified string to a semantic version.
+        /// 
+        /// The version string.
+        /// If set to true minor and patch version are required, else they default to 0.
+        /// The SemanticVersion object.
+        /// When a invalid version string is passed.
+        public static SemanticVersion Parse(string version, bool strict = false)
+        {
+            var match = parseEx.Match(version);
+            if (!match.Success)
+                throw new ArgumentException("Invalid version.", nameof(version));
+
+            var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
+
+            var minorMatch = match.Groups["minor"];
+            var minor = 0;
+            if (minorMatch.Success)
+            {
+                minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
+            }
+            else if (strict)
+            {
+                throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");
+            }
+
+            var patchMatch = match.Groups["patch"];
+            var patch = 0;
+            if (patchMatch.Success)
+            {
+                patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
+            }
+            else if (strict)
+            {
+                throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");
+            }
+
+            var prerelease = match.Groups["pre"].Value;
+            var build = match.Groups["build"].Value;
+
+            return new SemanticVersion(major, minor, patch, prerelease, build);
+        }
+
+        /// 
+        /// Parses the specified string to a semantic version.
+        /// 
+        /// The version string.
+        /// When the method returns, contains a SemanticVersion instance equivalent 
+        /// to the version string passed in, if the version string was valid, or null if the 
+        /// version string was not valid.
+        /// If set to true minor and patch version are required, else they default to 0.
+        /// False when a invalid version string is passed, otherwise true.
+        public static bool TryParse(string version, out SemanticVersion semver, bool strict = false)
+        {
+            try
+            {
+                semver = Parse(version, strict);
+                return true;
+            }
+            catch (Exception)
+            {
+                semver = null;
+                return false;
+            }
+        }
+
+        /// 
+        /// Tests the specified versions for equality.
+        /// 
+        /// The first version.
+        /// The second version.
+        /// If versionA is equal to versionB true, else false.
+        public static bool Equals(SemanticVersion versionA, SemanticVersion versionB)
+        {
+            if (ReferenceEquals(versionA, null))
+                return ReferenceEquals(versionB, null);
+            return versionA.Equals(versionB);
+        }
+
+        /// 
+        /// Compares the specified versions.
+        /// 
+        /// The version to compare to.
+        /// The version to compare against.
+        /// If versionA < versionB < 0, if versionA > versionB > 0,
+        /// if versionA is equal to versionB 0.
+        public static int Compare(SemanticVersion versionA, SemanticVersion versionB)
+        {
+            if (ReferenceEquals(versionA, null))
+                return ReferenceEquals(versionB, null) ? 0 : -1;
+            return versionA.CompareTo(versionB);
+        }
+
+        /// 
+        /// Make a copy of the current instance with optional altered fields. 
+        /// 
+        /// The major version.
+        /// The minor version.
+        /// The patch version.
+        /// The prerelease text.
+        /// The build text.
+        /// The new version object.
+        public SemanticVersion Change(int? major = null, int? minor = null, int? patch = null,
+            string prerelease = null, string build = null)
+        {
+            return new SemanticVersion(
+                major ?? Major,
+                minor ?? Minor,
+                patch ?? Patch,
+                prerelease ?? Prerelease,
+                build ?? Build);
+        }
+
+        /// 
+        /// Gets the major version.
+        /// 
+        /// 
+        /// The major version.
+        /// 
+        public int Major { get; private set; }
+
+        /// 
+        /// Gets the minor version.
+        /// 
+        /// 
+        /// The minor version.
+        /// 
+        public int Minor { get; private set; }
+
+        /// 
+        /// Gets the patch version.
+        /// 
+        /// 
+        /// The patch version.
+        /// 
+        public int Patch { get; private set; }
+
+        /// 
+        /// Gets the pre-release version.
+        /// 
+        /// 
+        /// The pre-release version.
+        /// 
+        public string Prerelease { get; private set; }
+
+        /// 
+        /// Gets the build version.
+        /// 
+        /// 
+        /// The build version.
+        /// 
+        public string Build { get; private set; }
+
+        /// 
+        /// Returns a  that represents this instance.
+        /// 
+        /// 
+        /// A  that represents this instance.
+        /// 
+        public override string ToString()
+        {
+            var version = "" + Major + "." + Minor + "." + Patch;
+            if (!string.IsNullOrEmpty(Prerelease))
+                version += "-" + Prerelease;
+            if (!string.IsNullOrEmpty(Build))
+                version += "+" + Build;
+            return version;
+        }
+
+        /// 
+        /// Compares the current instance with another object of the same type and returns an integer that indicates 
+        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the 
+        /// other object.
+        /// 
+        /// An object to compare with this instance.
+        /// 
+        /// A value that indicates the relative order of the objects being compared. 
+        /// The return value has these meanings: Value Meaning Less than zero 
+        ///  This instance precedes  in the sort order. 
+        ///  Zero This instance occurs in the same position in the sort order as . i
+        ///  Greater than zero This instance follows  in the sort order.
+        /// 
+        public int CompareTo(object obj)
+        {
+            return CompareTo((SemanticVersion)obj);
+        }
+
+        /// 
+        /// Compares the current instance with another object of the same type and returns an integer that indicates 
+        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the 
+        /// other object.
+        /// 
+        /// An object to compare with this instance.
+        /// 
+        /// A value that indicates the relative order of the objects being compared. 
+        /// The return value has these meanings: Value Meaning Less than zero 
+        ///  This instance precedes  in the sort order. 
+        ///  Zero This instance occurs in the same position in the sort order as . i
+        ///  Greater than zero This instance follows  in the sort order.
+        /// 
+        public int CompareTo(SemanticVersion other)
+        {
+            if (ReferenceEquals(other, null))
+                return 1;
+
+            var r = CompareByPrecedence(other);
+            if (r != 0)
+                return r;
+
+            r = CompareComponent(Build, other.Build);
+            return r;
+        }
+
+        /// 
+        /// Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
+        /// 
+        /// The semantic version.
+        /// true if the version precedence matches.
+        public bool PrecedenceMatches(SemanticVersion other)
+        {
+            return CompareByPrecedence(other) == 0;
+        }
+
+        /// 
+        /// Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
+        /// 
+        /// The semantic version.
+        /// 
+        /// A value that indicates the relative order of the objects being compared. 
+        /// The return value has these meanings: Value Meaning Less than zero 
+        ///  This instance precedes  in the version precedence.
+        ///  Zero This instance has the same precedence as . i
+        ///  Greater than zero This instance has creater precedence as .
+        /// 
+        public int CompareByPrecedence(SemanticVersion other)
+        {
+            if (ReferenceEquals(other, null))
+                return 1;
+
+            var r = Major.CompareTo(other.Major);
+            if (r != 0) return r;
+
+            r = Minor.CompareTo(other.Minor);
+            if (r != 0) return r;
+
+            r = Patch.CompareTo(other.Patch);
+            if (r != 0) return r;
+
+            r = CompareComponent(Prerelease, other.Prerelease, true);
+            return r;
+        }
+
+        static int CompareComponent(string a, string b, bool lower = false)
+        {
+            var aEmpty = string.IsNullOrEmpty(a);
+            var bEmpty = string.IsNullOrEmpty(b);
+            if (aEmpty && bEmpty)
+                return 0;
+
+            if (aEmpty)
+                return lower ? 1 : -1;
+            if (bEmpty)
+                return lower ? -1 : 1;
+
+            var aComps = a.Split('.');
+            var bComps = b.Split('.');
+
+            var minLen = Math.Min(aComps.Length, bComps.Length);
+            for (var i = 0; i < minLen; i++)
+            {
+                var ac = aComps[i];
+                var bc = bComps[i];
+                int anum, bnum;
+                var isanum = int.TryParse(ac, out anum);
+                var isbnum = int.TryParse(bc, out bnum);
+                int r;
+                if (isanum && isbnum)
+                {
+                    r = anum.CompareTo(bnum);
+                    if (r != 0) return anum.CompareTo(bnum);
+                }
+                else
+                {
+                    if (isanum)
+                        return -1;
+                    if (isbnum)
+                        return 1;
+                    r = string.CompareOrdinal(ac, bc);
+                    if (r != 0)
+                        return r;
+                }
+            }
+
+            return aComps.Length.CompareTo(bComps.Length);
+        }
+
+        /// 
+        /// Determines whether the specified  is equal to this instance.
+        /// 
+        /// The  to compare with this instance.
+        /// 
+        ///   true if the specified  is equal to this instance; otherwise, false.
+        /// 
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(obj, null))
+                return false;
+
+            if (ReferenceEquals(this, obj))
+                return true;
+
+            var other = (SemanticVersion)obj;
+
+            return Major == other.Major &&
+                Minor == other.Minor &&
+                Patch == other.Patch &&
+                string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal) &&
+                string.Equals(Build, other.Build, StringComparison.Ordinal);
+        }
+
+        /// 
+        /// Returns a hash code for this instance.
+        /// 
+        /// 
+        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
+        /// 
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                var result = Major.GetHashCode();
+                result = result * 31 + Minor.GetHashCode();
+                result = result * 31 + Patch.GetHashCode();
+                result = result * 31 + Prerelease.GetHashCode();
+                result = result * 31 + Build.GetHashCode();
+                return result;
+            }
+        }
+
+#if !NETSTANDARD
+        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+        public void GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            if (info == null) throw new ArgumentNullException(nameof(info));
+            info.AddValue("SemanticVersion", ToString());
+        }
+#endif
+
+        /// 
+        /// Implicit conversion from string to SemanticVersion.
+        /// 
+        /// The semantic version.
+        /// The SemanticVersion object.
+        public static implicit operator SemanticVersion(string version)
+        {
+            return Parse(version);
+        }
+
+        /// 
+        /// The override of the equals operator. 
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is equal to right true, else false.
+        public static bool operator ==(SemanticVersion left, SemanticVersion right)
+        {
+            return Equals(left, right);
+        }
+
+        /// 
+        /// The override of the un-equal operator. 
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is not equal to right true, else false.
+        public static bool operator !=(SemanticVersion left, SemanticVersion right)
+        {
+            return !Equals(left, right);
+        }
+
+        /// 
+        /// The override of the greater operator. 
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is greater than right true, else false.
+        public static bool operator >(SemanticVersion left, SemanticVersion right)
+        {
+            return Compare(left, right) > 0;
+        }
+
+        /// 
+        /// The override of the greater than or equal operator. 
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is greater than or equal to right true, else false.
+        public static bool operator >=(SemanticVersion left, SemanticVersion right)
+        {
+            return left == right || left > right;
+        }
+
+        /// 
+        /// The override of the less operator. 
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is less than right true, else false.
+        public static bool operator <(SemanticVersion left, SemanticVersion right)
+        {
+            return Compare(left, right) < 0;
+        }
+
+        /// 
+        /// The override of the less than or equal operator. 
+        /// 
+        /// The left value.
+        /// The right value.
+        /// If left is less than or equal to right true, else false.
+        public static bool operator <=(SemanticVersion left, SemanticVersion right)
+        {
+            return left == right || left < right;
+        }
+    }
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+}
\ No newline at end of file
diff --git a/Katteker/UpdateInfo.cs b/Katteker/UpdateInfo.cs
new file mode 100644
index 0000000..c4b4b44
--- /dev/null
+++ b/Katteker/UpdateInfo.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+
+namespace Katteker
+{
+    public class UpdateInfo
+    {
+        private readonly Releases _releases;
+        public ReleaseEntry CurrentlyInstalledVersion { get; protected set; }
+
+        public ReleaseEntry FutureReleaseEntry { get; protected set; }
+
+        public List ReleasesToApply { get; protected set; } = new List();
+
+        
+
+        public UpdateInfo(string applicationName, Releases releases)
+        {
+            _releases = releases;
+            CurrentlyInstalledVersion = new ReleaseEntry(applicationName, VersionExtension.GetCurrentVersion);
+            foreach (var release in releases)
+            {
+                if (applicationName.Equals(release.ApplicationName) && release.Version > CurrentlyInstalledVersion.Version)
+                {
+                    ReleasesToApply.Add(release);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Katteker/UpdateManager.cs b/Katteker/UpdateManager.cs
new file mode 100644
index 0000000..deef65e
--- /dev/null
+++ b/Katteker/UpdateManager.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Katteker
+{
+    public class UpdateManager
+    {
+        private static readonly string _baseDir = AppDomain.CurrentDomain.BaseDirectory;
+        private static KattekerConfig _config;
+        private readonly string _applicationName;
+        private readonly string _packageDir;
+        private readonly string _rootAppDirectory;
+        private readonly string _urlOrPath;
+        private Releases _releases;
+
+        private UpdateManager(string urlOrPath, string applicationName, string rootDirectory)
+        {
+            _urlOrPath = urlOrPath;
+            _applicationName = applicationName;
+            _rootAppDirectory = rootDirectory;
+            var packageDir = Path.Combine(rootDirectory, Constants.PackageFolder);
+            if (!Directory.Exists(packageDir)) Directory.CreateDirectory(packageDir);
+            _packageDir = packageDir;
+        }
+
+        /// 
+        /// Filename of the changelog.
+        /// 
+        public string ChangelogFilename => _config.Changelog;
+
+        /// 
+        /// Url or path where the update files are located.
+        /// 
+        public string UrlOrPath => _urlOrPath;
+
+        public static UpdateManager Create(string urlOrPath = null, string applicationName = null, string rootDirectory = null)
+        {
+            _config = ReadConfigFile();
+            urlOrPath = urlOrPath ?? _config.PublishDir;
+            var appName = applicationName ?? Utility.GetApplicationName();
+            var rootAppDirectory = Path.Combine(rootDirectory ?? Utility.GetLocalAppDataDirectory(), appName);
+            return new UpdateManager(urlOrPath, appName, rootAppDirectory);
+        }
+
+        public static bool TryCreate(out UpdateManager manager, string urlOrPath = null, string applicationName = null, string rootDirectory = null)
+        {
+            try
+            {
+                manager = Create(urlOrPath, applicationName, rootDirectory);
+                return true;
+            }
+            catch (Exception)
+            {
+                manager = default(UpdateManager);
+                return false;
+            }
+        }
+        public async Task> CheckForUpdateAsync()
+        {
+            _releases = Utility.IsWebUrl(_urlOrPath) ? await DownloadIndexAsync(_urlOrPath).ConfigureAwait(false) : GetFromFilesystem(_urlOrPath);
+            var updateInfo = new UpdateInfo(_applicationName, _releases);
+            return updateInfo.ReleasesToApply;
+        }
+
+        public void RestartApp(string exeToStart = null, string arguments = null)
+        {
+            exeToStart = exeToStart ?? Path.GetFileName(Assembly.GetEntryAssembly().Location);
+            var program = Path.Combine(_rootAppDirectory, exeToStart);
+            if (File.Exists(program))
+            {
+                Process.Start(program, arguments);
+                Thread.Sleep(500);
+                Environment.Exit(0);
+            }
+        }
+
+        public async Task UpdateAppAsync(IProgress progress = null)
+        {
+            progress?.Report(0);
+            var updateEntries = (await CheckForUpdateAsync().ConfigureAwait(false)).ToArray();
+            var update = updateEntries.LastOrDefault();
+            if (update == null) return false;
+            progress?.Report(30);
+            return await UpdateAppImplAsync(update, progress).ConfigureAwait(false);
+        }
+
+        private static async Task DownloadIndexAsync(string urlOrPath)
+        {
+            var url = urlOrPath.TrimEnd('/');
+            url += "/" + Constants.RELEASE;
+            var content = await new WebClient().DownloadStringTaskAsync(url).ConfigureAwait(false);
+            var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
+            return new Releases(lines);
+        }
+
+        private static Releases GetFromFilesystem(string urlOrPath) => new Releases(urlOrPath);
+
+        private static KattekerConfig ReadConfigFile()
+        {
+            var configPath = Path.Combine(_baseDir, Constants.KattekerConfig);
+            if (!File.Exists(configPath)) throw new FileNotFoundException("Configuration file not found.", configPath);
+            return KattekerConfig.ReadFromFile(configPath);
+        }
+        private static bool VerifyFileChecksum(string targetFile, string lastEntrySha1)
+        {
+            var hash = Utility.ComputeFileHash(targetFile);
+            return lastEntrySha1.Equals(hash);
+        }
+
+        private Task KillAppStubAsync()
+        {
+            foreach (var process in Process.GetProcessesByName(_applicationName))
+            {
+                var path = Path.GetDirectoryName(process.MainModule.FileName);
+                if (_rootAppDirectory.Equals(path))
+                {
+                    process.Kill();
+                    return Task.Delay(500);
+                }
+            }
+
+            return Task.FromResult(true);
+        }
+
+        private async Task PutFileInPackageFolderAsync(string filename)
+        {
+            var targetFile = Path.Combine(_packageDir, filename);
+            File.Delete(targetFile);
+            if (Utility.IsWebUrl(_urlOrPath))
+            {
+                var url = _urlOrPath.TrimEnd('/');
+                url += "/" + filename;
+                await new WebClient().DownloadFileTaskAsync(new Uri(url), targetFile).ConfigureAwait(false);
+            }
+            else
+            {
+                File.Copy(Path.Combine(_urlOrPath, filename), targetFile);
+            }
+        }
+
+        private async Task UpdateAppImplAsync(ReleaseEntry lastEntry, IProgress progress)
+        {
+            try
+            {
+                var targetFile = Path.Combine(_packageDir, lastEntry.Filename);
+                //download file.
+                await PutFileInPackageFolderAsync(lastEntry.Filename).ConfigureAwait(false);
+                progress?.Report(60);
+
+                if (!VerifyFileChecksum(targetFile, lastEntry.SHA1)) throw new FileLoadException();
+                progress?.Report(70);
+
+                await KillAppStubAsync().ConfigureAwait(false);
+                progress?.Report(80);
+
+                using (var updater = new Process())
+                {
+                    updater.StartInfo = new ProcessStartInfo(targetFile, "/S");
+                    updater.Start();
+                    updater.WaitForExit();
+                }
+                progress?.Report(100);
+
+                return true;
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e);
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/Katteker/Utility.cs b/Katteker/Utility.cs
new file mode 100644
index 0000000..ac53b55
--- /dev/null
+++ b/Katteker/Utility.cs
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Security.Cryptography;
+
+namespace Katteker
+{
+    public static class Utility
+    {
+        internal static string GetLocalAppDataDirectory()
+        {
+            return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+        }
+
+        internal static string GetApplicationName()
+        {
+            return Assembly.GetEntryAssembly().GetName().Name;
+        }
+
+        public static bool IsWebUrl(string urlOrPath)
+        {
+            var uri = new Uri(urlOrPath);
+            return uri.Scheme == "http" || uri.Scheme == "https";
+        }
+
+        public static string ComputeFileHash(string filename)
+        {
+            string sha1;
+            var fileInfo = new FileInfo(filename);
+
+            using (var fileStream = fileInfo.OpenRead())
+            {
+                var buffer = new byte[5242880];
+                fileStream.Read(buffer, 0, buffer.Length);
+                var sha1Managed = new SHA1Managed();
+                sha1 = Convert.ToBase64String(sha1Managed.ComputeHash(buffer));
+            }
+            return sha1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Katteker/VersionExtension.cs b/Katteker/VersionExtension.cs
new file mode 100644
index 0000000..a46ce02
--- /dev/null
+++ b/Katteker/VersionExtension.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Reflection;
+
+namespace Katteker
+{
+    /// 
+    /// Extension methods for the App-Version.
+    /// 
+    public static class VersionExtension
+    {
+        /// 
+        /// Get the current Version of Application.
+        /// 
+        public static SemanticVersion GetCurrentVersion
+        {
+            get
+            {
+                var assemblyVersion = Assembly.GetEntryAssembly().GetName().Version.ToString(3);
+                var getCurrentVersion = SemanticVersion.Parse(assemblyVersion);
+                var informalVersion = Assembly.GetEntryAssembly().GetCustomAttribute()?.InformationalVersion;
+                if (informalVersion != null && SemanticVersion.TryParse(informalVersion, out var semVersion))
+                    return semVersion;
+                return getCurrentVersion;
+            }
+        }
+
+        public static Version ToSystemVersion(this SemanticVersion value) => new Version(value.Major, value.Minor, value.Patch, 0);
+
+        /// 
+        /// Get the current Version of Application.
+        /// 
+        public static string GetFullVersion => GetCurrentVersion.ToString();
+    }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d6ce844
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+SquirrelKiller (aka Wombat)
+=============
+
+Ziele:
+-------------
+- Automatische Updates, auch für nicht verwaltete Software.
+- Popup unten rechts mit Informationen über Update.
+- Paket erstellt mit NSIS.
+- schnellere Verteilung.
+- Schnellere Paketerstellung ohne Umwege.
+- AppStub übernimmt die Resourcen der Executable. (Icon, Name, Version, Copyright, Beschreibung, etc)
+  [ResourceLib C# File Resource Management Library](https://github.com/dblock/resourcelib)
+
+Creator: 
+-------------
+- Informationen der Anwendung auslesen
+ - Name,
+ - Version,
+ - ...
+- AppStub kopieren & konfigurieren
+- Installer erstellen
+- RELEASE Datei updaten
+
+AppStub:
+-------------
+- Anwendung starten
+- Im Mutex ausführen
+ - Alte Versionen löschen (aufräumen)
+ - Updates für 'unmanaged' Anwendungen anzeigen
+
+Library:
+--------------
+- Updates suchen
+- Update installieren
+- App neustarten
\ No newline at end of file