mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 09:18:27 -04:00
Many IDisposable fixes. GlobalNadeko won't have file watchers for creds. Wallet simplified
This commit is contained in:
@@ -286,7 +286,7 @@ namespace NadekoBot.Coordinator
|
||||
for (var shardId = 0; shardId < _shardStatuses.Length; shardId++)
|
||||
{
|
||||
var status = _shardStatuses[shardId];
|
||||
if (status.Process is { } p)
|
||||
if (status.Process is Process p)
|
||||
{
|
||||
try{p.Kill();} catch {}
|
||||
try{p.Dispose();} catch {}
|
||||
|
@@ -76,6 +76,7 @@ public partial class Gambling
|
||||
(race.FinishedUsers[0].Bet * (race.Users.Count - 1)) + CurrencySign)));
|
||||
}
|
||||
|
||||
ar.Dispose();
|
||||
return SendConfirmAsync(GetText(strs.animal_race),
|
||||
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)
|
||||
{
|
||||
_service.AnimalRaces.TryRemove(ctx.Guild.Id, out _);
|
||||
race.Dispose();
|
||||
return ReplyErrorLocalizedAsync(strs.animal_race_failed);
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,7 @@ public class VoteRewardService : INService, IReadyExecutor
|
||||
if (_client.ShardId != 0)
|
||||
return;
|
||||
|
||||
var http = new HttpClient(new HttpClientHandler
|
||||
using var http = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||
|
@@ -359,7 +359,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
};
|
||||
|
||||
using var dlClient = new AmazonS3Client(accessKey, secretAcccessKey, config);
|
||||
var oldVersionObject = await dlClient.GetObjectAsync(new()
|
||||
using var oldVersionObject = await dlClient.GetObjectAsync(new()
|
||||
{
|
||||
BucketName = "nadeko-pictures",
|
||||
Key = "cmds/versions.json"
|
||||
|
@@ -76,7 +76,7 @@ public sealed partial class YtLoader
|
||||
var mem = GetScriptResponseSpan(response);
|
||||
var root = JsonDocument.Parse(mem).RootElement;
|
||||
|
||||
var tracksJsonItems = root
|
||||
using var tracksJsonItems = root
|
||||
.GetProperty("contents")
|
||||
.GetProperty("twoColumnSearchResultsRenderer")
|
||||
.GetProperty("primaryContents")
|
||||
|
@@ -98,15 +98,16 @@ public partial class Searches
|
||||
return;
|
||||
|
||||
var fileName = $"{query}-sparkline.{imageData.Extension}";
|
||||
using var attachment = new FileAttachment(
|
||||
imageData.FileData,
|
||||
fileName
|
||||
);
|
||||
await message.ModifyAsync(mp =>
|
||||
{
|
||||
mp.Attachments =
|
||||
new(new[]
|
||||
{
|
||||
new FileAttachment(
|
||||
imageData.FileData,
|
||||
fileName
|
||||
)
|
||||
attachment
|
||||
});
|
||||
|
||||
mp.Embed = eb.WithImageUrl($"attachment://{fileName}").Build();
|
||||
|
@@ -1,55 +1,55 @@
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public sealed class PolygonApiClient : IDisposable
|
||||
{
|
||||
private const string BASE_URL = "https://api.polygon.io/v3";
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _apiKey;
|
||||
|
||||
public PolygonApiClient(HttpClient httpClient, string apiKey)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_apiKey = apiKey;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
query = null;
|
||||
|
||||
if(query is not null)
|
||||
query = Uri.EscapeDataString(query);
|
||||
|
||||
var requestString = $"{BASE_URL}/reference/tickers"
|
||||
+ "?type=CS"
|
||||
+ "&active=true"
|
||||
+ "&order=asc"
|
||||
+ "&limit=1000"
|
||||
+ $"&apiKey={_apiKey}";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ticker))
|
||||
requestString += $"&ticker={ticker}";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query))
|
||||
requestString += $"&search={query}";
|
||||
|
||||
|
||||
var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString);
|
||||
|
||||
if (response is null)
|
||||
return Array.Empty<PolygonTickerData>();
|
||||
|
||||
return response.Results;
|
||||
}
|
||||
|
||||
// public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker)
|
||||
// {
|
||||
// return new();
|
||||
// }
|
||||
|
||||
public void Dispose()
|
||||
=> _httpClient.Dispose();
|
||||
}
|
||||
// using System.Net.Http.Json;
|
||||
//
|
||||
// namespace NadekoBot.Modules.Searches;
|
||||
//
|
||||
// public sealed class PolygonApiClient : IDisposable
|
||||
// {
|
||||
// private const string BASE_URL = "https://api.polygon.io/v3";
|
||||
//
|
||||
// private readonly HttpClient _httpClient;
|
||||
// private readonly string _apiKey;
|
||||
//
|
||||
// public PolygonApiClient(HttpClient httpClient, string apiKey)
|
||||
// {
|
||||
// _httpClient = httpClient;
|
||||
// _apiKey = apiKey;
|
||||
// }
|
||||
//
|
||||
// public async Task<IReadOnlyCollection<PolygonTickerData>> TickersAsync(string? ticker = null, string? query = null)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(query))
|
||||
// query = null;
|
||||
//
|
||||
// if(query is not null)
|
||||
// query = Uri.EscapeDataString(query);
|
||||
//
|
||||
// var requestString = $"{BASE_URL}/reference/tickers"
|
||||
// + "?type=CS"
|
||||
// + "&active=true"
|
||||
// + "&order=asc"
|
||||
// + "&limit=1000"
|
||||
// + $"&apiKey={_apiKey}";
|
||||
//
|
||||
// if (!string.IsNullOrWhiteSpace(ticker))
|
||||
// requestString += $"&ticker={ticker}";
|
||||
//
|
||||
// if (!string.IsNullOrWhiteSpace(query))
|
||||
// requestString += $"&search={query}";
|
||||
//
|
||||
//
|
||||
// var response = await _httpClient.GetFromJsonAsync<PolygonTickerResponse>(requestString);
|
||||
//
|
||||
// if (response is null)
|
||||
// return Array.Empty<PolygonTickerData>();
|
||||
//
|
||||
// return response.Results;
|
||||
// }
|
||||
//
|
||||
// // public async Task<PolygonTickerDetailsV3> TickerDetailsV3Async(string ticker)
|
||||
// // {
|
||||
// // return new();
|
||||
// // }
|
||||
//
|
||||
// public void Dispose()
|
||||
// => _httpClient.Dispose();
|
||||
// }
|
@@ -34,7 +34,7 @@ public partial class Searches
|
||||
return;
|
||||
|
||||
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();
|
||||
|
||||
|
@@ -109,7 +109,8 @@ public class SearchesService : INService
|
||||
using (var avatarImg = Image.Load<Rgba32>(data))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -141,7 +142,8 @@ public class SearchesService : INService
|
||||
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)
|
||||
@@ -532,7 +534,7 @@ public class SearchesService : INService
|
||||
http.DefaultRequestHeaders.Clear();
|
||||
|
||||
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);
|
||||
var elems = document.QuerySelectorAll("div.g > div > div");
|
||||
|
@@ -62,7 +62,7 @@ public class PicartoProvider : Provider
|
||||
{
|
||||
http.DefaultRequestHeaders.Accept.Add(new("application/json"));
|
||||
// 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)
|
||||
continue;
|
||||
|
@@ -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
|
||||
try
|
||||
{
|
||||
var res = await http.PostAsJsonAsync(
|
||||
using var res = await http.PostAsJsonAsync(
|
||||
$"https://open-api.trovo.live/openplatform/channels/id",
|
||||
new TrovoRequestData()
|
||||
{
|
||||
|
@@ -84,12 +84,13 @@ public class PatreonRewardsService : INService, IReadyExecutor
|
||||
try
|
||||
{
|
||||
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"
|
||||
+ $"&refresh_token={creds.Patreon.RefreshToken}"
|
||||
+ $"&client_id={creds.Patreon.ClientId}"
|
||||
+ $"&client_secret={creds.Patreon.ClientSecret}",
|
||||
new StringContent(string.Empty));
|
||||
content);
|
||||
|
||||
res.EnsureSuccessStatusCode();
|
||||
|
||||
|
@@ -27,7 +27,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
|
||||
_creds = creds;
|
||||
_client = client;
|
||||
|
||||
var uow = _db.GetDbContext();
|
||||
using var uow = _db.GetDbContext();
|
||||
var shardRepeaters = uow.Set<Repeater>()
|
||||
.Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards
|
||||
== _client.ShardId)
|
||||
|
@@ -336,7 +336,7 @@ public partial class Utility : NadekoModule
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.invalid_emoji_link);
|
||||
|
@@ -35,6 +35,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Html2Markdown" Version="5.0.2.561" />
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
@@ -77,6 +78,12 @@
|
||||
|
||||
<!-- Used by stream notifications -->
|
||||
<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>
|
||||
|
@@ -14,7 +14,7 @@ public class CurrencyService : ICurrencyService, INService
|
||||
public Task<IWallet> GetWalletAsync(ulong userId, CurrencyType 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));
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class CurrencyService : ICurrencyService, INService
|
||||
{
|
||||
foreach (var userId in userIds)
|
||||
{
|
||||
await using var wallet = await GetWalletAsync(userId);
|
||||
var wallet = await GetWalletAsync(userId);
|
||||
await wallet.Add(amount, txData);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class CurrencyService : ICurrencyService, INService
|
||||
long amount,
|
||||
TxData txData)
|
||||
{
|
||||
await using var wallet = await GetWalletAsync(userId);
|
||||
var wallet = await GetWalletAsync(userId);
|
||||
await wallet.Add(amount, txData);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class CurrencyService : ICurrencyService, INService
|
||||
long amount,
|
||||
TxData txData)
|
||||
{
|
||||
await using var wallet = await GetWalletAsync(user.Id);
|
||||
var wallet = await GetWalletAsync(user.Id);
|
||||
await wallet.Add(amount, txData);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class CurrencyService : ICurrencyService, INService
|
||||
long amount,
|
||||
TxData txData)
|
||||
{
|
||||
await using var wallet = await GetWalletAsync(userId);
|
||||
var wallet = await GetWalletAsync(userId);
|
||||
return await wallet.Take(amount, txData);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class CurrencyService : ICurrencyService, INService
|
||||
long amount,
|
||||
TxData txData)
|
||||
{
|
||||
await using var wallet = await GetWalletAsync(user.Id);
|
||||
var wallet = await GetWalletAsync(user.Id);
|
||||
return await wallet.Take(amount, txData);
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@ public static class CurrencyServiceExtensions
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ public static class CurrencyServiceExtensions
|
||||
string fromName,
|
||||
string note)
|
||||
{
|
||||
await using var fromWallet = await cs.GetWalletAsync(fromId);
|
||||
await using var toWallet = await cs.GetWalletAsync(toId);
|
||||
var fromWallet = await cs.GetWalletAsync(fromId);
|
||||
var toWallet = await cs.GetWalletAsync(toId);
|
||||
|
||||
var extra = new TxData("gift", fromName, note, fromId);
|
||||
|
||||
|
@@ -7,48 +7,53 @@ namespace NadekoBot.Services.Currency;
|
||||
|
||||
public class DefaultWallet : IWallet
|
||||
{
|
||||
private readonly DbService _db;
|
||||
public ulong UserId { get; }
|
||||
|
||||
private readonly NadekoContext _ctx;
|
||||
|
||||
public DefaultWallet(ulong userId, NadekoContext ctx)
|
||||
public DefaultWallet(ulong userId, DbService db)
|
||||
{
|
||||
UserId = userId;
|
||||
_ctx = ctx;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public Task<long> GetBalance()
|
||||
=> _ctx.DiscordUser
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.UserId == UserId)
|
||||
.Select(x => x.CurrencyAmount)
|
||||
.FirstOrDefaultAsync();
|
||||
public async Task<long> GetBalance()
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.DiscordUser
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.UserId == UserId)
|
||||
.Select(x => x.CurrencyAmount)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> Take(long amount, TxData txData)
|
||||
{
|
||||
if (amount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount), "Amount to take must be non negative.");
|
||||
|
||||
var changed = await _ctx.DiscordUser
|
||||
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
CurrencyAmount = x.CurrencyAmount - amount
|
||||
});
|
||||
await using var ctx = _db.GetDbContext();
|
||||
|
||||
var changed = await ctx.DiscordUser
|
||||
.Where(x => x.UserId == UserId && x.CurrencyAmount >= amount)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
CurrencyAmount = x.CurrencyAmount - amount
|
||||
});
|
||||
|
||||
if (changed == 0)
|
||||
return false;
|
||||
|
||||
await _ctx.CreateLinqToDbContext()
|
||||
.InsertAsync(new CurrencyTransaction()
|
||||
{
|
||||
Amount = -amount,
|
||||
Note = txData.Note,
|
||||
UserId = UserId,
|
||||
Type = txData.Type,
|
||||
Extra = txData.Extra,
|
||||
OtherId = txData.OtherId
|
||||
});
|
||||
await using var ctx2 = ctx.CreateLinqToDbContext();
|
||||
await ctx2
|
||||
.InsertAsync(new CurrencyTransaction()
|
||||
{
|
||||
Amount = -amount,
|
||||
Note = txData.Note,
|
||||
UserId = UserId,
|
||||
Type = txData.Type,
|
||||
Extra = txData.Extra,
|
||||
OtherId = txData.OtherId
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -58,9 +63,11 @@ public class DefaultWallet : IWallet
|
||||
if (amount <= 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)
|
||||
.UpdateAsync(x => new()
|
||||
{
|
||||
@@ -69,7 +76,7 @@ public class DefaultWallet : IWallet
|
||||
|
||||
if (changed == 0)
|
||||
{
|
||||
await _ctx.DiscordUser
|
||||
await ctx.DiscordUser
|
||||
.ToLinqToDBTable()
|
||||
.Value(x => x.UserId, UserId)
|
||||
.Value(x => x.Username, "Unknown")
|
||||
@@ -91,19 +98,7 @@ public class DefaultWallet : IWallet
|
||||
OtherId = txData.OtherId
|
||||
};
|
||||
|
||||
await _ctx.CreateLinqToDbContext()
|
||||
.InsertAsync(ct);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ctx.SaveChanges();
|
||||
_ctx.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _ctx.SaveChangesAsync();
|
||||
await _ctx.DisposeAsync();
|
||||
await using var ctx2 = ctx.CreateLinqToDbContext();
|
||||
await ctx2.InsertAsync(ct);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
namespace NadekoBot.Services.Currency;
|
||||
|
||||
public interface IWallet : IDisposable, IAsyncDisposable
|
||||
public interface IWallet
|
||||
{
|
||||
public ulong UserId { get; }
|
||||
|
||||
|
@@ -32,10 +32,9 @@ public class DbService
|
||||
using var context = new NadekoContext(_options);
|
||||
if (context.Database.GetPendingMigrations().Any())
|
||||
{
|
||||
var mContext = new NadekoContext(_migrateOptions);
|
||||
using var mContext = new NadekoContext(_migrateOptions);
|
||||
mContext.Database.Migrate();
|
||||
mContext.SaveChanges();
|
||||
mContext.Dispose();
|
||||
}
|
||||
|
||||
context.Database.ExecuteSqlRaw("PRAGMA journal_mode=WAL");
|
||||
|
@@ -32,6 +32,7 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
|
||||
|
||||
private readonly object _reloadLock = new();
|
||||
private readonly IDisposable _changeToken;
|
||||
|
||||
public BotCredsProvider(int? totalShards = null)
|
||||
{
|
||||
@@ -52,9 +53,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
_config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true)
|
||||
.AddEnvironmentVariables("NadekoBot_")
|
||||
.Build();
|
||||
|
||||
ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
|
||||
|
||||
#if !GLOBAL_NADEKO
|
||||
_changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
|
||||
#endif
|
||||
Reload();
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,7 @@ using System.Diagnostics;
|
||||
|
||||
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";
|
||||
|
||||
|
@@ -1,87 +1,87 @@
|
||||
// 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.
|
||||
// https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
public static void KillTree(this Process process)
|
||||
=> process.KillTree(_defaultTimeout);
|
||||
|
||||
public static void KillTree(this Process process, TimeSpan timeout)
|
||||
{
|
||||
if (_isWindows)
|
||||
RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
|
||||
else
|
||||
{
|
||||
var children = new HashSet<int>();
|
||||
GetAllChildIdsUnix(process.Id, children, timeout);
|
||||
foreach (var childId in children)
|
||||
KillProcessUnix(childId, timeout);
|
||||
|
||||
KillProcessUnix(process.Id, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
|
||||
{
|
||||
var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
|
||||
|
||||
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
|
||||
{
|
||||
using var reader = new StringReader(stdout);
|
||||
while (true)
|
||||
{
|
||||
var text = reader.ReadLine();
|
||||
if (text is null)
|
||||
return;
|
||||
|
||||
if (int.TryParse(text, out var id))
|
||||
{
|
||||
children.Add(id);
|
||||
// Recursively get the children
|
||||
GetAllChildIdsUnix(id, children, timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void KillProcessUnix(int processId, TimeSpan timeout)
|
||||
=> RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
|
||||
|
||||
private static int RunProcessAndWaitForExit(
|
||||
string fileName,
|
||||
string arguments,
|
||||
TimeSpan timeout,
|
||||
out string? stdout)
|
||||
{
|
||||
stdout = null;
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
var process = Process.Start(startInfo);
|
||||
|
||||
if (process is null)
|
||||
return -1;
|
||||
|
||||
if (process.WaitForExit((int)timeout.TotalMilliseconds))
|
||||
stdout = process.StandardOutput.ReadToEnd();
|
||||
else
|
||||
process.Kill();
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
// // 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.
|
||||
// // https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.Process.Sources/ProcessHelper.cs
|
||||
//
|
||||
// using System.Diagnostics;
|
||||
// using System.Runtime.InteropServices;
|
||||
//
|
||||
// namespace NadekoBot.Extensions;
|
||||
//
|
||||
// public static class ProcessExtensions
|
||||
// {
|
||||
// private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
// private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(10);
|
||||
//
|
||||
// public static void KillTree(this Process process)
|
||||
// => process.KillTree(_defaultTimeout);
|
||||
//
|
||||
// public static void KillTree(this Process process, TimeSpan timeout)
|
||||
// {
|
||||
// if (_isWindows)
|
||||
// RunProcessAndWaitForExit("taskkill", $"/T /F /PID {process.Id}", timeout, out _);
|
||||
// else
|
||||
// {
|
||||
// var children = new HashSet<int>();
|
||||
// GetAllChildIdsUnix(process.Id, children, timeout);
|
||||
// foreach (var childId in children)
|
||||
// KillProcessUnix(childId, timeout);
|
||||
//
|
||||
// KillProcessUnix(process.Id, timeout);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private static void GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
|
||||
// {
|
||||
// var exitCode = RunProcessAndWaitForExit("pgrep", $"-P {parentId}", timeout, out var stdout);
|
||||
//
|
||||
// if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
|
||||
// {
|
||||
// using var reader = new StringReader(stdout);
|
||||
// while (true)
|
||||
// {
|
||||
// var text = reader.ReadLine();
|
||||
// if (text is null)
|
||||
// return;
|
||||
//
|
||||
// if (int.TryParse(text, out var id))
|
||||
// {
|
||||
// children.Add(id);
|
||||
// // Recursively get the children
|
||||
// GetAllChildIdsUnix(id, children, timeout);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private static void KillProcessUnix(int processId, TimeSpan timeout)
|
||||
// => RunProcessAndWaitForExit("kill", $"-TERM {processId}", timeout, out _);
|
||||
//
|
||||
// private static int RunProcessAndWaitForExit(
|
||||
// string fileName,
|
||||
// string arguments,
|
||||
// TimeSpan timeout,
|
||||
// out string? stdout)
|
||||
// {
|
||||
// stdout = null;
|
||||
//
|
||||
// var startInfo = new ProcessStartInfo
|
||||
// {
|
||||
// FileName = fileName,
|
||||
// Arguments = arguments,
|
||||
// RedirectStandardOutput = true,
|
||||
// UseShellExecute = false
|
||||
// };
|
||||
//
|
||||
// using var process = Process.Start(startInfo);
|
||||
//
|
||||
// if (process is null)
|
||||
// return -1;
|
||||
//
|
||||
// if (process.WaitForExit((int)timeout.TotalMilliseconds))
|
||||
// stdout = process.StandardOutput.ReadToEnd();
|
||||
// else
|
||||
// process.Kill();
|
||||
//
|
||||
// return process.ExitCode;
|
||||
// }
|
||||
// }
|
@@ -21,7 +21,7 @@ public static class Rgba32Extensions
|
||||
var xOffset = 0;
|
||||
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;
|
||||
imgFrame.Mutate(x => x.DrawImage(frame, new(offset, 0), new GraphicsOptions()));
|
||||
xOffset += imgArray[i].Bounds().Width;
|
||||
|
@@ -89,7 +89,7 @@ public static class StringExtensions
|
||||
public static async Task<Stream> ToStream(this string str)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
var sw = new StreamWriter(ms);
|
||||
await using var sw = new StreamWriter(ms);
|
||||
await sw.WriteAsync(str);
|
||||
await sw.FlushAsync();
|
||||
ms.Position = 0;
|
||||
|
Reference in New Issue
Block a user