Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/c#/GeneralUpdate.Core/Event/IUpdateEventListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using GeneralUpdate.Core.Download;
using GeneralUpdate.Core.Download.Models;
using GeneralUpdate.Core.Event;

namespace GeneralUpdate.Core.Event;

/// <summary>Batch event registration — implement once, register once.</summary>
public interface IUpdateEventListener
{
void OnAllDownloadCompleted(MultiAllDownloadCompletedEventArgs args);
void OnDownloadCompleted(MultiDownloadCompletedEventArgs args);
void OnDownloadError(MultiDownloadErrorEventArgs args);
void OnDownloadStatistics(MultiDownloadStatisticsEventArgs args);
void OnUpdateInfo(UpdateInfoEventArgs args);
void OnException(ExceptionEventArgs args);
void OnProgress(DownloadProgress progress);
}

/// <summary>Progress event args for AddListenerProgress.</summary>
public class ProgressEventArgs : EventArgs
{
Comment on lines +19 to +21
public DownloadProgress Progress { get; }
public ProgressEventArgs(DownloadProgress progress) => Progress = progress;
}
96 changes: 96 additions & 0 deletions src/c#/GeneralUpdate.Core/Ipc/IProcessInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using GeneralUpdate.Core.Configuration;
using GeneralUpdate.Core.JsonContext;

namespace GeneralUpdate.Core.Ipc;

/// <summary>IPC provider for Client-to-Upgrade process communication.</summary>
public interface IProcessInfoProvider
{
Task SendAsync(ProcessInfo info, CancellationToken token = default);
Task<ProcessInfo?> ReceiveAsync(CancellationToken token = default);
}

/// <summary>Named pipe IPC — preferred (no file residue).</summary>
public class NamedPipeProcessInfoProvider : IProcessInfoProvider
{
private readonly string _pipeName;

public NamedPipeProcessInfoProvider(string pipeName = "GeneralUpdate.IPC")
=> _pipeName = pipeName;

public async Task SendAsync(ProcessInfo info, CancellationToken token = default)
{
using var server = new NamedPipeServerStream(_pipeName, PipeDirection.Out);
await server.WaitForConnectionAsync(token).ConfigureAwait(false);
var json = JsonSerializer.Serialize(info, ProcessInfoJsonContext.Default.ProcessInfo);
var bytes = Encoding.UTF8.GetBytes(json);
await server.WriteAsync(bytes, 0, bytes.Length, token).ConfigureAwait(false);
}

public async Task<ProcessInfo?> ReceiveAsync(CancellationToken token = default)
{
using var client = new NamedPipeClientStream(".", _pipeName, PipeDirection.In);
await client.ConnectAsync(5000, token).ConfigureAwait(false);
Comment on lines +31 to +41
using var ms = new MemoryStream();
var buffer = new byte[4096];
int read;
while ((read = await client.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false)) > 0)
await ms.WriteAsync(buffer, 0, read, token).ConfigureAwait(false);
var json = Encoding.UTF8.GetString(ms.ToArray());
return JsonSerializer.Deserialize(json, ProcessInfoJsonContext.Default.ProcessInfo);
}
}

/// <summary>Encrypted file fallback IPC (AES).</summary>
public class EncryptedFileProcessInfoProvider : IProcessInfoProvider
{
private static readonly byte[] Key = SHA256.Create()
.ComputeHash(Encoding.UTF8.GetBytes("GeneralUpdate.ProcessInfo.IPC.v1"));
private static readonly byte[] IV = new byte[16] { 0x47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

Comment on lines +55 to +58
Comment on lines +55 to +58
private readonly string _filePath;

public EncryptedFileProcessInfoProvider(string? basePath = null)
{
var dir = basePath ?? Path.Combine(Path.GetTempPath(), "GeneralUpdate", "ipc");
Directory.CreateDirectory(dir);
_filePath = Path.Combine(dir, $"{Guid.NewGuid():N}.enc");
}
Comment on lines +61 to +66

public Task SendAsync(ProcessInfo info, CancellationToken token = default)
{
var json = JsonSerializer.Serialize(info, ProcessInfoJsonContext.Default.ProcessInfo);
var plainBytes = Encoding.UTF8.GetBytes(json);
using var aes = Aes.Create();
aes.Key = Key; aes.IV = IV;
using var enc = aes.CreateEncryptor();
var cipher = enc.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
File.WriteAllBytes(_filePath, cipher);
return Task.CompletedTask;
}

public Task<ProcessInfo?> ReceiveAsync(CancellationToken token = default)
{
if (!File.Exists(_filePath)) return Task.FromResult<ProcessInfo?>(null);
try
{
var cipher = File.ReadAllBytes(_filePath);
using var aes = Aes.Create();
aes.Key = Key; aes.IV = IV;
using var dec = aes.CreateDecryptor();
var plain = dec.TransformFinalBlock(cipher, 0, cipher.Length);
var json = Encoding.UTF8.GetString(plain);
return Task.FromResult<ProcessInfo?>(
JsonSerializer.Deserialize(json, ProcessInfoJsonContext.Default.ProcessInfo));
Comment on lines +68 to +92
}
finally { try { File.Delete(_filePath); } catch { } }
}
}