Files
ClassicAssist.Avalonia/ClassicAssist.Updater/MainViewModel.cs
T
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

280 lines
9.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows.Threading;
using ClassicAssist.Updater.Annotations;
using ClassicAssist.Updater.Properties;
using Exceptionless;
using Exceptionless.Models;
using Octokit;
using Application = System.Windows.Application;
namespace ClassicAssist.Updater
{
public class MainViewModel : INotifyPropertyChanged
{
private readonly Dispatcher _dispatcher;
private long _downloadSize;
private bool _isIndeterminate = true;
private bool _isUpdating;
private ObservableCollection<string> _items = new ObservableCollection<string>();
public MainViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
Task.Run( async () =>
{
if ( App.CurrentOptions.Stage == UpdaterStage.Initial )
{
string path = await Task.Run( CheckForUpdate );
if ( !string.IsNullOrEmpty( path ) )
{
App.CurrentOptions.UpdatePath = path;
}
}
if ( !string.IsNullOrEmpty( App.CurrentOptions.UpdatePath ) )
{
IsUpdating = true;
AddText( Resources.Updating_files___ );
DirectoryInfo source = new DirectoryInfo( App.CurrentOptions.UpdatePath );
DirectoryInfo destination = new DirectoryInfo( App.CurrentOptions.Path );
CopyAll( source, destination );
AddText( Resources.Done_ );
IsUpdating = false;
}
} );
}
public long DownloadSize
{
get => _downloadSize;
set
{
SetProperty( ref _downloadSize, value );
IsIndeterminate = value == 0;
}
}
public bool IsIndeterminate
{
get => _isIndeterminate;
set => SetProperty( ref _isIndeterminate, value );
}
public bool IsUpdating
{
get => _isUpdating;
set => SetProperty( ref _isUpdating, value );
}
public ObservableCollection<string> Items
{
get => _items;
set => SetProperty( ref _items, value );
}
public event PropertyChangedEventHandler PropertyChanged;
//https://stackoverflow.com/questions/627504/what-is-the-best-way-to-recursively-copy-contents-in-c/627518#627518
public void CopyAll( DirectoryInfo source, DirectoryInfo target )
{
//check if the target directory exists
if ( Directory.Exists( target.FullName ) == false )
{
Directory.CreateDirectory( target.FullName );
}
//copy all the files into the new directory
foreach ( FileInfo fi in source.GetFiles() )
{
try
{
fi.CopyTo( Path.Combine( target.ToString(), fi.Name ), true );
AddText( string.Format( Resources.Copying__0____, fi.Name ) );
}
catch ( IOException ie )
{
//handle it here
AddText( ie.Message );
}
}
//copy all the sub directories using recursion
foreach ( DirectoryInfo diSourceDir in source.GetDirectories() )
{
DirectoryInfo nextTargetDir = target.CreateSubdirectory( diSourceDir.Name );
CopyAll( diSourceDir, nextTargetDir );
}
}
private async Task<string> CheckForUpdate()
{
try
{
IsUpdating = true;
AddText( Resources.Checking_for_latest_release___ );
GitHubClient client = new GitHubClient( new ProductHeaderValue( "ClassicAssist" ) );
IReadOnlyList<Release> releases =
await client.Repository.Release.GetAll( Settings.Default.RepositoryOwner,
Settings.Default.RepositoryName );
Release latestRelease = releases?.FirstOrDefault();
if ( latestRelease == null )
{
throw new InvalidOperationException( Resources.Unable_to_locate_GitHub_release );
}
AddText( $"{Resources.Latest_Release_} {latestRelease.TagName}" );
Version newVersion = Version.Parse( latestRelease.TagName );
if ( newVersion > App.CurrentOptions.CurrentVersion || App.CurrentOptions.Force )
{
ExceptionlessClient.Default.SubmitEvent( new Event
{
Message =
$"Update: Previous version: {App.CurrentOptions.CurrentVersion}, New version: {newVersion}"
} );
if ( App.CurrentOptions.PID != 0 )
{
Process p = Process.GetProcessById( App.CurrentOptions.PID );
p.Kill();
await Task.Delay( 2000 );
}
if ( latestRelease.Assets.Count == 0 )
{
throw new InvalidOperationException( Resources.No_release_asset___ );
}
ReleaseAsset assest = latestRelease.Assets.FirstOrDefault( a =>
a.Name.EndsWith( ".zip", StringComparison.InvariantCultureIgnoreCase ) );
if ( assest == null )
{
AddText( Resources.Cannot_locate_update_package___ );
return null;
}
AddText( $"{Resources.Downloading} {assest.Name}..." );
string fileName = await DownloadFile( assest.BrowserDownloadUrl, assest.Size );
AddText( Resources.Extracting_package___ );
string updatePath = await ExtractPackage( fileName, newVersion );
if ( !File.Exists( Path.Combine( updatePath, "ClassicAssist.Updater.exe" ) ) )
{
return updatePath;
}
// if directory contains updater...
ProcessStartInfo psi = new ProcessStartInfo(
Path.Combine( updatePath, "ClassicAssist.Updater.exe" ),
$"--stage Install --updatepath {updatePath} --path {App.CurrentOptions.Path}" );
Process.Start( psi );
_dispatcher.Invoke( () => Application.Current.Shutdown() );
return null;
}
else
{
AddText( Resources.No_new_release_available___ );
}
}
catch ( Exception e )
{
AddText( $"{Resources.Error_} {e.Message}" );
}
finally
{
IsUpdating = false;
}
return null;
}
private static async Task<string> ExtractPackage( string fileName, Version newVersion )
{
string path = Path.Combine( Path.GetTempPath(), $"CAUpdate-{newVersion}" );
try
{
await Task.Run( () => ZipFile.ExtractToDirectory( fileName, path ) );
}
catch ( Exception )
{
// ignored
}
return path;
}
private static async Task<string> DownloadFile( string browserDownloadUrl, int size )
{
using ( HttpClient http = new HttpClient() )
{
http.Timeout = TimeSpan.FromMinutes( 5 );
string fileName = Path.Combine( Environment.CurrentDirectory, "Update.zip" );
http.DefaultRequestHeaders.Add( "User-Agent", "ClassicAssist Updater" );
byte[] response = await http.GetByteArrayAsync( browserDownloadUrl );
if ( response.Length != size )
{
throw new InvalidOperationException( Resources.Downloaded_size_doesn_t_match_expected___ );
}
File.WriteAllBytes( fileName, response );
return fileName;
}
}
private void AddText( string message )
{
_dispatcher.Invoke( () => Items.Add( message ) );
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
// ReSharper disable once RedundantAssignment
private void SetProperty<T>( ref T obj, T value, [CallerMemberName] string propertyName = null )
{
obj = value;
OnPropertyChanged( propertyName );
}
}
}