はじめに
AMDlab BIMソフトウェアエンジニアの河野(twitter@tatsukikouno)です。
キャッチアップ画像はリモートワークを手伝ってくれている猫のゲンジです。
今回は、GitHub Releaseをチェックして自動でRevitアドイン用のモジュールを置き換えるサンプルプログラムを作成してみたので、その紹介記事になります。
どちらかというと開発者目線の内容になりますが、一助になれば幸いです。
ソースコード全文はGitHubをご参照ください。
なお、本プログラムにはDLLファイルの自動ダウンロードと配置機能が含まれていますが、検証は行っておらず、悪意あるコードが含まれているDLLファイルをRevitが実行してしまう恐れがあります。
ご利用はあくまで自己責任でお願いいたします。
ストーリー
あなたが現在利用しているアドインのバージョンはv1.0.0.0です。
開発者が何かしらの変更を加えたv2.0.0.0を作成し、GitHub Releaseにアップロードしました。
アドイン起動時、現在バージョンより新しいバージョンがGitHub Releaseに見つかるので、それをダウンロードし、現在のv1.0.0.0と置き換えます。
なお、GitHub Releaseに紐づけられているTagをバージョンとして認識しています。
手順1:UpdateCheckerを用意する
GitHub Releaseの状態をチェックするクラスUpdateCheckerを用意します。
コンストラクタでHttpClientとGitHub APIのURL、アクセストークンを受け取り、 GetLatestVersionAsync() で最新バージョンのチェックを行うクラスです。
対象リポジトリがpublicの場合、アクセストークンはなくても最新リリース情報を取得できます。
対象リポジトリがprivateの場合、アクセストークンを使用して認証する必要があります。
なお、 _httpClient.SendAsync(request) についている .ConfigureAwait(false); の記述が気になるかもしれませんが、Revit起動時の OnStartup() 内で非同期メソッドを実行する上での工夫になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/// <summary> /// GitHub API を利用して最新リリースの tag_name を取得するクラス /// </summary> public class UpdateChecker { private readonly HttpClient _httpClient; private readonly string _githubApiUrl; private readonly string? _githubToken; public UpdateChecker(HttpClient httpClient, string githubApiUrl, string? githubToken = null) { _httpClient = httpClient; _githubApiUrl = githubApiUrl; _githubToken = githubToken; } // GitHub APIから最新のバージョン(tag_name)を取得 public async Task<Version> GetLatestVersionAsync() { var request = new HttpRequestMessage(HttpMethod.Get, _githubApiUrl); if (!string.IsNullOrEmpty(_githubToken)) { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("token", _githubToken); } var response = await _httpClient.SendAsync(request).ConfigureAwait(false); response.EnsureSuccessStatusCode(); // GitHub API のレスポンス例: { "tag_name": "v2.0.0.0", ... } var json = await response.Content.ReadAsStringAsync(); var obj = JObject.Parse(json); if (obj["tag_name"] is not JToken token) { throw new Exception("tag_name not found in response."); } var tagName = token.ToString(); if (tagName.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { tagName = tagName.Substring(1); } return Version.Parse(tagName); } } |
手順2:削除予定の古いバージョンのモジュールがあれば削除する
古いバージョンは、後述する手順4で {deleteFileName}.dll にリネームされます。
このタイミングで削除してしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var assembly = Assembly.GetExecutingAssembly(); var assemblyLocation = assembly.Location; if (assembly.GetName().Name is not string assemblyName) { throw new Exception("Failed to get assembly name."); } // {deleteFileName}.dll が存在する場合は削除 var deleteTarget = assemblyLocation.Replace(assemblyName, deleteFileName); if (File.Exists(deleteTarget)) { File.Delete(deleteTarget); } |
手順3:いい感じに前処理をしてから、UpdateCheckerで最新バージョンをチェックする
非同期処理を同期的に実行するため、
GetLatestVersionAsync().GetAwaiter().GetResult() と記述しています。
ただし、非同期処理を同期的に待機すると呼び出し元のスレッドがブロックされることがあるため、
GetLatestVersionAsync() 内のawait部分で
.ConfigureAwait(false)としています。
1 2 3 4 5 6 7 8 9 10 11 12 |
// HttpClient の初期化(GitHub API では User-Agent ヘッダーが必要) using var client = new HttpClient(); client.DefaultRequestHeaders.UserAgent.TryParseAdd(assemblyName); // UpdateChecker を初期化 var updateChecker = new UpdateChecker(client, githubApiUrl); // 現在のバージョンを取得 var currentVersion = Assembly.GetExecutingAssembly().GetName().Version; // 最新バージョンを取得(非同期処理を同期的に待機) var latestVersion = updateChecker.GetLatestVersionAsync().GetAwaiter().GetResult(); |
手順4:現在のバージョンより新しいバージョンが見つかったら、ダウンロードして配置する
Revit起動中、アドインのDLLを削除できないのは有名ですが、実はリネームは可能です。
現在のモジュールを {deleteFileName}.dll にリネームして、ダウンロードした新しいモジュールを配置します。
次回Revit起動時、手順1で古いモジュールを削除されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
if (latestVersion > currentVersion) { // 新しいアップデートがある場合、ユーザーに確認 var dialog = new TaskDialog("アップデート確認"); dialog.MainInstruction = $"新しいアップデート (v{latestVersion}) が利用可能です。"; dialog.MainContent = "アップデートを適用しますか?"; dialog.CommonButtons = TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No; TaskDialogResult result = dialog.Show(); if (result == TaskDialogResult.Yes) { // 最新リリースの AutoUpdateRevitAddin.dll をダウンロードする URL を構築 var downloadUrl = $"https://github.com/tatsukikouno/AutoUpdateRevitAddin/releases/download/v{latestVersion}/AutoUpdateRevitAddin.dll"; // 一時フォルダーにダウンロード(ファイル名は固定) var tempPath = Path.Combine(Path.GetTempPath(), "AutoUpdateRevitAddin.dll"); var fileBytes = client.GetByteArrayAsync(downloadUrl).GetAwaiter().GetResult(); File.WriteAllBytes(tempPath, fileBytes); // 現在のアセンブリを{deleteFileName}.dllに変更 File.Move(assemblyLocation, deleteTarget); File.Move(tempPath, assemblyLocation); TaskDialog.Show("アップデート確認", "最新バージョンのダウンロードが完了しました。アドインはRevit再起動時に更新されます。"); } } else { TaskDialog.Show("アップデート確認", "最新バージョンを利用しています。"); } |
おわりに
今回は触れませんでしたが、GitHub Actionsを利用することでGitHub Releaseへのデプロイも自動化できます。
そうすれば、開発者はpushするだけで利用者に更新を通知できることになり、大変便利そうです。
サーバーを用意しなくてよいのも、中小規模なプロジェクトが多いRevitアドインで気軽に採用できてよさそうです。
AMDlabでは、開発に力を貸していただけるエンジニアさんを大募集しております。
少しでもご興味をお持ちいただけましたら、カジュアルにお話するだけでも大丈夫ですのでお気軽にご連絡ください!
中途求人ページ: https://www.amd-lab.com/recruit-list/mid-career
カジュアル面談がエントリーフォームからできるようになりました。
採用種別を「カジュアル面談(オンライン)」にして必要事項を記載の上送信してください!
エントリーフォーム: https://www.amd-lab.com/entry
COMMENTS