Made MessageStorage.GetMessages more flexible.
This commit is contained in:
parent
eae379e933
commit
0bc025e5f8
5 changed files with 167 additions and 80 deletions
SharpChat.MariaDB
SharpChat.SQLite
SharpChatCommon
|
@ -1,6 +1,9 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
using SharpChat.Data;
|
||||||
using SharpChat.Messages;
|
using SharpChat.Messages;
|
||||||
|
using System.Data.Common;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ZLogger;
|
using ZLogger;
|
||||||
|
|
||||||
|
@ -54,20 +57,20 @@ public class MariaDBMessageStorage(MariaDBStorage storage, ILogger logger) : Mes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Message ReadMessage(MySqlDataReader reader) {
|
private static Message ReadMessage(DbDataReader reader) {
|
||||||
using Stream data = reader.GetStream("event_data");
|
using Stream data = reader.GetStream(reader.GetOrdinal("event_data"));
|
||||||
return new Message(
|
return new Message(
|
||||||
reader.GetInt64("event_id"),
|
reader.GetInt64(reader.GetOrdinal("event_id")),
|
||||||
reader.GetString("event_type"),
|
reader.GetString(reader.GetOrdinal("event_type")),
|
||||||
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString("event_sender"),
|
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString(reader.GetOrdinal("event_sender")),
|
||||||
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
|
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString(reader.GetOrdinal("event_sender_name")),
|
||||||
ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
|
ColourInheritable.FromMisuzu(reader.GetInt32(reader.GetOrdinal("event_sender_colour"))),
|
||||||
reader.GetInt32("event_sender_rank"),
|
reader.GetInt32(reader.GetOrdinal("event_sender_rank")),
|
||||||
MariaDBUserPermissionsConverter.From((MariaDBUserPermissions)reader.GetInt32("event_sender_perms")),
|
MariaDBUserPermissionsConverter.From((MariaDBUserPermissions)reader.GetInt32(reader.GetOrdinal("event_sender_perms"))),
|
||||||
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick"),
|
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString(reader.GetOrdinal("event_sender_nick")),
|
||||||
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
|
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32(reader.GetOrdinal("event_created"))),
|
||||||
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
|
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32(reader.GetOrdinal("event_deleted"))),
|
||||||
reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString("event_channel"),
|
reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString(reader.GetOrdinal("event_channel")),
|
||||||
JsonDocument.Parse(data)
|
JsonDocument.Parse(data)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -100,39 +103,59 @@ public class MariaDBMessageStorage(MariaDBStorage storage, ILogger logger) : Mes
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
|
public async Task<IEnumerable<Message>> GetMessages(
|
||||||
List<Message> msgs = [];
|
string? channelName = null,
|
||||||
|
int? take = 20,
|
||||||
|
long? beforeId = null,
|
||||||
|
bool includeDeleted = false
|
||||||
|
) {
|
||||||
|
List<MySqlParameter> parameters = [];
|
||||||
|
bool firstParam = true;
|
||||||
|
StringBuilder qb = new();
|
||||||
|
qb.Append("SELECT event_id, event_type, event_data, event_channel");
|
||||||
|
qb.Append(", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms");
|
||||||
|
qb.Append(", UNIX_TIMESTAMP(event_created) AS event_created");
|
||||||
|
qb.Append(", UNIX_TIMESTAMP(event_deleted) AS event_deleted");
|
||||||
|
qb.Append(" FROM sqc_events");
|
||||||
|
|
||||||
try {
|
if(!includeDeleted) {
|
||||||
using MariaDBConnection conn = await storage.CreateConnection();
|
firstParam = false;
|
||||||
using MySqlDataReader? reader = await conn.RunQuery(
|
qb.Append(" WHERE event_deleted IS NULL");
|
||||||
"SELECT event_id, event_type, event_data, event_channel"
|
|
||||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms"
|
|
||||||
+ ", UNIX_TIMESTAMP(event_created) AS event_created"
|
|
||||||
+ ", UNIX_TIMESTAMP(event_deleted) AS event_deleted"
|
|
||||||
+ " FROM sqc_events"
|
|
||||||
+ " WHERE event_deleted IS NULL AND (event_channel = @channel OR event_channel IS NULL)"
|
|
||||||
+ " AND event_id > @offset"
|
|
||||||
+ " ORDER BY event_id DESC"
|
|
||||||
+ " LIMIT @amount",
|
|
||||||
new MySqlParameter("channel", channelName),
|
|
||||||
new MySqlParameter("amount", amount),
|
|
||||||
new MySqlParameter("offset", offset)
|
|
||||||
);
|
|
||||||
if(reader is null)
|
|
||||||
return msgs;
|
|
||||||
|
|
||||||
while(reader.Read()) {
|
|
||||||
Message evt = ReadMessage(reader);
|
|
||||||
if(evt != null)
|
|
||||||
msgs.Add(evt);
|
|
||||||
}
|
|
||||||
} catch(MySqlException ex) {
|
|
||||||
logger.ZLogError($"Error in GetMessages(): {ex}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs.Reverse();
|
if(!string.IsNullOrEmpty(channelName)) {
|
||||||
|
qb.AppendFormat(" {0} (event_channel = @channel OR event_channel IS NULL)", firstParam ? "WHERE" : "AND");
|
||||||
|
parameters.Add(new MySqlParameter("channel", channelName));
|
||||||
|
firstParam = false;
|
||||||
|
}
|
||||||
|
|
||||||
return msgs;
|
if(beforeId.HasValue) {
|
||||||
|
qb.AppendFormat(" {0} event_id < @before", firstParam ? "WHERE" : "AND");
|
||||||
|
parameters.Add(new MySqlParameter("before", beforeId.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.Append(" ORDER BY event_id DESC");
|
||||||
|
|
||||||
|
if(take.HasValue) {
|
||||||
|
qb.Append(" LIMIT @take");
|
||||||
|
parameters.Add(new MySqlParameter("take", take.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
string query = string.Format("SELECT * FROM ({0}) AS _ ORDER BY event_id ASC", qb);
|
||||||
|
|
||||||
|
try {
|
||||||
|
MariaDBConnection conn = await storage.CreateConnection();
|
||||||
|
DbDataReader? reader = await conn.RunQuery(query, [.. parameters]);
|
||||||
|
|
||||||
|
if(reader is null) {
|
||||||
|
conn.Dispose();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DbObjectEnumerable<Message>(reader, ReadMessage, () => conn.Dispose());
|
||||||
|
} catch(MySqlException ex) {
|
||||||
|
logger.ZLogError($"Error in GetMessages({channelName}, {take}, {beforeId}, {includeDeleted}): {ex}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharpChat.Data;
|
||||||
using SharpChat.Messages;
|
using SharpChat.Messages;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
@ -79,50 +80,57 @@ public class SQLiteMessageStorage(ILogger logger, SQLiteConnection conn) : Messa
|
||||||
new SQLiteParameter("id", id)
|
new SQLiteParameter("id", id)
|
||||||
);
|
);
|
||||||
|
|
||||||
if(reader is null)
|
return reader?.Read() == true ? ReadMessage(reader) : null;
|
||||||
return null;
|
|
||||||
|
|
||||||
while(reader.Read()) {
|
|
||||||
Message evt = ReadMessage(reader);
|
|
||||||
if(evt != null)
|
|
||||||
return evt;
|
|
||||||
}
|
|
||||||
} catch(SQLiteException ex) {
|
} catch(SQLiteException ex) {
|
||||||
logger.ZLogError($"Error in GetMessage(): {ex}");
|
logger.ZLogError($"Error in GetMessage(): {ex}");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
|
public async Task<IEnumerable<Message>> GetMessages(
|
||||||
List<Message> msgs = [];
|
string? channelName = null,
|
||||||
|
int? take = 20,
|
||||||
|
long? beforeId = null,
|
||||||
|
bool includeDeleted = false
|
||||||
|
) {
|
||||||
|
List<SQLiteParameter> parameters = [];
|
||||||
|
bool firstParam = true;
|
||||||
|
StringBuilder qb = new();
|
||||||
|
qb.Append("SELECT msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_data");
|
||||||
|
qb.Append(", msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms");
|
||||||
|
qb.Append(" FROM messages");
|
||||||
|
|
||||||
|
if(!includeDeleted) {
|
||||||
|
firstParam = false;
|
||||||
|
qb.Append(" WHERE msg_deleted IS NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(channelName)) {
|
||||||
|
qb.AppendFormat(" {0} (msg_channel = @channel OR msg_channel IS NULL)", firstParam ? "WHERE" : "AND");
|
||||||
|
parameters.Add(new SQLiteParameter("channel", channelName));
|
||||||
|
firstParam = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(beforeId.HasValue) {
|
||||||
|
qb.AppendFormat(" {0} msg_id < @before", firstParam ? "WHERE" : "AND");
|
||||||
|
parameters.Add(new SQLiteParameter("before", beforeId.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.Append(" ORDER BY msg_id DESC");
|
||||||
|
|
||||||
|
if(take.HasValue) {
|
||||||
|
qb.Append(" LIMIT @take");
|
||||||
|
parameters.Add(new SQLiteParameter("take", take.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
string query = string.Format("SELECT * FROM ({0}) AS _ ORDER BY msg_id ASC", qb);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
using DbDataReader? reader = await conn.RunQuery(
|
DbDataReader? reader = await conn.RunQuery(query, [.. parameters]);
|
||||||
"SELECT msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms, msg_data"
|
return reader is null ? [] : new DbObjectEnumerable<Message>(reader, ReadMessage);
|
||||||
+ " FROM messages"
|
|
||||||
+ " WHERE msg_deleted IS NULL AND (msg_channel = @channel OR msg_channel IS NULL)"
|
|
||||||
+ " AND msg_id > @offset"
|
|
||||||
+ " ORDER BY msg_id DESC"
|
|
||||||
+ " LIMIT @amount",
|
|
||||||
new SQLiteParameter("channel", channelName),
|
|
||||||
new SQLiteParameter("amount", amount),
|
|
||||||
new SQLiteParameter("offset", offset)
|
|
||||||
);
|
|
||||||
if(reader is null)
|
|
||||||
return msgs;
|
|
||||||
|
|
||||||
while(reader.Read()) {
|
|
||||||
Message evt = ReadMessage(reader);
|
|
||||||
if(evt != null)
|
|
||||||
msgs.Add(evt);
|
|
||||||
}
|
|
||||||
} catch(SQLiteException ex) {
|
} catch(SQLiteException ex) {
|
||||||
logger.ZLogError($"Error in GetMessages(): {ex}");
|
logger.ZLogError($"Error in GetMessages({channelName}, {take}, {beforeId}, {includeDeleted}): {ex}");
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs.Reverse();
|
|
||||||
|
|
||||||
return msgs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
SharpChatCommon/Data/DbObjectEnumerable.cs
Normal file
14
SharpChatCommon/Data/DbObjectEnumerable.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Data.Common;
|
||||||
|
|
||||||
|
namespace SharpChat.Data;
|
||||||
|
|
||||||
|
public class DbObjectEnumerable<T>(DbDataReader reader, Func<DbDataReader, T> constructor, Action? dispose = null) : IEnumerable<T> {
|
||||||
|
public IEnumerator<T> GetEnumerator() {
|
||||||
|
return new DbObjectEnumerator<T>(reader, constructor, dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
37
SharpChatCommon/Data/DbObjectEnumerator.cs
Normal file
37
SharpChatCommon/Data/DbObjectEnumerator.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Data.Common;
|
||||||
|
|
||||||
|
namespace SharpChat.Data;
|
||||||
|
|
||||||
|
public class DbObjectEnumerator<T>(DbDataReader reader, Func<DbDataReader, T> constructor, Action? dispose) : IEnumerator<T> {
|
||||||
|
public T Current => constructor(reader);
|
||||||
|
|
||||||
|
object? IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public bool MoveNext() {
|
||||||
|
return reader.Read();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() {
|
||||||
|
// no-op cus you can't reset a DbDataReader afaik
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
~DbObjectEnumerator() {
|
||||||
|
DoDispose();
|
||||||
|
}
|
||||||
|
public void Dispose() {
|
||||||
|
DoDispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoDispose() {
|
||||||
|
if(disposed)
|
||||||
|
return;
|
||||||
|
disposed = true;
|
||||||
|
reader.Dispose();
|
||||||
|
if(dispose is not null)
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,5 +15,10 @@ public interface MessageStorage {
|
||||||
);
|
);
|
||||||
Task DeleteMessage(Message msg);
|
Task DeleteMessage(Message msg);
|
||||||
Task<Message?> GetMessage(long id);
|
Task<Message?> GetMessage(long id);
|
||||||
Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0);
|
Task<IEnumerable<Message>> GetMessages(
|
||||||
|
string? channelName = null,
|
||||||
|
int? take = 20,
|
||||||
|
long? beforeId = null,
|
||||||
|
bool includeDeleted = false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue