diff --git a/examples/AvaloniaCrossPlat/AvaloniaCrossPlat.csproj b/examples/AvaloniaCrossPlat/AvaloniaCrossPlat.csproj index 27e2d91b..3856c3b4 100644 --- a/examples/AvaloniaCrossPlat/AvaloniaCrossPlat.csproj +++ b/examples/AvaloniaCrossPlat/AvaloniaCrossPlat.csproj @@ -1,12 +1,11 @@  - + WinExe net8.0 true app.manifest true - $(NoWarn);CA2007 @@ -16,10 +15,29 @@ + + + + + + + + + + + + + + + + - + + <_Parameter1>AvaloniaSampleReleaseDir + <_Parameter2>$(MSBuildThisFileDirectory)releases + - + diff --git a/examples/AvaloniaCrossPlat/ConsoleLogger.cs b/examples/AvaloniaCrossPlat/ConsoleLogger.cs deleted file mode 100644 index 44a42823..00000000 --- a/examples/AvaloniaCrossPlat/ConsoleLogger.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace AvaloniaCrossPlat; - -public class ConsoleLogger : ILogger -{ - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - Console.WriteLine(formatter(state, exception)); - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public IDisposable BeginScope(TState state) - { - return null; - } -} \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/Const.cs b/examples/AvaloniaCrossPlat/Const.cs deleted file mode 100644 index 6633c5b7..00000000 --- a/examples/AvaloniaCrossPlat/Const.cs +++ /dev/null @@ -1 +0,0 @@ -class Const { public const string RELEASES_DIR = @"{REPLACE_ME}"; } diff --git a/examples/AvaloniaCrossPlat/MainWindow.axaml.cs b/examples/AvaloniaCrossPlat/MainWindow.axaml.cs index e30d9d63..4f1eb656 100644 --- a/examples/AvaloniaCrossPlat/MainWindow.axaml.cs +++ b/examples/AvaloniaCrossPlat/MainWindow.axaml.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Threading; using Microsoft.Extensions.Logging; using Velopack; @@ -16,54 +17,61 @@ public partial class MainWindow : Window public MainWindow() { InitializeComponent(); - _um = new UpdateManager(Const.RELEASES_DIR, logger: new TextBoxLogger(Log)); + _um = new UpdateManager(Program.UpdateUrl, logger: Program.Log); + TextLog.Text = Program.Log.ToString(); + Program.Log.LogUpdated += LogUpdated; UpdateStatus(); } - private async void BtnCheckUpdateClick(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private async void BtnCheckUpdateClick(object sender, RoutedEventArgs e) { Working(); try { - _update = await _um.CheckForUpdatesAsync(); + // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread + _update = await _um.CheckForUpdatesAsync().ConfigureAwait(true); } catch (Exception ex) { - Log("ERROR: " + ex.Message); + Program.Log.LogError(ex, "Error checking for updates"); } UpdateStatus(); } - private async void BtnDownloadUpdateClick(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private async void BtnDownloadUpdateClick(object sender, RoutedEventArgs e) { Working(); try { - await _um.DownloadUpdatesAsync(_update, Progress); + // ConfigureAwait(true) so that UpdateStatus() is called on the UI thread + await _um.DownloadUpdatesAsync(_update, Progress).ConfigureAwait(true); } catch (Exception ex) { - Log("ERROR: " + ex.Message); + Program.Log.LogError(ex, "Error downloading updates"); } - await Task.Delay(10); UpdateStatus(); } - private void Log(string text) - { - TextLog.Text += text + Environment.NewLine; - ScrollLog.ScrollToEnd(); - } - - private void BtnRestartApplyClick(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void BtnRestartApplyClick(object sender, RoutedEventArgs e) { _um.ApplyUpdatesAndRestart(); } + private void LogUpdated(object sender, LogUpdatedEventArgs e) + { + // logs can be sent from other threads + Dispatcher.UIThread.InvokeAsync(() => { + TextLog.Text = e.Text; + ScrollLog.ScrollToEnd(); + }); + } + private void Progress(int percent) { - Dispatcher.UIThread.Post(() => { + // progress can be sent from other threads + Dispatcher.UIThread.InvokeAsync(() => { TextStatus.Text = $"Downloading ({percent}%)..."; }); } private void Working() { - Log(""); + Program.Log.LogInformation(""); BtnCheckUpdate.IsEnabled = false; BtnDownloadUpdate.IsEnabled = false; BtnRestartApply.IsEnabled = false; @@ -93,28 +101,4 @@ public partial class MainWindow : Window TextStatus.Text = sb.ToString(); BtnCheckUpdate.IsEnabled = true; } - - private class TextBoxLogger : ILogger - { - private readonly Action _textBox; - - public TextBoxLogger(Action textBox) - { - _textBox = textBox; - } - - public IDisposable BeginScope(TState state) => null; - - public bool IsEnabled(LogLevel logLevel) => true; - - public void Log(LogLevel logLevel, EventId eventId, TState state, - Exception exception, Func formatter) - { - if (logLevel < LogLevel.Information) return; - var text = formatter(state, exception); - Dispatcher.UIThread.Post(() => { - _textBox(text); - }); - } - } } \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/MemoryLogger.cs b/examples/AvaloniaCrossPlat/MemoryLogger.cs new file mode 100644 index 00000000..81836f67 --- /dev/null +++ b/examples/AvaloniaCrossPlat/MemoryLogger.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace AvaloniaCrossPlat; + +public class LogUpdatedEventArgs : EventArgs +{ + public string Text { get; set; } +} + +public class MemoryLogger : ILogger +{ + public event EventHandler LogUpdated; + private readonly StringBuilder _sb = new StringBuilder(); + + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + lock (_sb) { + var message = formatter(state, exception); + if (exception != null) message += "\n" + exception.ToString(); + Console.WriteLine("log: " + message); + _sb.AppendLine(message); + LogUpdated?.Invoke(this, new LogUpdatedEventArgs { Text = _sb.ToString() }); + } + } + + public override string ToString() + { + lock (_sb) { + return _sb.ToString(); + } + } +} diff --git a/examples/AvaloniaCrossPlat/Program.cs b/examples/AvaloniaCrossPlat/Program.cs index cf43c65e..b80cee3a 100644 --- a/examples/AvaloniaCrossPlat/Program.cs +++ b/examples/AvaloniaCrossPlat/Program.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Reflection; using Avalonia; using Velopack; @@ -6,23 +8,48 @@ namespace AvaloniaCrossPlat; class Program { + public static string UpdateUrl { get; private set; } + + public static MemoryLogger Log { get; private set; } + // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. [STAThread] public static void Main(string[] args) { - VelopackApp.Build() - .Run(new ConsoleLogger()); + try { + // Logging is essential for debugging! Ideally you should write it to a file. + Log = new MemoryLogger(); - BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + // It's important to Run() the VelopackApp as early as possible in app startup. + VelopackApp.Build() + .Run(Log); + + // This is purely for demonstration purposes, we get the update URL from a + // property defined by MSBuild, so we can locate the local releases directory. + // In your production app, this should point to your update server. + UpdateUrl = Assembly.GetEntryAssembly() + .GetCustomAttributes() + .Where(x => x.Key == "AvaloniaSampleReleaseDir") + .Single().Value; + + // Now it's time to run Avalonia + BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + } catch (Exception ex) { + string message = "Unhandled exception: " + ex.ToString(); + Console.WriteLine(message); + throw; + } } - // Avalonia configuration, don't remove; also used by visual designer. + // Avalonia configuration, don't remove method; also used by visual designer. public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() + { + return AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() .LogToTrace(); + } } diff --git a/examples/AvaloniaCrossPlat/build-linux.sh b/examples/AvaloniaCrossPlat/build-linux.sh index 830bcd59..c4082a2d 100755 --- a/examples/AvaloniaCrossPlat/build-linux.sh +++ b/examples/AvaloniaCrossPlat/build-linux.sh @@ -5,30 +5,20 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Check if version parameter is provided if [ "$#" -ne 1 ]; then - echo "Please provide a version number." - echo "Usage: ./build.sh version_number" + echo "Version number is required." + echo "Usage: ./build.sh [version]" exit 1 fi -echo "Building Velopack" -cd "$SCRIPT_DIR/../../src/Rust" -cargo build -cd "$SCRIPT_DIR/../.." -dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj -cd "$SCRIPT_DIR" - -version="$1" -releasesDir="$SCRIPT_DIR/releases" - -# Write to Const.cs -echo "class Const { public const string RELEASES_DIR = @\"$releasesDir\"; } " > "$(dirname "$0")/Const.cs" -echo "Const.cs file updated with releases directory ($releasesDir)." +BUILD_VERSION="$1" +RELEASE_DIR="$SCRIPT_DIR/releases" +PUBLISH_DIR="$SCRIPT_DIR/publish" +ICON_PATH="$SCRIPT_DIR/Velopack.png" +echo "" echo "Compiling AvaloniaCrossPlat with dotnet..." -dotnet publish -c Release --self-contained -r linux-x64 -o "$(dirname "$0")/publish" +dotnet publish -c Release --self-contained -r linux-x64 -o "$PUBLISH_DIR" -echo "class Const { public const string RELEASES_DIR = @\"{REPLACE_ME}\"; } " > "$(dirname "$0")/Const.cs" -echo "Const.cs file reset" - -echo "Building Velopack Release v$version" -"$(dirname "$0")/../../build/Debug/net8.0/vpk" pack -u AvaloniaCrossPlat -v "$version" -o "$releasesDir" -p "$(dirname "$0")/publish" -i Velopack.png \ No newline at end of file +echo "" +echo "Building Velopack Release v$BUILD_VERSION" +vpk pack -u AvaloniaCrossPlat -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" -i "$ICON_PATH" \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/build-osx.sh b/examples/AvaloniaCrossPlat/build-osx.sh index 1a2450aa..5a7008d2 100755 --- a/examples/AvaloniaCrossPlat/build-osx.sh +++ b/examples/AvaloniaCrossPlat/build-osx.sh @@ -5,30 +5,20 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Check if version parameter is provided if [ "$#" -ne 1 ]; then - echo "Please provide a version number." - echo "Usage: ./build.sh version_number" + echo "Version number is required." + echo "Usage: ./build.sh [version]" exit 1 fi -echo "Building Velopack" -cd "$SCRIPT_DIR/../../src/Rust" -cargo build -cd "$SCRIPT_DIR/../.." -dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj -cd "$SCRIPT_DIR" - -version="$1" -releasesDir="$SCRIPT_DIR/releases" - -# Write to Const.cs -echo "class Const { public const string RELEASES_DIR = @\"$releasesDir\"; } " > "$(dirname "$0")/Const.cs" -echo "Const.cs file updated with releases directory ($releasesDir)." +BUILD_VERSION="$1" +RELEASE_DIR="$SCRIPT_DIR/releases" +PUBLISH_DIR="$SCRIPT_DIR/publish" +ICON_PATH="$SCRIPT_DIR/Velopack.icns" +echo "" echo "Compiling AvaloniaCrossPlat with dotnet..." -dotnet publish -c Release --self-contained -r osx-x64 -o "$(dirname "$0")/publish" +dotnet publish -c Release --self-contained -r osx-x64 -o "$PUBLISH_DIR" -echo "class Const { public const string RELEASES_DIR = @\"{REPLACE_ME}\"; } " > "$(dirname "$0")/Const.cs" -echo "Const.cs file reset" - -echo "Building Velopack Release v$version" -"$(dirname "$0")/../../build/Debug/net6.0/vpk" pack -u AvaloniaCrossPlat -v "$version" -o "$releasesDir" -p "$(dirname "$0")/publish" -i Velopack.icns \ No newline at end of file +echo "" +echo "Building Velopack Release v$BUILD_VERSION" +vpk pack -u AvaloniaCrossPlat -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" -i "$ICON_PATH" \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/build-win.bat b/examples/AvaloniaCrossPlat/build-win.bat index 0a479d9d..95cf5e92 100644 --- a/examples/AvaloniaCrossPlat/build-win.bat +++ b/examples/AvaloniaCrossPlat/build-win.bat @@ -1,32 +1,18 @@ @echo off setlocal enabledelayedexpansion -:: Check if version parameter is provided if "%~1"=="" ( - echo Please provide a version number. - echo Usage: build.bat version_number + echo Version number is required. + echo Usage: build.bat [version] exit /b 1 ) -echo Building Velopack -cd %~dp0..\..\src\Rust -cargo build --features windows -cd %~dp0..\..\ -dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj -cd %~dp0 - set "version=%~1" -set "releasesDir=%~dp0releases" - -:: Write to Const.cs -echo class Const { public const string RELEASES_DIR = @"%releasesDir%"; } > "%~dp0Const.cs" -echo Const.cs file updated with releases directory (%releasesDir%). +echo. echo Compiling AvaloniaCrossPlat with dotnet... dotnet publish -c Release --no-self-contained -r win-x64 -o %~dp0publish -echo class Const { public const string RELEASES_DIR = @"{REPLACE_ME}"; } > "%~dp0Const.cs" -echo Const.cs file reset - +echo. echo Building Velopack Release v%version% -%~dp0..\..\build\Debug\net6.0\vpk.exe pack -u AvaloniaCrossPlat -v %version% -o %releasesDir% -p %~dp0publish -f net8-x64-desktop \ No newline at end of file +vpk pack -u AvaloniaCrossPlat -v %version% -o %~dp0releases -p %~dp0publish -f net8-x64-desktop \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/dev-scripts/build-linux.sh b/examples/AvaloniaCrossPlat/dev-scripts/build-linux.sh new file mode 100644 index 00000000..830bcd59 --- /dev/null +++ b/examples/AvaloniaCrossPlat/dev-scripts/build-linux.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Find the absolute path of the script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check if version parameter is provided +if [ "$#" -ne 1 ]; then + echo "Please provide a version number." + echo "Usage: ./build.sh version_number" + exit 1 +fi + +echo "Building Velopack" +cd "$SCRIPT_DIR/../../src/Rust" +cargo build +cd "$SCRIPT_DIR/../.." +dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj +cd "$SCRIPT_DIR" + +version="$1" +releasesDir="$SCRIPT_DIR/releases" + +# Write to Const.cs +echo "class Const { public const string RELEASES_DIR = @\"$releasesDir\"; } " > "$(dirname "$0")/Const.cs" +echo "Const.cs file updated with releases directory ($releasesDir)." + +echo "Compiling AvaloniaCrossPlat with dotnet..." +dotnet publish -c Release --self-contained -r linux-x64 -o "$(dirname "$0")/publish" + +echo "class Const { public const string RELEASES_DIR = @\"{REPLACE_ME}\"; } " > "$(dirname "$0")/Const.cs" +echo "Const.cs file reset" + +echo "Building Velopack Release v$version" +"$(dirname "$0")/../../build/Debug/net8.0/vpk" pack -u AvaloniaCrossPlat -v "$version" -o "$releasesDir" -p "$(dirname "$0")/publish" -i Velopack.png \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/dev-scripts/build-osx.sh b/examples/AvaloniaCrossPlat/dev-scripts/build-osx.sh new file mode 100644 index 00000000..1a2450aa --- /dev/null +++ b/examples/AvaloniaCrossPlat/dev-scripts/build-osx.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Find the absolute path of the script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check if version parameter is provided +if [ "$#" -ne 1 ]; then + echo "Please provide a version number." + echo "Usage: ./build.sh version_number" + exit 1 +fi + +echo "Building Velopack" +cd "$SCRIPT_DIR/../../src/Rust" +cargo build +cd "$SCRIPT_DIR/../.." +dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj +cd "$SCRIPT_DIR" + +version="$1" +releasesDir="$SCRIPT_DIR/releases" + +# Write to Const.cs +echo "class Const { public const string RELEASES_DIR = @\"$releasesDir\"; } " > "$(dirname "$0")/Const.cs" +echo "Const.cs file updated with releases directory ($releasesDir)." + +echo "Compiling AvaloniaCrossPlat with dotnet..." +dotnet publish -c Release --self-contained -r osx-x64 -o "$(dirname "$0")/publish" + +echo "class Const { public const string RELEASES_DIR = @\"{REPLACE_ME}\"; } " > "$(dirname "$0")/Const.cs" +echo "Const.cs file reset" + +echo "Building Velopack Release v$version" +"$(dirname "$0")/../../build/Debug/net6.0/vpk" pack -u AvaloniaCrossPlat -v "$version" -o "$releasesDir" -p "$(dirname "$0")/publish" -i Velopack.icns \ No newline at end of file diff --git a/examples/AvaloniaCrossPlat/dev-scripts/build-win.bat b/examples/AvaloniaCrossPlat/dev-scripts/build-win.bat new file mode 100644 index 00000000..5d988ccc --- /dev/null +++ b/examples/AvaloniaCrossPlat/dev-scripts/build-win.bat @@ -0,0 +1,29 @@ +@echo off +setlocal enabledelayedexpansion + +if "%~1"=="" ( + echo Version number is required. + echo Usage: build.bat [version] + exit /b 1 +) + +echo. +echo Building Velopack Rust +cd %~dp0..\..\..\src\Rust +cargo build --features windows + +echo. +echo Building Velopack Vpk +cd %~dp0..\..\..\ +dotnet build src/Velopack.Vpk/Velopack.Vpk.csproj + +cd %~dp0.. +set "version=%~1" + +echo. +echo Compiling AvaloniaCrossPlat with dotnet... +dotnet publish -c Release --no-self-contained -r win-x64 -o publish -p:UseLocalVelopack=true + +echo. +echo Building Velopack Release v%version% +%~dp0..\..\..\build\Debug\net8.0\vpk pack -u AvaloniaCrossPlat -v %version% -o releases -p publish -f net8-x64-desktop \ No newline at end of file diff --git a/examples/VeloWpfSample/build.bat b/examples/VeloWpfSample/build.bat index 67ec3ddc..892bbaeb 100644 --- a/examples/VeloWpfSample/build.bat +++ b/examples/VeloWpfSample/build.bat @@ -9,8 +9,10 @@ if "%~1"=="" ( set "version=%~1" +echo. echo Compiling VeloWpfSample with dotnet... dotnet publish -c Release --no-self-contained -r win-x64 -o %~dp0publish +echo. echo Building Velopack Release v%version% vpk pack -u VeloWpfSample -v %version% -o %~dp0releases -p %~dp0publish -f net8-x64-desktop \ No newline at end of file