public async Task InvalidateSystem(int systemId) { if (_cache.TryGetValue <CachedAccount>(KeyForSystem(systemId), out var systemCache)) { // If we have the system cached here, just invalidate for all the accounts we have in the cache _logger.Debug("Invalidating cache for system {System} and accounts {Accounts}", systemId, systemCache.Accounts); _cache.Remove(KeyForSystem(systemId)); foreach (var account in systemCache.Accounts) { _cache.Remove(KeyForAccount(account)); } return; } // If we don't, look up the accounts from the database and invalidate *those* _cache.Remove(KeyForSystem(systemId)); using var conn = await _db.Obtain(); var accounts = (await conn.QueryAsync <ulong>("select uid from accounts where system = @System", new { System = systemId })).ToArray(); _logger.Debug("Invalidating cache for system {System} and accounts {Accounts}", systemId, accounts); foreach (var account in accounts) { _cache.Remove(KeyForAccount(account)); } }
public async Task <ActionResult <IEnumerable <SwitchesReturn> > > GetSwitches(string hid, [FromQuery(Name = "before")] Instant?before) { if (before == null) { before = SystemClock.Instance.GetCurrentInstant(); } var system = await _data.GetSystemByHid(hid); if (system == null) { return(NotFound("System not found.")); } using (var conn = await _conn.Obtain()) { var res = await conn.QueryAsync <SwitchesReturn>( @"select *, array( select members.hid from switch_members, members where switch_members.switch = switches.id and members.id = switch_members.member ) as members from switches where switches.system = @System and switches.timestamp < @Before order by switches.timestamp desc limit 100;", new { System = system.Id, Before = before }); return(Ok(res)); } }
public async Task <ActionResult <IEnumerable <SwitchesReturn> > > GetSwitches(string hid, [FromQuery(Name = "before")] Instant?before) { if (before == null) { before = SystemClock.Instance.GetCurrentInstant(); } var system = await _data.GetSystemByHid(hid); if (system == null) { return(NotFound("System not found.")); } if (!system.FrontHistoryPrivacy.CanAccess(_auth.ContextFor(system))) { return(StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view front history.")); } using (var conn = await _conn.Obtain()) { var res = await conn.QueryAsync <SwitchesReturn>( @"select *, array( select members.hid from switch_members, members where switch_members.switch = switches.id and members.id = switch_members.member ) as members from switches where switches.system = @System and switches.timestamp < @Before order by switches.timestamp desc limit 100;", new { System = system.Id, Before = before }); return(Ok(res)); } }
public async Task InvalidateResultsForSystem(PKSystem system) { _logger.Information("Invalidating proxy cache for system {System}", system.Id); using (var conn = await _conn.Obtain()) foreach (var accountId in await conn.QueryAsync <ulong>("select uid from accounts where system = @Id", system)) { _cache.Remove(GetKey(accountId)); } }
public async Task <ITextChannel> GetLogChannel(IGuild guild) { using (var conn = await _conn.Obtain()) { var server = await conn.QueryFirstOrDefaultAsync <ServerDefinition>("select * from servers where id = @Id", new { Id = guild.Id }); if (server?.LogChannel == null) { return(null); } return(await _client.GetChannelAsync(server.LogChannel.Value) as ITextChannel); } }
public async Task HandleMessage(SocketMessage msg) { RegisterMessageMetrics(msg); // Ignore system messages (member joined, message pinned, etc) var arg = msg as SocketUserMessage; if (arg == null) { return; } // Ignore bot messages if (arg.Author.IsBot || arg.Author.IsWebhook) { return; } int argPos = 0; // Check if message starts with the command prefix if (arg.HasStringPrefix("pk;", ref argPos, StringComparison.OrdinalIgnoreCase) || arg.HasStringPrefix("pk!", ref argPos, StringComparison.OrdinalIgnoreCase) || arg.HasMentionPrefix(_client.CurrentUser, ref argPos)) { _logger.Debug("Parsing command {Command} from message {Channel}-{Message}", msg.Content, msg.Channel.Id, msg.Id); // Essentially move the argPos pointer by however much whitespace is at the start of the post-argPos string var trimStartLengthDiff = arg.Content.Substring(argPos).Length - arg.Content.Substring(argPos).TrimStart().Length; argPos += trimStartLengthDiff; // If it does, fetch the sender's system (because most commands need that) into the context, // and start command execution // Note system may be null if user has no system, hence `OrDefault` PKSystem system; using (var conn = await _connectionFactory.Obtain()) system = await conn.QueryFirstOrDefaultAsync <PKSystem>( "select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system", new { Id = arg.Author.Id }); await _commands.ExecuteAsync(new PKCommandContext(_client, arg, system, _services), argPos, _services); } else { // If not, try proxying anyway try { await _proxy.HandleMessageAsync(arg); } catch (PKError e) { await msg.Channel.SendMessageAsync($"{Emojis.Error} {e.Message}"); } } }
public static async IAsyncEnumerable <T> QueryStreamAsync <T>(this DbConnectionFactory connFactory, string sql, object param) { using var conn = await connFactory.Obtain(); await using var reader = (DbDataReader) await conn.ExecuteReaderAsync(sql, param); var parser = reader.GetRowParser <T>(); while (reader.Read()) { yield return(parser(reader)); } }
private async Task ApplyMigration(int migrationId) { // migrationId is the *target* version using var conn = await _conn.Obtain(); using var tx = conn.BeginTransaction(); // See if we even have the info table... if not, we implicitly define the version as -1 // This means migration 0 will get executed, which ensures we're at a consistent state. // *Technically* this also means schema version 0 will be identified as -1, but since we're only doing these // checks in the above for loop, this doesn't matter. var hasInfoTable = await conn.QuerySingleOrDefaultAsync <int>("select count(*) from information_schema.tables where table_name = 'info'") == 1; int currentVersion; if (hasInfoTable) { currentVersion = await conn.QuerySingleOrDefaultAsync <int>("select schema_version from info"); } else { currentVersion = -1; } if (currentVersion >= migrationId) { return; // Don't execute the migration if we're already at the target version. } using var stream = typeof(SchemaService).Assembly.GetManifestResourceStream($"PluralKit.Core.Migrations.{migrationId}.sql"); if (stream == null) { throw new ArgumentException("Invalid migration ID"); } using var reader = new StreamReader(stream); var migrationQuery = await reader.ReadToEndAsync(); _logger.Information("Current schema version is {CurrentVersion}, applying migration {MigrationId}", currentVersion, migrationId); await conn.ExecuteAsync(migrationQuery, transaction : tx); tx.Commit(); // If the above migration creates new enum/composite types, we must tell Npgsql to reload the internal type caches // This will propagate to every other connection as well, since it marks the global type mapper collection dirty. // TODO: find a way to get around the cast to our internal tracker wrapper... this could break if that ever changes ((PerformanceTrackingConnection)conn)._impl.ReloadTypes(); }
public async Task <IEnumerable <PKMember> > GetConflictingProxies(PKSystem system, ProxyTag tag) { using (var conn = await _conn.Obtain()) // return await conn.QueryAsync<PKMember>("select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix ilike @Prefix and suffix ilike @Suffix", new // { // System = system.Id, // Prefix = tag.Prefix.Replace("%", "\\%") + "%", // Suffix = "%" + tag.Suffix.Replace("%", "\\%") // }); return(await conn.QueryAsync <PKMember>("select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix = @Prefix and suffix = @Suffix", new { System = system.Id, Prefix = tag.Prefix, Suffix = tag.Suffix })); }
public async Task HandleMessage(SocketMessage arg) { if (_client.GetShardFor((arg.Channel as IGuildChannel)?.Guild).ConnectionState != ConnectionState.Connected) { return; // Discard messages while the bot "catches up" to avoid unnecessary CPU pressure causing timeouts } RegisterMessageMetrics(arg); // Ignore system messages (member joined, message pinned, etc) var msg = arg as SocketUserMessage; if (msg == null) { return; } // Ignore bot messages if (msg.Author.IsBot || msg.Author.IsWebhook) { return; } int argPos = -1; // Check if message starts with the command prefix if (msg.Content.StartsWith("pk;", StringComparison.InvariantCultureIgnoreCase)) { argPos = 3; } else if (msg.Content.StartsWith("pk!", StringComparison.InvariantCultureIgnoreCase)) { argPos = 3; } else if (Utils.HasMentionPrefix(msg.Content, ref argPos, out var id)) // Set argPos to the proper value { if (id != _client.CurrentUser.Id) // But undo it if it's someone else's ping { argPos = -1; } } // If it does, try executing a command if (argPos > -1) { _logger.Verbose("Parsing command {Command} from message {Channel}-{Message}", msg.Content, msg.Channel.Id, msg.Id); // Essentially move the argPos pointer by however much whitespace is at the start of the post-argPos string var trimStartLengthDiff = msg.Content.Substring(argPos).Length - msg.Content.Substring(argPos).TrimStart().Length; argPos += trimStartLengthDiff; // If it does, fetch the sender's system (because most commands need that) into the context, // and start command execution // Note system may be null if user has no system, hence `OrDefault` PKSystem system; using (var conn = await _connectionFactory.Obtain()) system = await conn.QueryFirstOrDefaultAsync <PKSystem>( "select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system", new { Id = msg.Author.Id }); try { await _tree.ExecuteCommand(new Context(_services, msg, argPos, system)); } catch (Exception e) { await HandleCommandError(msg, e); // HandleCommandError only *reports* the error, we gotta pass it through to the parent // error handler by rethrowing: throw; } } else { // If not, try proxying anyway try { await _proxy.HandleMessageAsync(msg); } catch (PKError e) { await arg.Channel.SendMessageAsync($"{Emojis.Error} {e.Message}"); } } }