Reimplement Auto Update

Use ImageFactory Whenever Images Are Grabbed
This commit is contained in:
Alan Moon 2025-12-10 15:10:08 -08:00
parent a0e83ad4df
commit 91c733c3ee
9 changed files with 213 additions and 45 deletions

View File

@ -17,11 +17,11 @@ namespace qtcnet_client.Factories
_apiService = apiService;
}
public async Task<Bitmap> GetAndCreateProfileImage(string userId, int status, int cosmeticStoreId = 0)
public async Task<Bitmap> 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,6 +51,8 @@ namespace qtcnet_client.Factories
}
// set status image
if(!int.IsNegative(status))
{
switch (status)
{
case 0:
@ -66,6 +68,7 @@ namespace qtcnet_client.Factories
img3 = Resources.DNDIcon;
break;
}
}
// finally, combine the images
Graphics g = Graphics.FromImage(combined);
@ -80,9 +83,11 @@ namespace qtcnet_client.Factories
g.DrawImage(img2, 0, 0, 139, 138);
}
if(!int.IsNegative(status))
{
img3.MakeTransparent();
g.DrawImage(img3, 104, 0, 35, 35);
}
return combined;
}

View File

@ -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<string, Image> UserProfileImages { get; set; } = [];
public Dictionary<string, Bitmap> 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<Image?> AddUserProfileImageToDictionary(string userId)
private async Task<Bitmap?> 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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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<LoggingService>();
service.AddSingleton<CredentialService>();
service.AddSingleton<ImageFactory>();
service.AddSingleton<UpdateService>();
service.AddSingleton(provider => GetOrCreateClientConfig());
service.AddSingleton<IApiService>(provider => new ApiService(provider.GetService<ClientConfig>()!.ServerBaseUri + "/api", provider.GetService<LoggingService>()!, provider.GetService<CredentialService>()!));
service.AddSingleton<IGatewayService>(provider => new GatewayService(provider.GetService<ClientConfig>()!.ServerBaseUri + "/chat", provider.GetService<IApiService>()!, provider.GetService<LoggingService>()!));

View File

@ -80,6 +80,15 @@ namespace qtcnet_client.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to 1.0.0.
/// </summary>
internal static string AssemblyVersion {
get {
return ResourceManager.GetString("AssemblyVersion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View File

@ -166,4 +166,7 @@
<data name="DNDIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\DNDIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="AssemblyVersion" xml:space="preserve">
<value>1.0.0</value>
</data>
</root>

View File

@ -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<ClientUpdateInfo>("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);
}
}
}
}

View File

@ -8,6 +8,8 @@
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationIcon>QtCNETAppIconV2.ico</ApplicationIcon>
<AssemblyName>QtCNetClient</AssemblyName>
<AssemblyTitle>QtCNET</AssemblyTitle>
</PropertyGroup>
<ItemGroup>