Skip to content

Commit

Permalink
tweak update check & self-update
Browse files Browse the repository at this point in the history
  • Loading branch information
cfbao committed Jan 3, 2024
1 parent 994a174 commit 7159d7e
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 102 deletions.
2 changes: 1 addition & 1 deletion src/D2L.Bmx/BmxPaths.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ internal static class BmxPaths {
public static readonly string SESSIONS_FILE_LEGACY_NAME = Path.Join( BMX_DIR, "sessions" );
public static readonly string UPDATE_CHECK_FILE_NAME = Path.Join( CACHE_DIR, "updateCheck" );
public static readonly string AWS_CREDS_CACHE_FILE_NAME = Path.Join( CACHE_DIR, "awsCreds" );
public static readonly string OLD_BMX_VERSIONS_PATH = Path.Join( BMX_DIR, "temp" );
public static readonly string TEMP_DIR = Path.Join( BMX_DIR, "temp" );
}
12 changes: 9 additions & 3 deletions src/D2L.Bmx/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,16 @@
throw new BmxException( "Failed to initialize BMX directory (~/.bmx)", ex );
}
}
// clean up temp files from earlier runs
if( Directory.Exists( BmxPaths.TEMP_DIR ) ) {
try {
Directory.Delete( BmxPaths.TEMP_DIR, recursive: true );
} catch( Exception ) {
// ignore errors as this doesn't impact normal BMX usage
}
}

UpdateHandler.Cleanup();

var updateChecker = new UpdateChecker( github, config );
var updateChecker = new UpdateChecker( github );
await updateChecker.CheckForUpdatesAsync();

await next( context );
Expand Down
63 changes: 32 additions & 31 deletions src/D2L.Bmx/UpdateChecker.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json;
using D2L.Bmx.GitHub;

namespace D2L.Bmx;

internal class UpdateChecker( IGitHubClient github, BmxConfig config ) {
internal class UpdateChecker( IGitHubClient github ) {
public async Task CheckForUpdatesAsync() {
try {
var cachedVersion = GetUpdateCheckCache();
var localVersion = Assembly.GetExecutingAssembly().GetName().Version;
var latestVersion = new Version( cachedVersion?.VersionName ?? "0.0.0" );
if( ShouldFetchLatestVersion( cachedVersion ) ) {
var releaseData = await github.GetLatestBmxReleaseAsync();
latestVersion = releaseData.Version;
var updateCheckCache = GetUpdateCheckCacheOrNull();

Version? latestVersion;
if( ShouldFetchLatestVersion( updateCheckCache ) ) {
latestVersion = ( await github.GetLatestBmxReleaseAsync() ).Version;
if( latestVersion is null ) {
return;
}
SetUpdateCheckCache( latestVersion );
} else {
latestVersion = updateCheckCache.VersionName;
if( latestVersion is null ) {
return;
}
SaveVersion( latestVersion );
}

string updateLocation = string.Equals( config.Org, "d2l", StringComparison.OrdinalIgnoreCase )
? "https://bmx.d2l.dev"
: "https://github.com/Brightspace/bmx/releases/latest";

Version? localVersion = Assembly.GetExecutingAssembly().GetName().Version;
if( latestVersion > localVersion ) {
DisplayUpdateMessage(
$"""
A new BMX release is available: v{latestVersion} (current: v{localVersion})
Upgrade now at {updateLocation}
Run "bmx update" now to upgrade.
"""
);
}
Expand Down Expand Up @@ -58,11 +60,11 @@ private static void DisplayUpdateMessage( string message ) {
Console.Error.WriteLine();
}

private static void SaveVersion( Version version ) {
var cache = new UpdateCheckCache {
VersionName = version.ToString(),
TimeLastChecked = DateTimeOffset.UtcNow
};
private static void SetUpdateCheckCache( Version version ) {
var cache = new UpdateCheckCache(
VersionName: version,
TimeLastChecked: DateTimeOffset.UtcNow
);
string content = JsonSerializer.Serialize( cache, JsonCamelCaseContext.Default.UpdateCheckCache );
var op = new FileStreamOptions {
Mode = FileMode.OpenOrCreate,
Expand All @@ -76,7 +78,7 @@ private static void SaveVersion( Version version ) {
writer.Write( content );
}

private static UpdateCheckCache? GetUpdateCheckCache() {
private static UpdateCheckCache? GetUpdateCheckCacheOrNull() {
if( !File.Exists( BmxPaths.UPDATE_CHECK_FILE_NAME ) ) {
return null;
}
Expand All @@ -89,19 +91,18 @@ private static void SaveVersion( Version version ) {
}
}

private static bool ShouldFetchLatestVersion( UpdateCheckCache? cache ) {
if( cache is null || string.IsNullOrWhiteSpace( cache.VersionName )
|| ( DateTimeOffset.UtcNow - cache.TimeLastChecked ) > TimeSpan.FromDays( 1 )
|| ( cache.TimeLastChecked > DateTimeOffset.UtcNow )
) {
return true;
}
return false;
private static bool ShouldFetchLatestVersion(
[NotNullWhen( returnValue: false )] UpdateCheckCache? cache
) {
return cache?.VersionName is null
|| cache.TimeLastChecked is null
|| DateTimeOffset.UtcNow - cache.TimeLastChecked > TimeSpan.FromDays( 1 )
|| cache.TimeLastChecked > DateTimeOffset.UtcNow;
}
}


internal record UpdateCheckCache {
public string? VersionName { get; set; }
public DateTimeOffset? TimeLastChecked { get; set; }
}
internal record UpdateCheckCache(
Version? VersionName,
DateTimeOffset? TimeLastChecked
);
136 changes: 69 additions & 67 deletions src/D2L.Bmx/UpdateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,115 +8,105 @@ namespace D2L.Bmx;

internal class UpdateHandler( IGitHubClient github ) {
public async Task HandleAsync() {
if( !Directory.Exists( BmxPaths.OLD_BMX_VERSIONS_PATH ) ) {
try {
Directory.CreateDirectory( BmxPaths.OLD_BMX_VERSIONS_PATH );
} catch( Exception ex ) {
throw new BmxException( "Failed to initialize temporary BMX file directory (~/.bmx/temp)", ex );
}
string workspaceDir = Path.Join( BmxPaths.TEMP_DIR, Path.GetRandomFileName() );
try {
Directory.CreateDirectory( workspaceDir );
} catch( Exception ex ) {
throw new BmxException( "Failed to create temporary BMX directory in ~/.bmx/temp", ex );
}

GitHubRelease releaseData;
var bmxFileInfo = GetFileInfo();

Console.WriteLine( "Finding the latest BMX release..." );
GitHubRelease latestRelease;
try {
releaseData = await github.GetLatestBmxReleaseAsync();
latestRelease = await github.GetLatestBmxReleaseAsync();
} catch {
throw new BmxException( "Failed to find the latest BMX release." );
}
Version latestVersion = releaseData.Version
Version latestVersion = latestRelease.Version
?? throw new BmxException( "Failed to find the latest version of BMX." );

var localVersion = Assembly.GetExecutingAssembly().GetName().Version;
if( latestVersion <= localVersion ) {
Console.WriteLine( $"You already have the latest version {latestVersion}" );
return;
}

string archiveName = GetOSFileName();
var asset = releaseData?.Assets.Find( a => a.Name == archiveName )
var asset = latestRelease?.Assets.Find( a => a.Name == bmxFileInfo.ArchiveName )
?? throw new BmxException( "Failed to find the download URL of the latest BMX" );

string? currentFilePath = Environment.ProcessPath;
if( string.IsNullOrEmpty( currentFilePath ) ) {
throw new BmxException( "BMX could not update" );
}

string downloadPath = Path.GetTempFileName();
Console.WriteLine( "Downloading the latest BMX..." );
string downloadPath = Path.Join( workspaceDir, bmxFileInfo.ArchiveName );
try {
await github.DownloadAssetAsync( asset, downloadPath );
} catch( Exception ex ) {
throw new BmxException( "Failed to download the update", ex );
}

string extractFolder = Path.Combine( Path.GetTempPath(), Path.GetRandomFileName() );
Console.WriteLine( "Extracting files from archive..." );
string extractFolder = Path.Join( workspaceDir, "latest" );
try {
Directory.CreateDirectory( extractFolder );
} catch( Exception ex ) {
File.Delete( downloadPath );
throw new BmxException( "Failed to initialize temporary folder for downloaded file", ex );
}

string currentDirectory = Path.GetDirectoryName( currentFilePath )!;
long backupPathTimeStamp = DateTime.Now.Millisecond;
string backupPath = Path.Join( BmxPaths.OLD_BMX_VERSIONS_PATH, $"bmx-v{localVersion}-{backupPathTimeStamp}-old.bak" );
try {
string extension = Path.GetExtension( archiveName );

if( extension.Equals( ".zip", StringComparison.OrdinalIgnoreCase ) ) {
if( bmxFileInfo.ArchiveType == ArchiveType.Zip ) {
ExtractZipFile( downloadPath, extractFolder );
} else if( extension.Equals( ".gz", StringComparison.OrdinalIgnoreCase ) ) {
ExtractTarGzipFile( downloadPath, extractFolder );
} else {
throw new Exception( "Unknown archive type" );
ExtractTarGzipFile( downloadPath, extractFolder );
}
} catch( Exception ex ) {
Directory.Delete( extractFolder, recursive: true );
throw new BmxException( "Failed to update with new files", ex );
} finally {
File.Delete( downloadPath );
throw new BmxException( "Failed to extract from downloaded archive", ex );
}

Console.WriteLine( "Replacing the currently running BMX executable..." );
string currentFilePath = Environment.ProcessPath
?? throw new BmxException( "Failed to locate the current BMX executable." );
string backupPath = Path.Join( workspaceDir, $"bmx-v{localVersion}-old.bak" );
try {
File.Move( currentFilePath, backupPath );
} catch( IOException ex ) {
Directory.Delete( extractFolder, recursive: true );
throw new BmxException( "Could not remove the old version. Please try again with elevated permissions.", ex );
} catch {
Directory.Delete( extractFolder, recursive: true );
throw new BmxException( "BMX could not update" );
} catch( Exception ex ) {
throw new BmxException( "Failed to back up the old version", ex );
}

string newFilePath = Path.Join( extractFolder, bmxFileInfo.ExeName );
try {
foreach( string file in Directory.GetFiles( extractFolder ) ) {
string destinationFile = Path.Combine( currentDirectory, Path.GetFileName( file ) );
File.Move( file, destinationFile );
}
File.Move( newFilePath, currentFilePath );
} catch( Exception ex ) {
File.Move( backupPath, currentFilePath );
throw new BmxException( "BMX could not update with the new version", ex );
} finally {
Directory.Delete( extractFolder, recursive: true );
string errorMessage = "Failed to update with the new version.";
try {
File.Move( backupPath, currentFilePath );
} catch {
errorMessage += $"""
Failed to restore the backup.
Your BMX executable is now at {backupPath}. Please restore manually.
""";
}
throw new BmxException( errorMessage, ex );
}
}

private static string GetOSFileName() {
private static BmxFileInfo GetFileInfo() {
if( RuntimeInformation.IsOSPlatform( OSPlatform.Windows ) ) {
return "bmx-win-x64.zip";
return new BmxFileInfo(
ArchiveName: "bmx-win-x64.zip",
ExeName: "bmx.exe",
ArchiveType: ArchiveType.Zip
);
} else if( RuntimeInformation.IsOSPlatform( OSPlatform.OSX ) ) {
return "bmx-osx-x64.tar.gz";
return new BmxFileInfo(
ArchiveName: "bmx-osx-x64.tar.gz",
ExeName: "bmx",
ArchiveType: ArchiveType.TarGzip
);
} else if( RuntimeInformation.IsOSPlatform( OSPlatform.Linux ) ) {
return "bmx-linux-x64.tar.gz";
return new BmxFileInfo(
ArchiveName: "bmx-linux-x64.tar.gz",
ExeName: "bmx",
ArchiveType: ArchiveType.TarGzip
);
} else {
throw new BmxException( "New version does not support you current OS" );
}
}

public static void Cleanup() {
if( Directory.Exists( BmxPaths.OLD_BMX_VERSIONS_PATH ) ) {
try {
Directory.Delete( BmxPaths.OLD_BMX_VERSIONS_PATH, recursive: true );
} catch( Exception ) {
Console.Error.WriteLine( "WARNING: Failed to delete old version files" );
}
throw new BmxException(
"Unable to choose the appropriate file for your current OS. Please update manually." );
}
}

Expand All @@ -134,7 +124,7 @@ private static void ExtractTarGzipFile( string compressedFilePath, string decomp
}

try {
TarFile.ExtractToDirectory( tarPath, decompressedFilePath, true );
TarFile.ExtractToDirectory( tarPath, decompressedFilePath, overwriteFiles: true );
} finally {
File.Delete( tarPath );
}
Expand All @@ -149,4 +139,16 @@ private static void ExtractZipFile( string compressedFilePath, string decompress
}
}
}


private readonly record struct BmxFileInfo(
string ArchiveName,
string ExeName,
ArchiveType ArchiveType
);

private enum ArchiveType {
TarGzip,
Zip,
}
}

0 comments on commit 7159d7e

Please sign in to comment.