Skip to content

Commit

Permalink
✏️ Create installer
Browse files Browse the repository at this point in the history
  • Loading branch information
kuleshov-aleksei committed Nov 11, 2023
1 parent dd0e202 commit 9e78c7b
Show file tree
Hide file tree
Showing 72 changed files with 428 additions and 235 deletions.
18 changes: 14 additions & 4 deletions src/Watermarker.Common/ApplicationConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,29 @@ public sealed class ApplicationConfiguration
[JsonIgnore]
public Color BorderColor { get; private set; }

private const string PUBLISHER = "Encamy";
private const string APPLICATION_TITLE = "Watermarker";
private const string CONFIG_FILENAME = "config.json";
private readonly Regex m_colorRegex = new Regex(@"^rgba?\((?'red'\d+)\, *(?'green'\d+)\, *(?'blue'\d+)[, ]*(?'alpha'\d+)?\)$", RegexOptions.Compiled);
private static readonly Logger m_logger = LogManager.GetLogger("ColoredConsole");

public static ApplicationConfiguration Load()
{
if (!File.Exists(CONFIG_FILENAME))
string configPath = CONFIG_FILENAME;
if (!File.Exists(configPath))
{
m_logger.Fatal($"Config file \"{CONFIG_FILENAME}\" does not exists");
Environment.Exit(1);
string programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%");
string installPath = Path.Combine(programFiles, PUBLISHER, APPLICATION_TITLE);
configPath = Path.Combine(installPath, CONFIG_FILENAME);
if (!File.Exists(configPath))
{
m_logger.Fatal($"Config file \"{configPath}\" does not exists");
Environment.Exit(1);
}
}

ApplicationConfiguration configuration = JsonSerializer.Deserialize<ApplicationConfiguration>(File.ReadAllText(CONFIG_FILENAME));
m_logger.Info($"Reading config from {configPath}");
ApplicationConfiguration configuration = JsonSerializer.Deserialize<ApplicationConfiguration>(File.ReadAllText(configPath));
configuration.Process();
return configuration;
}
Expand Down
9 changes: 9 additions & 0 deletions src/Watermarker.Installer/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Application x:Class="Watermarker.Installer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Watermarker.Installer"
StartupUri="MainWindow.xaml">
<Application.Resources>

</Application.Resources>
</Application>
17 changes: 17 additions & 0 deletions src/Watermarker.Installer/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace Watermarker.Installer
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
10 changes: 10 additions & 0 deletions src/Watermarker.Installer/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Windows;

[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)
)]
Binary file added src/Watermarker.Installer/Assets/icon.ico
Binary file not shown.
Binary file added src/Watermarker.Installer/Assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/Watermarker.Installer/ContextMenuRegistrar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.Win32;
using System;
using System.IO;

namespace Watermarker.Installer
{
internal static class ContextMenuRegistrar
{
private const string SHELL_KEY_NAME = "Watermarker";

internal static void UnregisterContextMenu(Action<string> onLog)
{
try
{
UnregisterContextMenu();
}
catch (Exception ex)
{
onLog($"Failed to unregister command for context menu: {ex.Message}");
}
}

internal static void RegisterContextMenu(string executablePath, string iconUrl, Action<string> onLog)
{
try
{
RegisterContextMenu(executablePath, iconUrl);
}
catch (Exception ex)
{
onLog($"Failed to register command for context menu: {ex.Message}");
}
}

private static void RegisterContextMenu(string executablePath, string iconUrl)
{
UnregisterContextMenu();

string regPath = $"Software\\Classes\\directory\\shell\\{SHELL_KEY_NAME}";
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(regPath))
{
key.SetValue(null, "Add watermark");
key.SetValue("Icon", iconUrl);
}

string commandPath = $"{regPath}\\command";
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(commandPath))
{
string baseDirectory = Directory.GetParent(executablePath).FullName;
string menuCommand = $"\"{executablePath}\" \"%1\"";
key.SetValue(null, menuCommand);
}
}

private static void UnregisterContextMenu()
{
string regPath = $"Software\\Classes\\directory\\shell\\{SHELL_KEY_NAME}";
Registry.CurrentUser.DeleteSubKeyTree(regPath, throwOnMissingSubKey: false);
}

}
}
65 changes: 65 additions & 0 deletions src/Watermarker.Installer/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<Window x:Class="Watermarker.Installer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Watermarker.Installer"
mc:Ignorable="d"
Title="Watermarker installer" Height="350" Width="500" ResizeMode="NoResize"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Loaded="OnWindowLoaded"
Icon="/Assets/icon.ico">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>

<Label Grid.Row="1" Grid.Column="1" FontSize="24" FontWeight="SemiBold">Install Watermarker?</Label>

<Grid Grid.Row="2" Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>

<Label Grid.Column="0" FontSize="14" FontWeight="Regular" Margin="0">Version:</Label>
<Label Grid.Column="1" FontSize="14" FontWeight="Regular" Margin="0" Content="{Binding Path=DetectedApplicationVersion, FallbackValue='1.0.0'}"></Label>
</Grid>

<Image Grid.Column="2" Grid.Row="1" Grid.RowSpan="2" Source="/Watermarker.Installer;component/Assets/logo.png"></Image>

<ScrollViewer Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="2" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<TextBlock TextWrapping="Wrap" Text="{Binding Path=LogMessages, FallbackValue='placeholder'}"></TextBlock>
</ScrollViewer>

<Button x:Name="InstallButton" Grid.Column="2" Grid.Row="4" Width="80" Height="30" Background="#005fb7" BorderThickness="0" Foreground="White" HorizontalAlignment="Right" Click="InstallButton_Click">
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5"/>
</Style>
</Button.Resources>
Install
</Button>

<Button x:Name="UninstallButton" Grid.Column="2" Grid.Row="4" Width="80" Height="30" Background="#005fb7" BorderThickness="0" Foreground="White" HorizontalAlignment="Left" Click="UninstallButton_Click">
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5"/>
</Style>
</Button.Resources>
Uninstall
</Button>
</Grid>
</Window>
182 changes: 182 additions & 0 deletions src/Watermarker.Installer/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using PropertyChanged.SourceGenerator;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;

namespace Watermarker.Installer
{
public partial class MainWindow : Window
{
private const string BUNDLED_APPLICATION_PATTERN = "watermarker-*.zip";
private readonly Regex m_bundleVersionRegex = new Regex(@"^watermarker-(?'version'\d+\.\d+.\d+).zip$", RegexOptions.Compiled);
private const string PUBLISHER = "Encamy";
private const string APPLICATION_TITLE = "Watermarker";
private const string APPLICATION_FILE = "Watermarker.exe";
private readonly string m_iconUrl = Path.Combine("Assets", "icon-72x72.ico");
private bool m_installed;

private string m_selectedBundleFile = string.Empty;

[Notify] private string _detectedApplicationVersion;
[Notify] private string _logMessages;

public MainWindow()
{
InitializeComponent();

DetectedApplicationVersion = "Unknown";
}

private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
List<string> bundles = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), BUNDLED_APPLICATION_PATTERN).ToList();
if (bundles.Count == 0)
{
DetectedApplicationVersion = "Bundle not found";
InstallButton.IsEnabled = false;
return;
}

Version maximumVersion = null;
string selectedBundle = string.Empty;
string selectedVersion = string.Empty;
foreach (string bundle in bundles.Select(x => Path.GetFileName(x)))
{
Match match = m_bundleVersionRegex.Match(bundle);
if (match.Success)
{
Version version = new Version(match.Groups["version"].Value);
if (maximumVersion == null || version > maximumVersion)
{
maximumVersion = version;
selectedBundle = bundle;
selectedVersion = match.Groups["version"].Value;
}
}
}

m_selectedBundleFile = selectedBundle;
DetectedApplicationVersion = selectedVersion;
InstallButton.IsEnabled = true;
}

private void InstallButton_Click(object sender, RoutedEventArgs e)
{
try
{
InstallInternal();
}
catch (Exception ex)
{
Log($"Installation failed: {ex.Message}");
}
}

private void UninstallButton_Click(object sender, RoutedEventArgs e)
{
try
{
UninstallInternal();
}
catch (Exception ex)
{
Log($"Installation failed: {ex.Message}");
}
}

private void UninstallInternal()
{
Log($"Removing context menu");
ContextMenuRegistrar.UnregisterContextMenu(Log);

string programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%");
string installPath = Path.Combine(programFiles, PUBLISHER, APPLICATION_TITLE);
Log($"Cleaning-up installation files from {installPath}");
if (Directory.Exists(installPath))
{
Directory.Delete(installPath, true);
}
}

private void InstallInternal()
{
if (m_installed)
{
Application.Current.Shutdown();
return;
}

string programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%");
string installPath = Path.Combine(programFiles, PUBLISHER, APPLICATION_TITLE);
Log($"Installing to {installPath}");

if (!Directory.Exists(installPath))
{
Directory.CreateDirectory(installPath);
Log($"Installation directory created");
}

List<string> existingFiles = Directory.EnumerateFiles(installPath, "*", SearchOption.AllDirectories).ToList();
string ignorePath = Path.Combine(installPath, "old");
existingFiles = existingFiles.Where(x => !x.StartsWith(ignorePath)).ToList();
string previousRelease = Path.Combine(installPath, "old");
if (existingFiles.Count > 0)
{
Log($"Cleaning up previous release");
if (Directory.Exists(previousRelease))
{
Directory.Delete(previousRelease, true);
}

Directory.CreateDirectory(previousRelease);
foreach (string file in existingFiles)
{
File.Move(file, Path.Combine(previousRelease, Path.GetFileName(file)));
}

Log($"Previous release moved to {previousRelease}");
}

using (ZipArchive archive = ZipFile.OpenRead(m_selectedBundleFile))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
string entryFullPath = Path.Combine(installPath, entry.FullName);
string directoryName = Path.GetDirectoryName(entryFullPath);
if (directoryName.Length > 0)
{
Directory.CreateDirectory(directoryName);
}

// Skip directory entry
if (Path.GetFileName(entryFullPath).Length == 0)
{
continue;
}

Log($"Extracting file {entry.FullName}");
entry.ExtractToFile(entryFullPath);
}
}

Log("Installed. Registering shell extension");
ContextMenuRegistrar.RegisterContextMenu(
Path.Combine(installPath, APPLICATION_FILE),
Path.Combine(installPath, m_iconUrl),
Log);
Log("Shell extension added");

m_installed = true;
InstallButton.Content = "Exit";
}

private void Log(string message)
{
LogMessages += message + Environment.NewLine;
}
}
}
Loading

0 comments on commit 9e78c7b

Please sign in to comment.