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++)
{
var status = _shardStatuses[shardId];
if (status.Process is { } p)
if (status.Process is Process p)
{
try{p.Kill();} catch {}
try{p.Dispose();} catch {}

View File

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

View File

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

View File

@@ -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"

View File

@@ -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")

View File

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

View File

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

View File

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

View File

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

View File

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

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
try
{
var res = await http.PostAsJsonAsync(
using var res = await http.PostAsJsonAsync(
$"https://open-api.trovo.live/openplatform/channels/id",
new TrovoRequestData()
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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