Many IDisposable fixes. GlobalNadeko won't have file watchers for creds. Wallet simplified

This commit is contained in:
Kwoth
2022-03-21 15:33:18 +01:00
parent 4cf3bdb53a
commit b7d1fd1b47
25 changed files with 229 additions and 221 deletions

View File

@@ -286,7 +286,7 @@ namespace NadekoBot.Coordinator
for (var shardId = 0; shardId < _shardStatuses.Length; shardId++) for (var shardId = 0; shardId < _shardStatuses.Length; shardId++)
{ {
var status = _shardStatuses[shardId]; var status = _shardStatuses[shardId];
if (status.Process is { } p) if (status.Process is Process p)
{ {
try{p.Kill();} catch {} try{p.Kill();} catch {}
try{p.Dispose();} catch {} try{p.Dispose();} catch {}

View File

@@ -76,6 +76,7 @@ public partial class Gambling
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign))); (race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
} }
ar.Dispose();
return SendConfirmAsync(GetText(strs.animal_race), return SendConfirmAsync(GetText(strs.animal_race),
GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon))); GetText(strs.animal_race_won(Format.Bold(winner.Username), winner.Animal.Icon)));
} }
@@ -127,6 +128,7 @@ public partial class Gambling
private Task Ar_OnStartingFailed(AnimalRace race) private Task Ar_OnStartingFailed(AnimalRace race)
{ {
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _); _service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
race.Dispose();
return ReplyErrorLocalizedAsync(strs.animal_race_failed); return ReplyErrorLocalizedAsync(strs.animal_race_failed);
} }

View File

@@ -35,7 +35,7 @@ public class VoteRewardService : INService, IReadyExecutor
if (_client.ShardId != 0) if (_client.ShardId != 0)
return; return;
var http = new HttpClient(new HttpClientHandler using var http = new HttpClient(new HttpClientHandler
{ {
AllowAutoRedirect = false, AllowAutoRedirect = false,
ServerCertificateCustomValidationCallback = delegate { return true; } ServerCertificateCustomValidationCallback = delegate { return true; }

View File

@@ -359,7 +359,7 @@ public partial class Help : NadekoModule<HelpService>
}; };
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config); using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
var oldVersionObject = await dlClient.GetObjectAsync(new() using var oldVersionObject = await dlClient.GetObjectAsync(new()
{ {
BucketName = "nadeko-pictures", BucketName = "nadeko-pictures",
Key = "cmds/versions.json" Key = "cmds/versions.json"

View File

@@ -76,7 +76,7 @@ public sealed partial class YtLoader
var mem = GetScriptResponseSpan(response); var mem = GetScriptResponseSpan(response);
var root = JsonDocument.Parse(mem).RootElement; var root = JsonDocument.Parse(mem).RootElement;
var tracksJsonItems = root using var tracksJsonItems = root
.GetProperty("contents") .GetProperty("contents")
.GetProperty("twoColumnSearchResultsRenderer") .GetProperty("twoColumnSearchResultsRenderer")
.GetProperty("primaryContents") .GetProperty("primaryContents")

View File

@@ -98,15 +98,16 @@ public partial class Searches
return; return;
var fileName = $"{query}-sparkline.{imageData.Extension}"; var fileName = $"{query}-sparkline.{imageData.Extension}";
using var attachment = new FileAttachment(
imageData.FileData,
fileName
);
await message.ModifyAsync(mp => await message.ModifyAsync(mp =>
{ {
mp.Attachments = mp.Attachments =
new(new[] new(new[]
{ {
new FileAttachment( attachment
imageData.FileData,
fileName
)
}); });
mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build(); mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();

View File

@@ -1,55 +1,55 @@
using System.Net.Http.Json; // using System.Net.Http.Json;
//
namespace NadekoBot.Modules.Searches; // namespace NadekoBot.Modules.Searches;
//
public sealed class PolygonApiClient : IDisposable // public sealed class PolygonApiClient : IDisposable
{ // {
private const string BASE_URL = "https://api.polygon.io/v3"; // private const string BASE_URL = "https://api.polygon.io/v3";
//
private readonly HttpClient _httpClient; // private readonly HttpClient _httpClient;
private readonly string _apiKey; // private readonly string _apiKey;
//
public PolygonApiClient(HttpClient httpClient, string apiKey) // public PolygonApiClient(HttpClient httpClient, string apiKey)
{ // {
_httpClient = httpClient; // _httpClient = httpClient;
_apiKey = apiKey; // _apiKey = apiKey;
} // }
//
public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null) // public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null)
{ // {
if (string.IsNullOrWhiteSpace(query)) // if (string.IsNullOrWhiteSpace(query))
query = null; // query = null;
//
if(query is not null) // if(query is not null)
query = Uri.EscapeDataString(query); // query = Uri.EscapeDataString(query);
//
var requestString = $"{BASE_URL}/reference/tickers" // var requestString = $"{BASE_URL}/reference/tickers"
+ "?type=CS" // + "?type=CS"
+ "&active=true" // + "&active=true"
+ "&order=asc" // + "&order=asc"
+ "&limit=1000" // + "&limit=1000"
+ $"&apiKey={_apiKey}"; // + $"&apiKey={_apiKey}";
//
if (!string.IsNullOrWhiteSpace(ticker)) // if (!string.IsNullOrWhiteSpace(ticker))
requestString += $"&ticker={ticker}"; // requestString += $"&ticker={ticker}";
//
if (!string.IsNullOrWhiteSpace(query)) // if (!string.IsNullOrWhiteSpace(query))
requestString += $"&search={query}"; // requestString += $"&search={query}";
//
//
var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString); // var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString);
//
if (response is null) // if (response is null)
return Array.Empty<PolygonTickerData>(); // return Array.Empty<PolygonTickerData>();
//
return response.Results; // return response.Results;
} // }
//
// public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker) // // public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker)
// { // // {
// return new(); // // return new();
// } // // }
//
public void Dispose() // public void Dispose()
=> _httpClient.Dispose(); // => _httpClient.Dispose();
} // }

View File

@@ -34,7 +34,7 @@ public partial class Searches
return; return;
using var http = _httpFactory.CreateClient("memelist"); using var http = _httpFactory.CreateClient("memelist");
var res = await http.GetAsync("https://api.memegen.link/templates/"); using var res = await http.GetAsync("https://api.memegen.link/templates/");
var rawJson = await res.Content.ReadAsStringAsync(); var rawJson = await res.Content.ReadAsStringAsync();

View File

@@ -109,7 +109,8 @@ public class SearchesService : INService
using (var avatarImg = Image.Load<Rgba32>(data)) using (var avatarImg = Image.Load<Rgba32>(data))
{ {
avatarImg.Mutate(x => x.Resize(85, 85).ApplyRoundedCorners(42)); avatarImg.Mutate(x => x.Resize(85, 85).ApplyRoundedCorners(42));
data = avatarImg.ToStream().ToArray(); await using var avStream = avatarImg.ToStream();
data = avStream.ToArray();
DrawAvatar(bg, avatarImg); DrawAvatar(bg, avatarImg);
} }
@@ -141,7 +142,8 @@ public class SearchesService : INService
bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions())); bg.Mutate(x => x.DrawImage(flowers, new(0, 0), new GraphicsOptions()));
} }
return bg.ToStream().ToArray(); await using var stream = bg.ToStream();
return stream.ToArray();
} }
public Task<WeatherData> GetWeatherDataAsync(string query) public Task<WeatherData> GetWeatherDataAsync(string query)
@@ -532,7 +534,7 @@ public class SearchesService : INService
http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Clear();
using var response = await http.SendAsync(msg); using var response = await http.SendAsync(msg);
var content = await response.Content.ReadAsStreamAsync(); await using var content = await response.Content.ReadAsStreamAsync();
using var document = await _googleParser.ParseDocumentAsync(content); using var document = await _googleParser.ParseDocumentAsync(content);
var elems = document.QuerySelectorAll("div.g > div > div"); var elems = document.QuerySelectorAll("div.g > div > div");

View File

@@ -62,7 +62,7 @@ public class PicartoProvider : Provider
{ {
http.DefaultRequestHeaders.Accept.Add(new("application/json")); http.DefaultRequestHeaders.Accept.Add(new("application/json"));
// get id based on the username // get id based on the username
var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}"); using var res = await http.GetAsync($"https://api.picarto.tv/v1/channel/name/{login}");
if (!res.IsSuccessStatusCode) if (!res.IsSuccessStatusCode)
continue; continue;

View File

@@ -59,7 +59,7 @@ If you are experiencing ratelimits, you should create your own application at: h
// so there is no need for ratelimit checks atm // so there is no need for ratelimit checks atm
try try
{ {
var res = await http.PostAsJsonAsync( using var res = await http.PostAsJsonAsync(
$"https://open-api.trovo.live/openplatform/channels/id", $"https://open-api.trovo.live/openplatform/channels/id",
new TrovoRequestData() new TrovoRequestData()
{ {

View File

@@ -84,12 +84,13 @@ public class PatreonRewardsService : INService, IReadyExecutor
try try
{ {
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token" using var content = new StringContent(string.Empty);
using var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
+ "?grant_type=refresh_token" + "?grant_type=refresh_token"
+ $"&refresh_token={creds.Patreon.RefreshToken}" + $"&refresh_token={creds.Patreon.RefreshToken}"
+ $"&client_id={creds.Patreon.ClientId}" + $"&client_id={creds.Patreon.ClientId}"
+ $"&client_secret={creds.Patreon.ClientSecret}", + $"&client_secret={creds.Patreon.ClientSecret}",
new StringContent(string.Empty)); content);
res.EnsureSuccessStatusCode(); res.EnsureSuccessStatusCode();

View File

@@ -27,7 +27,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
_creds = creds; _creds = creds;
_client = client; _client = client;
var uow = _db.GetDbContext(); using var uow = _db.GetDbContext();
var shardRepeaters = uow.Set<Repeater>() var shardRepeaters = uow.Set<Repeater>()
.Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards .Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards
== _client.ShardId) == _client.ShardId)

View File

@@ -336,7 +336,7 @@ public partial class Utility : NadekoModule
return; return;
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); using var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (!res.IsImage() || res.GetImageSize() is null or > 262_144) if (!res.IsImage() || res.GetImageSize() is null or > 262_144)
{ {
await ReplyErrorLocalizedAsync(strs.invalid_emoji_link); await ReplyErrorLocalizedAsync(strs.invalid_emoji_link);

View File

@@ -35,6 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Html2Markdown" Version="5.0.2.561" /> <PackageReference Include="Html2Markdown" Version="5.0.2.561" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
@@ -77,6 +78,12 @@
<!-- Used by stream notifications --> <!-- Used by stream notifications -->
<PackageReference Include="TwitchLib.Api" Version="3.4.1" /> <PackageReference Include="TwitchLib.Api" Version="3.4.1" />
<!-- Uncomment to check for disposable issues -->
<!-- <PackageReference Include="IDisposableAnalyzers" Version="4.0.2">-->
<!-- <PrivateAssets>all</PrivateAssets>-->
<!-- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>-->
<!-- </PackageReference>-->
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -14,7 +14,7 @@ public class CurrencyService : ICurrencyService, INService
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default) public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType type = CurrencyType.Default)
{ {
if (type == CurrencyType.Default) if (type == CurrencyType.Default)
return Task.FromResult<IWallet>(new DefaultWallet(userId, _db.GetDbContext())); return Task.FromResult<IWallet>(new DefaultWallet(userId, _db));
throw new ArgumentOutOfRangeException(nameof(type)); throw new ArgumentOutOfRangeException(nameof(type));
} }
@@ -29,7 +29,7 @@ public class CurrencyService : ICurrencyService, INService
{ {
foreach (var userId in userIds) foreach (var userId in userIds)
{ {
await using var wallet = await GetWalletAsync(userId); var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData); await wallet.Add(amount, txData);
} }
@@ -68,7 +68,7 @@ public class CurrencyService : ICurrencyService, INService
long amount, long amount,
TxData txData) TxData txData)
{ {
await using var wallet = await GetWalletAsync(userId); var wallet = await GetWalletAsync(userId);
await wallet.Add(amount, txData); await wallet.Add(amount, txData);
} }
@@ -77,7 +77,7 @@ public class CurrencyService : ICurrencyService, INService
long amount, long amount,
TxData txData) TxData txData)
{ {
await using var wallet = await GetWalletAsync(user.Id); var wallet = await GetWalletAsync(user.Id);
await wallet.Add(amount, txData); await wallet.Add(amount, txData);
} }
@@ -86,7 +86,7 @@ public class CurrencyService : ICurrencyService, INService
long amount, long amount,
TxData txData) TxData txData)
{ {
await using var wallet = await GetWalletAsync(userId); var wallet = await GetWalletAsync(userId);
return await wallet.Take(amount, txData); return await wallet.Take(amount, txData);
} }
@@ -95,7 +95,7 @@ public class CurrencyService : ICurrencyService, INService
long amount, long amount,
TxData txData) TxData txData)
{ {
await using var wallet = await GetWalletAsync(user.Id); var wallet = await GetWalletAsync(user.Id);
return await wallet.Take(amount, txData); return await wallet.Take(amount, txData);
} }
} }

View File

@@ -6,7 +6,7 @@ public static class CurrencyServiceExtensions
{ {
public static async Task<long> GetBalanceAsync(this ICurrencyService cs, ulong userId) public static async Task<long> GetBalanceAsync(this ICurrencyService cs, ulong userId)
{ {
await using var wallet = await cs.GetWalletAsync(userId); var wallet = await cs.GetWalletAsync(userId);
return await wallet.GetBalance(); return await wallet.GetBalance();
} }
@@ -18,8 +18,8 @@ public static class CurrencyServiceExtensions
string fromName, string fromName,
string note) string note)
{ {
await using var fromWallet = await cs.GetWalletAsync(fromId); var fromWallet = await cs.GetWalletAsync(fromId);
await using var toWallet = await cs.GetWalletAsync(toId); var toWallet = await cs.GetWalletAsync(toId);
var extra = new TxData("gift", fromName, note, fromId); var extra = new TxData("gift", fromName, note, fromId);

View File

@@ -7,48 +7,53 @@ namespace NadekoBot.Services.Currency;
public class DefaultWallet : IWallet public class DefaultWallet : IWallet
{ {
private readonly DbService _db;
public ulong UserId { get; } public ulong UserId { get; }
private readonly NadekoContext _ctx; public DefaultWallet(ulong userId, DbService db)
public DefaultWallet(ulong userId, NadekoContext ctx)
{ {
UserId = userId; UserId = userId;
_ctx = ctx; _db = db;
} }
public Task<long> GetBalance() public async Task<long> GetBalance()
=> _ctx.DiscordUser {
.ToLinqToDBTable() await using var ctx = _db.GetDbContext();
.Where(x => x.UserId == UserId) return await ctx.DiscordUser
.Select(x => x.CurrencyAmount) .ToLinqToDBTable()
.FirstOrDefaultAsync(); .Where(x => x.UserId == UserId)
.Select(x => x.CurrencyAmount)
.FirstOrDefaultAsync();
}
public async Task<bool> Take(long amount, TxData txData) public async Task<bool> Take(long amount, TxData txData)
{ {
if (amount < 0) if (amount < 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative."); throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
var changed = await _ctx.DiscordUser await using var ctx = _db.GetDbContext();
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
.UpdateAsync(x => new() var changed = await ctx.DiscordUser
{ .Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
CurrencyAmount = x.CurrencyAmount - amount .UpdateAsync(x => new()
}); {
CurrencyAmount = x.CurrencyAmount - amount
});
if (changed == 0) if (changed == 0)
return false; return false;
await _ctx.CreateLinqToDbContext() await using var ctx2 = ctx.CreateLinqToDbContext();
.InsertAsync(new CurrencyTransaction() await ctx2
{ .InsertAsync(new CurrencyTransaction()
Amount = -amount, {
Note = txData.Note, Amount = -amount,
UserId = UserId, Note = txData.Note,
Type = txData.Type, UserId = UserId,
Extra = txData.Extra, Type = txData.Type,
OtherId = txData.OtherId Extra = txData.Extra,
}); OtherId = txData.OtherId
});
return true; return true;
} }
@@ -58,9 +63,11 @@ public class DefaultWallet : IWallet
if (amount <= 0) if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0."); throw new ArgumentOutOfRangeException(nameof(amount), "Amount must be greater than 0.");
await using (var tran = await _ctx.Database.BeginTransactionAsync()) await using var ctx = _db.GetDbContext();
await using (var tran = await ctx.Database.BeginTransactionAsync())
{ {
var changed = await _ctx.DiscordUser var changed = await ctx.DiscordUser
.Where(x => x.UserId == UserId) .Where(x => x.UserId == UserId)
.UpdateAsync(x => new() .UpdateAsync(x => new()
{ {
@@ -69,7 +76,7 @@ public class DefaultWallet : IWallet
if (changed == 0) if (changed == 0)
{ {
await _ctx.DiscordUser await ctx.DiscordUser
.ToLinqToDBTable() .ToLinqToDBTable()
.Value(x => x.UserId, UserId) .Value(x => x.UserId, UserId)
.Value(x => x.Username, "Unknown") .Value(x => x.Username, "Unknown")
@@ -91,19 +98,7 @@ public class DefaultWallet : IWallet
OtherId = txData.OtherId OtherId = txData.OtherId
}; };
await _ctx.CreateLinqToDbContext() await using var ctx2 = ctx.CreateLinqToDbContext();
.InsertAsync(ct); await ctx2.InsertAsync(ct);
}
public void Dispose()
{
_ctx.SaveChanges();
_ctx.Dispose();
}
public async ValueTask DisposeAsync()
{
await _ctx.SaveChangesAsync();
await _ctx.DisposeAsync();
} }
} }

View File

@@ -1,6 +1,6 @@
namespace NadekoBot.Services.Currency; namespace NadekoBot.Services.Currency;
public interface IWallet : IDisposable, IAsyncDisposable public interface IWallet
{ {
public ulong UserId { get; } public ulong UserId { get; }

View File

@@ -32,10 +32,9 @@ public class DbService
using var context = new NadekoContext(_options); using var context = new NadekoContext(_options);
if (context.Database.GetPendingMigrations().Any()) if (context.Database.GetPendingMigrations().Any())
{ {
var mContext = new NadekoContext(_migrateOptions); using var mContext = new NadekoContext(_migrateOptions);
mContext.Database.Migrate(); mContext.Database.Migrate();
mContext.SaveChanges(); mContext.SaveChanges();
mContext.Dispose();
} }
context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL"); context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL");

View File

@@ -32,6 +32,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
private readonly object _reloadLock = new(); private readonly object _reloadLock = new();
private readonly IDisposable _changeToken;
public BotCredsProvider(int? totalShards = null) public BotCredsProvider(int? totalShards = null)
{ {
@@ -52,9 +53,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
_config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true) _config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true)
.AddEnvironmentVariables("NadekoBot_") .AddEnvironmentVariables("NadekoBot_")
.Build(); .Build();
#if !GLOBAL_NADEKO
ChangeToken.OnChange(() => _config.GetReloadToken(), Reload); _changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
#endif
Reload(); Reload();
} }

View File

@@ -5,7 +5,7 @@ using System.Diagnostics;
namespace NadekoBot.Services; namespace NadekoBot.Services;
public class StatsService : IStatsService, IReadyExecutor, INService, IDisposable public sealed class StatsService : IStatsService, IReadyExecutor, INService, IDisposable
{ {
public const string BOT_VERSION = "4.0.4"; public const string BOT_VERSION = "4.0.4";

View File

@@ -1,87 +1,87 @@
// Copyright (c) .NET Foundation. All rights reserved. // // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs // // https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
//
using System.Diagnostics; // using System.Diagnostics;
using System.Runtime.InteropServices; // using System.Runtime.InteropServices;
//
namespace NadekoBot.Extensions; // namespace NadekoBot.Extensions;
//
public static class ProcessExtensions // public static class ProcessExtensions
{ // {
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); // private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10); // private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
//
public static void KillTree(this Process process) // public static void KillTree(this Process process)
=> process.KillTree(_defaultTimeout); // => process.KillTree(_defaultTimeout);
//
public static void KillTree(this Process process, TimeSpan timeout) // public static void KillTree(this Process process, TimeSpan timeout)
{ // {
if (_isWindows) // if (_isWindows)
RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _); // RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
else // else
{ // {
var children = new HashSet<int>(); // var children = new HashSet<int>();
GetAllChildIdsUnix(process.Id, children, timeout); // GetAllChildIdsUnix(process.Id, children, timeout);
foreach (var childId in children) // foreach (var childId in children)
KillProcessUnix(childId, timeout); // KillProcessUnix(childId, timeout);
//
KillProcessUnix(process.Id, timeout); // KillProcessUnix(process.Id, timeout);
} // }
} // }
//
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout) // private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
{ // {
var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout); // var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
//
if (exitCode == 0 && !string.IsNullOrEmpty(stdout)) // if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
{ // {
using var reader = new StringReader(stdout); // using var reader = new StringReader(stdout);
while (true) // while (true)
{ // {
var text = reader.ReadLine(); // var text = reader.ReadLine();
if (text is null) // if (text is null)
return; // return;
//
if (int.TryParse(text, out var id)) // if (int.TryParse(text, out var id))
{ // {
children.Add(id); // children.Add(id);
// Recursively get the children // // Recursively get the children
GetAllChildIdsUnix(id, children, timeout); // GetAllChildIdsUnix(id, children, timeout);
} // }
} // }
} // }
} // }
//
private static void KillProcessUnix(int processId, TimeSpan timeout) // private static void KillProcessUnix(int processId, TimeSpan timeout)
=> RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _); // => RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
//
private static int RunProcessAndWaitForExit( // private static int RunProcessAndWaitForExit(
string fileName, // string fileName,
string arguments, // string arguments,
TimeSpan timeout, // TimeSpan timeout,
out string? stdout) // out string? stdout)
{ // {
stdout = null; // stdout = null;
//
var startInfo = new ProcessStartInfo // var startInfo = new ProcessStartInfo
{ // {
FileName = fileName, // FileName = fileName,
Arguments = arguments, // Arguments = arguments,
RedirectStandardOutput = true, // RedirectStandardOutput = true,
UseShellExecute = false // UseShellExecute = false
}; // };
//
var process = Process.Start(startInfo); // using var process = Process.Start(startInfo);
//
if (process is null) // if (process is null)
return -1; // return -1;
//
if (process.WaitForExit((int)timeout.TotalMilliseconds)) // if (process.WaitForExit((int)timeout.TotalMilliseconds))
stdout = process.StandardOutput.ReadToEnd(); // stdout = process.StandardOutput.ReadToEnd();
else // else
process.Kill(); // process.Kill();
//
return process.ExitCode; // return process.ExitCode;
} // }
} // }

View File

@@ -21,7 +21,7 @@ public static class Rgba32Extensions
var xOffset = 0; var xOffset = 0;
for (var i = 0; i < imgArray.Count; i++) for (var i = 0; i < imgArray.Count; i++)
{ {
var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count); using var frame = imgArray[i].Frames.CloneFrame(frameNumber % imgArray[i].Frames.Count);
var offset = xOffset; var offset = xOffset;
imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions())); imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions()));
xOffset += imgArray[i].Bounds().Width; xOffset += imgArray[i].Bounds().Width;

View File

@@ -89,7 +89,7 @@ public static class StringExtensions
public static async Task<Stream> ToStream(this string str) public static async Task<Stream> ToStream(this string str)
{ {
var ms = new MemoryStream(); var ms = new MemoryStream();
var sw = new StreamWriter(ms); await using var sw = new StreamWriter(ms);
await sw.WriteAsync(str); await sw.WriteAsync(str);
await sw.FlushAsync(); await sw.FlushAsync();
ms.Position = 0; ms.Position = 0;