diff --git a/qtcnet-client/Factories/ImageFactory.cs b/qtcnet-client/Factories/ImageFactory.cs index 9cc980d..858674b 100644 --- a/qtcnet-client/Factories/ImageFactory.cs +++ b/qtcnet-client/Factories/ImageFactory.cs @@ -17,11 +17,11 @@ namespace qtcnet_client.Factories _apiService = apiService; } - public async Task GetAndCreateProfileImage(string userId, int status, int cosmeticStoreId = 0) + public async Task GetAndCreateProfileImage(string userId, int status = -1, int cosmeticStoreId = 0) { Bitmap combined = new(139, 138); - Image img1 = Resources.DefaultPfp; + Bitmap img1 = Resources.DefaultPfp; Bitmap? img2 = null; Bitmap img3 = Resources.OfflineIcon; @@ -30,7 +30,7 @@ namespace qtcnet_client.Factories if (_pfpRes.Success && _pfpRes.Data != null) { using var ms = new MemoryStream(_pfpRes.Data); - img1 = Image.FromStream(ms); + img1 = (Bitmap)Image.FromStream(ms); } // get cosmetic image @@ -51,20 +51,23 @@ namespace qtcnet_client.Factories } // set status image - switch(status) + if(!int.IsNegative(status)) { - case 0: - img3 = Resources.OfflineIcon; - break; - case 1: - img3 = Resources.OnlineIcon; - break; - case 2: - img3 = Resources.AwayIcon; - break; - case 3: - img3 = Resources.DNDIcon; - break; + switch (status) + { + case 0: + img3 = Resources.OfflineIcon; + break; + case 1: + img3 = Resources.OnlineIcon; + break; + case 2: + img3 = Resources.AwayIcon; + break; + case 3: + img3 = Resources.DNDIcon; + break; + } } // finally, combine the images @@ -80,9 +83,11 @@ namespace qtcnet_client.Factories g.DrawImage(img2, 0, 0, 139, 138); } - - img3.MakeTransparent(); - g.DrawImage(img3, 104, 0, 35, 35); + if(!int.IsNegative(status)) + { + img3.MakeTransparent(); + g.DrawImage(img3, 104, 0, 35, 35); + } return combined; } diff --git a/qtcnet-client/Forms/ChatRoomForm.cs b/qtcnet-client/Forms/ChatRoomForm.cs index 6d4cc0b..3a835c1 100644 --- a/qtcnet-client/Forms/ChatRoomForm.cs +++ b/qtcnet-client/Forms/ChatRoomForm.cs @@ -1,10 +1,12 @@ using qtcnet_client.Controls; +using qtcnet_client.Factories; using QtCNETAPI.Dtos.User; using QtCNETAPI.Events; using QtCNETAPI.Models; using QtCNETAPI.Services.ApiService; using QtCNETAPI.Services.GatewayService; using System.ComponentModel; +using System.Reflection.Metadata.Ecma335; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace qtcnet_client.Forms @@ -16,7 +18,7 @@ namespace qtcnet_client.Forms [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public string RoomName { get; set; } = "Room"; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public Dictionary UserProfileImages { get; set; } = []; + public Dictionary UserProfileImages { get; set; } = []; public string SentMessage { get; private set; } = string.Empty; public event EventHandler? OnSend; @@ -24,11 +26,13 @@ namespace qtcnet_client.Forms private readonly IGatewayService _gatewayService; private readonly IApiService _apiService; + private readonly ImageFactory _imgFactory; - public ChatRoomForm(IGatewayService gatewayService, IApiService apiService) + public ChatRoomForm(IGatewayService gatewayService, IApiService apiService, ImageFactory imageFactory) { _gatewayService = gatewayService; _apiService = apiService; + _imgFactory = imageFactory; InitializeComponent(); } @@ -127,11 +131,11 @@ namespace qtcnet_client.Forms lvUsers.Items.Add(lvi); } - public async Task LoadProfileImage(MessageControl ctrl, string userId) + public async Task LoadProfileImage(MessageControl ctrl, User user) { _ = Task.Run(async () => { - var img = await AddUserProfileImageToDictionary(userId); + var img = await AddUserProfileImageToDictionary(user); if (img == null || ctrl.IsDisposed) return; @@ -150,22 +154,15 @@ namespace qtcnet_client.Forms }); } - private async Task AddUserProfileImageToDictionary(string userId) + private async Task AddUserProfileImageToDictionary(User user) { - if(UserProfileImages.TryGetValue(userId, out var image)) + if(UserProfileImages.TryGetValue(user.Id, out var image)) return image; else { - var _res = await _apiService.GetUserProfilePic(userId); - if (_res.Success && _res.Data != null) - { - using var ms = new MemoryStream(_res.Data); - Image img = Image.FromStream(ms); - UserProfileImages.Add(userId, img); - return UserProfileImages[userId]; - } - else - return default; + var img = await _imgFactory.GetAndCreateProfileImage(user.Id, -1, user.ActiveProfileCosmetic); + UserProfileImages.Add(user.Id, img); + return img; } } diff --git a/qtcnet-client/Forms/MainForm.cs b/qtcnet-client/Forms/MainForm.cs index fb5bee9..11c0eab 100644 --- a/qtcnet-client/Forms/MainForm.cs +++ b/qtcnet-client/Forms/MainForm.cs @@ -6,6 +6,7 @@ using qtcnet_client.Factories; using qtcnet_client.Forms; using qtcnet_client.Model; using qtcnet_client.Properties; +using qtcnet_client.Services; using QtCNETAPI.Dtos.User; using QtCNETAPI.Events; using QtCNETAPI.Models; @@ -13,6 +14,7 @@ using QtCNETAPI.Services; using QtCNETAPI.Services.ApiService; using QtCNETAPI.Services.GatewayService; using System.Drawing.Drawing2D; +using System.Runtime.ExceptionServices; using System.Threading.Tasks; namespace qtcnet_client @@ -39,8 +41,15 @@ namespace qtcnet_client private readonly CredentialService _credentialService; private readonly ClientConfig _config; private readonly ImageFactory _imgFactory; + private readonly UpdateService _updateService; - public MainForm(IApiService apiService, IGatewayService gatewayService, LoggingService loggingService, CredentialService credentialService, ClientConfig config, ImageFactory imageFactory) + public MainForm(IApiService apiService, + IGatewayService gatewayService, + LoggingService loggingService, + CredentialService credentialService, + ClientConfig config, + ImageFactory imageFactory, + UpdateService updateService) { _apiService = apiService; _gatewayService = gatewayService; @@ -48,6 +57,7 @@ namespace qtcnet_client _credentialService = credentialService; _config = config; _imgFactory = imageFactory; + _updateService = updateService; // sub to currentuser updates _apiService.OnCurrentUserUpdate += _apiService_OnCurrentUserUpdate; @@ -72,6 +82,9 @@ namespace qtcnet_client private async void MainForm_Load(object sender, EventArgs e) { + // first check for updates + await _updateService.CheckForUpdatesAsync(); + SuspendLayout(); Size = LoggedOutSize; @@ -237,7 +250,7 @@ namespace qtcnet_client } // open the room form - ChatRoomForm _chatRoom = new(_gatewayService, _apiService) + ChatRoomForm _chatRoom = new(_gatewayService, _apiService, _imgFactory) { RoomId = _roomCtrl.RoomId, RoomName = _roomCtrl.RoomName, @@ -569,6 +582,9 @@ namespace qtcnet_client if (_res) { + if (_apiService.CurrentUser.Status == 0) + await _gatewayService.UpdateStatus(1); + // setup current profile control based on current user CurrentProfileControl = new() { @@ -581,13 +597,7 @@ namespace qtcnet_client CurrentProfileControl.OnSignOutClicked += CurrentProfileControl_OnSignOutClicked; // get profile image for the current user - var _currentProfileImageRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id); - if (_currentProfileImageRes.Success && _currentProfileImageRes.Data != null) - { - using MemoryStream ms = new(_currentProfileImageRes.Data); - Image img = Image.FromStream(ms); - CurrentProfileControl.ProfileImage = img; - } + CurrentProfileControl.ProfileImage = await _imgFactory.GetAndCreateProfileImage(_apiService.CurrentUser.Id, _apiService.CurrentUser.Status, _apiService.CurrentUser.ActiveProfileCosmetic); // setup main tab control MainTabControl = new() @@ -791,7 +801,7 @@ namespace qtcnet_client _chatRoom.AddMessage(_msg); if(_args.User.Id != "0") - await _chatRoom.LoadProfileImage(_msg, _args.User.Id); + await _chatRoom.LoadProfileImage(_msg, _args.User); } } } diff --git a/qtcnet-client/Model/ClientUpdateInfo.cs b/qtcnet-client/Model/ClientUpdateInfo.cs new file mode 100644 index 0000000..abbf817 --- /dev/null +++ b/qtcnet-client/Model/ClientUpdateInfo.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace qtcnet_client.Model +{ + public class ClientUpdateInfo + { + [JsonPropertyName("clientFileName")] + [JsonRequired] + public string ClientFileName { get; set; } = "QtCNetChat.exe"; + [JsonPropertyName("version")] + [JsonRequired] + public string Version { get; set; } = string.Empty; + [JsonPropertyName("downloadUri")] + [JsonRequired] + public string DownloadUrl { get; set; } = string.Empty; + [JsonPropertyName("updateMandatory")] + [JsonRequired] + public bool IsUpdateMandatory { get; set; } = false; + } +} diff --git a/qtcnet-client/Program.cs b/qtcnet-client/Program.cs index 8bfcb5e..d028236 100644 --- a/qtcnet-client/Program.cs +++ b/qtcnet-client/Program.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using qtcnet_client.Factories; using qtcnet_client.Model; +using qtcnet_client.Services; using QtCNETAPI.Services; using QtCNETAPI.Services.ApiService; using QtCNETAPI.Services.GatewayService; @@ -39,6 +40,7 @@ namespace qtcnet_client service.AddSingleton(); service.AddSingleton(); service.AddSingleton(); + service.AddSingleton(); service.AddSingleton(provider => GetOrCreateClientConfig()); service.AddSingleton(provider => new ApiService(provider.GetService()!.ServerBaseUri + "/api", provider.GetService()!, provider.GetService()!)); service.AddSingleton(provider => new GatewayService(provider.GetService()!.ServerBaseUri + "/chat", provider.GetService()!, provider.GetService()!)); diff --git a/qtcnet-client/Properties/Resources.Designer.cs b/qtcnet-client/Properties/Resources.Designer.cs index ff5e0d8..83a542c 100644 --- a/qtcnet-client/Properties/Resources.Designer.cs +++ b/qtcnet-client/Properties/Resources.Designer.cs @@ -80,6 +80,15 @@ namespace qtcnet_client.Properties { } } + /// + /// Looks up a localized string similar to 1.0.0. + /// + internal static string AssemblyVersion { + get { + return ResourceManager.GetString("AssemblyVersion", resourceCulture); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/qtcnet-client/Properties/Resources.resx b/qtcnet-client/Properties/Resources.resx index f76589f..b5cad6e 100644 --- a/qtcnet-client/Properties/Resources.resx +++ b/qtcnet-client/Properties/Resources.resx @@ -166,4 +166,7 @@ ..\Resources\DNDIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + 1.0.0 + \ No newline at end of file diff --git a/qtcnet-client/Services/UpdateService.cs b/qtcnet-client/Services/UpdateService.cs new file mode 100644 index 0000000..a92d1f3 --- /dev/null +++ b/qtcnet-client/Services/UpdateService.cs @@ -0,0 +1,117 @@ +using qtcnet_client.Properties; +using qtcnet_client.Model; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; + +namespace qtcnet_client.Services +{ + public class UpdateService + { + public async Task CheckForUpdatesAsync() + { + if (Debugger.IsAttached) return; + + // get client update info + HttpClient client = new() + { + BaseAddress = new Uri("https://qtcclient.alanmoon.net") + }; + + try + { + ClientUpdateInfo? updateInfo = await client.GetFromJsonAsync("clientinfo.json"); + if (updateInfo != null) + { + var serverVersion = Version.Parse(updateInfo.Version); + var clientVersion = Version.Parse(Resources.AssemblyVersion); + + int relativeComparison = clientVersion.CompareTo(serverVersion); + + if (relativeComparison > 0) return; // probably a version being developed or tested without a debugger, so don't do anything + + if (relativeComparison < 0) + { + // inform the user an update is available + if (!updateInfo.IsUpdateMandatory) + { + var result = MessageBox.Show( + $"An Update For QtC.NET Is Available. You Can View The Changelog At {updateInfo.DownloadUrl}. Do You Want To Update Now?", + "Update Available", + MessageBoxButtons.YesNo, + MessageBoxIcon.Information); + if (result == DialogResult.Yes) await UpdateAsync(updateInfo); + } + else + { + var result = MessageBox.Show( + "Your QtC.NET Client Is Not Up To Date. Using This Version Of The Client With The Current Version Of The Server Could Lead To Data Loss.\n" + + $"You Can View The Changelog At {updateInfo.DownloadUrl}. Do You Want To Update Now?", + "Update Required", + MessageBoxButtons.YesNo, + MessageBoxIcon.Error); + if (result == DialogResult.Yes) await UpdateAsync(updateInfo); + else Environment.Exit(1); + } + } + } + } + catch (HttpRequestException ex) + { + Debug.WriteLine("Client Update HTTP Request Failed - " + ex.Message); + } + catch (JsonException) + { + Debug.WriteLine("Client Update Info From Server Invalid"); + } + } + + private async Task UpdateAsync(ClientUpdateInfo updateInfo) + { + HttpClient client = new(); + client.BaseAddress = new Uri("https://qtcclient.alanmoon.net"); + + try + { + // if bak file already exists, delete it + if (File.Exists($"/{updateInfo.ClientFileName}.bak")) File.Delete($"/{updateInfo.ClientFileName}.bak"); + + // move old client to backup file + File.Move($"./{updateInfo.ClientFileName}", $"{updateInfo.ClientFileName}.bak"); + + // download new client version + var clientFileStream = await client.GetStreamAsync(updateInfo.ClientFileName); + using (var fs = new FileStream($"./{updateInfo.ClientFileName}", FileMode.Create)) + { + clientFileStream.CopyTo(fs); + fs.Dispose(); + } + clientFileStream.Dispose(); + + // restart the process + Process process = new Process(); + process.StartInfo = new ProcessStartInfo { FileName = $"./{updateInfo.ClientFileName}", WorkingDirectory = Environment.CurrentDirectory }; + + process.Start(); + Environment.Exit(0); + } + catch (HttpRequestException ex) + { + MessageBox.Show($"Update Failed. Please Check Your Internet Connection.\n{ex.Message}", + "Update Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + catch (UnauthorizedAccessException ex) + { + MessageBox.Show($"Update Failed. Permissions In Client Folder Are Wrong.\n{ex.Message}", + "Update Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + } + } +} diff --git a/qtcnet-client/qtcnet-client.csproj b/qtcnet-client/qtcnet-client.csproj index 749567c..22e63c2 100644 --- a/qtcnet-client/qtcnet-client.csproj +++ b/qtcnet-client/qtcnet-client.csproj @@ -8,6 +8,8 @@ true enable QtCNETAppIconV2.ico + QtCNetClient + QtCNET