feat: add msglib bounded message listing
This commit is contained in:
@@ -8,7 +8,7 @@ namespace MsgLibReadSidecar
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
private const string Version = "0.3.0";
|
||||
private const string Version = "0.4.0";
|
||||
private const string DefaultPassword = "123";
|
||||
|
||||
private static readonly HashSet<string> DefaultTargetTables = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -49,6 +49,9 @@ namespace MsgLibReadSidecar
|
||||
case "message_sources":
|
||||
response = SidecarProtocol.Success(requestId, op, MessageSources(opArgs));
|
||||
break;
|
||||
case "list_messages":
|
||||
response = SidecarProtocol.Success(requestId, op, ListMessages(opArgs));
|
||||
break;
|
||||
case "display_entities":
|
||||
response = SidecarProtocol.Success(requestId, op, DisplayEntities(opArgs));
|
||||
break;
|
||||
@@ -80,7 +83,7 @@ namespace MsgLibReadSidecar
|
||||
{ "read_only", true },
|
||||
{ "mutates_db", false },
|
||||
{ "db_mutation_allowed", false },
|
||||
{ "message_body_reads_allowed", false }
|
||||
{ "message_body_reads_allowed", true }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,6 +163,89 @@ namespace MsgLibReadSidecar
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> ListMessages(Dictionary<string, object> args)
|
||||
{
|
||||
string sqliteDllPath = SidecarProtocol.GetString(args, "sqlite_dll_path", "");
|
||||
string dbPath = SidecarProtocol.GetString(args, "db_path", "");
|
||||
string password = SidecarProtocol.GetString(args, "password", DefaultPassword);
|
||||
string conversationId = SidecarProtocol.GetString(args, "conversation_id", "").Trim();
|
||||
string conversationType = SidecarProtocol.GetString(args, "conversation_type", "auto").Trim().ToLowerInvariant();
|
||||
string query = SidecarProtocol.GetString(args, "query", "").Trim();
|
||||
string since = SidecarProtocol.GetString(args, "since", "").Trim();
|
||||
string cursor = SidecarProtocol.GetString(args, "cursor", "").Trim();
|
||||
int limit = Clamp(SidecarProtocol.GetInt(args, "limit", 20), 1, 50);
|
||||
bool includeBody = SidecarProtocol.GetBool(args, "include_body", false);
|
||||
bool includeAttachmentMetadata = SidecarProtocol.GetBool(args, "include_attachment_metadata", false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cursor))
|
||||
{
|
||||
throw new ArgumentException("list_messages cursor pagination is not available yet");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(conversationType)) conversationType = "auto";
|
||||
if (!string.Equals(conversationType, "auto", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(conversationType, "direct", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(conversationType, "group", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(conversationType, "system", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ArgumentException("conversation_type must be auto, direct, group, or system");
|
||||
}
|
||||
|
||||
ValidateInputs(sqliteDllPath, dbPath);
|
||||
|
||||
var result = new Dictionary<string, object>
|
||||
{
|
||||
{ "metadata_only", !includeBody },
|
||||
{ "read_only", true },
|
||||
{ "message_body_values_returned", includeBody },
|
||||
{ "raw_rows_returned", false },
|
||||
{ "file_paths_returned", false },
|
||||
{ "limit", limit },
|
||||
{ "provider", "System.Data.SQLite" },
|
||||
{ "process_bits", IntPtr.Size * 8 },
|
||||
{ "next_cursor", null }
|
||||
};
|
||||
|
||||
using (DbConnection connection = OpenSqliteConnection(sqliteDllPath, dbPath, password))
|
||||
{
|
||||
connection.Open();
|
||||
TryExecuteNonQuery(connection, "PRAGMA query_only=ON");
|
||||
List<Dictionary<string, object>> tables = ReadMasterTables(connection, 256);
|
||||
HashSet<string> listTables = BuildListMessagesTableSet();
|
||||
List<Dictionary<string, object>> columns = ReadColumns(connection, tables, listTables);
|
||||
Dictionary<string, HashSet<string>> columnMap = BuildColumnMap(columns);
|
||||
var sourceTables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var messages = new List<Dictionary<string, object>>();
|
||||
|
||||
if (ShouldReadMessageRole(conversationType, "direct"))
|
||||
{
|
||||
ReadDirectMessages(connection, columnMap, conversationId, query, since, limit, includeBody, includeAttachmentMetadata, messages, sourceTables);
|
||||
}
|
||||
if (ShouldReadMessageRole(conversationType, "group"))
|
||||
{
|
||||
ReadGroupMessages(connection, columnMap, conversationId, query, since, limit, includeBody, includeAttachmentMetadata, messages, sourceTables);
|
||||
}
|
||||
if (ShouldReadMessageRole(conversationType, "system"))
|
||||
{
|
||||
ReadSystemMessages(connection, columnMap, conversationId, query, since, limit, includeBody, includeAttachmentMetadata, messages, sourceTables);
|
||||
}
|
||||
|
||||
messages.Sort(CompareMessagesDescending);
|
||||
while (messages.Count > limit)
|
||||
{
|
||||
messages.RemoveAt(messages.Count - 1);
|
||||
}
|
||||
foreach (Dictionary<string, object> message in messages)
|
||||
{
|
||||
message.Remove("sort_key");
|
||||
}
|
||||
|
||||
result["source_tables"] = ToSortedArray(sourceTables);
|
||||
result["messages"] = messages;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private static Dictionary<string, object> DisplayEntities(Dictionary<string, object> args)
|
||||
{
|
||||
@@ -476,6 +562,511 @@ namespace MsgLibReadSidecar
|
||||
});
|
||||
}
|
||||
|
||||
private static HashSet<string> BuildListMessagesTableSet()
|
||||
{
|
||||
return new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"tblPersonMsg",
|
||||
"tblMsgGroupPersonMsg",
|
||||
"TD_SystemMessageRecord",
|
||||
"TD_ReceiveFileRecord"
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ShouldReadMessageRole(string requested, string role)
|
||||
{
|
||||
return string.Equals(requested, "auto", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(requested, role, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void ReadDirectMessages(
|
||||
DbConnection connection,
|
||||
Dictionary<string, HashSet<string>> columnMap,
|
||||
string conversationId,
|
||||
string query,
|
||||
string since,
|
||||
int limit,
|
||||
bool includeBody,
|
||||
bool includeAttachmentMetadata,
|
||||
List<Dictionary<string, object>> messages,
|
||||
HashSet<string> sourceTables)
|
||||
{
|
||||
const string table = "tblPersonMsg";
|
||||
if (!HasColumns(columnMap, table, new string[] { "Id" })) return;
|
||||
|
||||
string senderIdColumn = FirstExistingColumn(columnMap, table, new string[] { "SndPersonId", "SenderId" });
|
||||
string senderNameColumn = FirstExistingColumn(columnMap, table, new string[] { "SndPerson", "SenderName" });
|
||||
string receiverIdColumn = FirstExistingColumn(columnMap, table, new string[] { "RecvPersonId", "ReceiverId" });
|
||||
string receiverNameColumn = FirstExistingColumn(columnMap, table, new string[] { "RecvPerson", "ReceiverName" });
|
||||
string objectIdColumn = FirstExistingColumn(columnMap, table, new string[] { "ObjPersonId", "PersonId" });
|
||||
string objectNameColumn = FirstExistingColumn(columnMap, table, new string[] { "ObjPerson", "PersonName" });
|
||||
string timeColumn = FirstExistingColumn(columnMap, table, new string[] { "ActionTime", "MsgTime", "CreateTime", "SendTime" });
|
||||
string readColumn = FirstExistingColumn(columnMap, table, new string[] { "IsRead", "ReadState", "MsgReadState" });
|
||||
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
var where = new List<string>();
|
||||
where.Add("coalesce(" + QuoteIdent("Id") + ", '') <> ''");
|
||||
AddListMessageWhere(command, where, table, columnMap, conversationId, query, since, timeColumn,
|
||||
new string[] { senderIdColumn, receiverIdColumn, objectIdColumn },
|
||||
new string[] { "Id", senderIdColumn, senderNameColumn, receiverIdColumn, receiverNameColumn, objectIdColumn, objectNameColumn },
|
||||
includeBody);
|
||||
|
||||
command.CommandText =
|
||||
"select " +
|
||||
QuoteIdent("Id") + " as message_id, " +
|
||||
SelectColumn(senderIdColumn, "sender_id") + ", " +
|
||||
SelectColumn(FirstNonEmpty(senderNameColumn, objectNameColumn), "sender_name") + ", " +
|
||||
SelectColumn(receiverIdColumn, "receiver_id") + ", " +
|
||||
SelectColumn(FirstNonEmpty(objectIdColumn, receiverIdColumn, senderIdColumn), "conversation_id") + ", " +
|
||||
SelectColumn(timeColumn, "timestamp") + ", " +
|
||||
SelectColumn(readColumn, "read_state") + ", " +
|
||||
BodySelect(columnMap, table, includeBody) +
|
||||
" from " + QuoteIdent(table) +
|
||||
" where " + string.Join(" and ", where.ToArray()) +
|
||||
" order by " + OrderByExpression(timeColumn) + " desc limit " + Convert.ToString(limit);
|
||||
|
||||
using (DbDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read() && messages.Count < limit * 3)
|
||||
{
|
||||
string messageId = ReaderString(reader, "message_id");
|
||||
string timestamp = ReaderString(reader, "timestamp");
|
||||
string contentText = includeBody ? ReaderString(reader, "content_text") : "";
|
||||
List<Dictionary<string, object>> attachments = includeAttachmentMetadata ? ReadAttachmentsForMessage(connection, columnMap, messageId) : new List<Dictionary<string, object>>();
|
||||
messages.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "message_id", messageId },
|
||||
{ "id", messageId },
|
||||
{ "conversation_id", ReaderString(reader, "conversation_id") },
|
||||
{ "conversation_type", "direct" },
|
||||
{ "sender_id", ReaderString(reader, "sender_id") },
|
||||
{ "sender_name", ReaderString(reader, "sender_name") },
|
||||
{ "receiver_id", ReaderString(reader, "receiver_id") },
|
||||
{ "text", contentText },
|
||||
{ "content_text", contentText },
|
||||
{ "content_type", ContentType(contentText, attachments) },
|
||||
{ "timestamp", timestamp },
|
||||
{ "created_at", timestamp },
|
||||
{ "subject", "" },
|
||||
{ "read", ReaderBool(reader, "read_state") },
|
||||
{ "source", "local_readonly" },
|
||||
{ "raw_ref", "msglib:" + table },
|
||||
{ "attachments", attachments },
|
||||
{ "sort_key", timestamp + "|" + messageId }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceTables.Add(table);
|
||||
}
|
||||
|
||||
private static void ReadGroupMessages(
|
||||
DbConnection connection,
|
||||
Dictionary<string, HashSet<string>> columnMap,
|
||||
string conversationId,
|
||||
string query,
|
||||
string since,
|
||||
int limit,
|
||||
bool includeBody,
|
||||
bool includeAttachmentMetadata,
|
||||
List<Dictionary<string, object>> messages,
|
||||
HashSet<string> sourceTables)
|
||||
{
|
||||
const string table = "tblMsgGroupPersonMsg";
|
||||
if (!HasColumns(columnMap, table, new string[] { "Id" })) return;
|
||||
|
||||
string senderIdColumn = FirstExistingColumn(columnMap, table, new string[] { "SndPersonId", "SessionPersonId", "PersonId" });
|
||||
string senderNameColumn = FirstExistingColumn(columnMap, table, new string[] { "SndPerson", "SessionPersonName", "PersonName" });
|
||||
string groupIdColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgGroupId", "GroupJID", "GroupId" });
|
||||
string groupNameColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgGroupName", "GroupName", "SessionPersonName" });
|
||||
string timeColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgTime", "ActionTime", "CreateTime", "SendTime" });
|
||||
string readColumn = FirstExistingColumn(columnMap, table, new string[] { "IsRead", "ReadState", "MsgReadState" });
|
||||
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
var where = new List<string>();
|
||||
where.Add("coalesce(" + QuoteIdent("Id") + ", '') <> ''");
|
||||
AddListMessageWhere(command, where, table, columnMap, conversationId, query, since, timeColumn,
|
||||
new string[] { groupIdColumn, senderIdColumn },
|
||||
new string[] { "Id", senderIdColumn, senderNameColumn, groupIdColumn, groupNameColumn },
|
||||
includeBody);
|
||||
|
||||
command.CommandText =
|
||||
"select " +
|
||||
QuoteIdent("Id") + " as message_id, " +
|
||||
SelectColumn(senderIdColumn, "sender_id") + ", " +
|
||||
SelectColumn(senderNameColumn, "sender_name") + ", " +
|
||||
SelectColumn(groupIdColumn, "conversation_id") + ", " +
|
||||
SelectColumn(timeColumn, "timestamp") + ", " +
|
||||
SelectColumn(readColumn, "read_state") + ", " +
|
||||
BodySelect(columnMap, table, includeBody) +
|
||||
" from " + QuoteIdent(table) +
|
||||
" where " + string.Join(" and ", where.ToArray()) +
|
||||
" order by " + OrderByExpression(timeColumn) + " desc limit " + Convert.ToString(limit);
|
||||
|
||||
using (DbDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read() && messages.Count < limit * 3)
|
||||
{
|
||||
string messageId = ReaderString(reader, "message_id");
|
||||
string timestamp = ReaderString(reader, "timestamp");
|
||||
string contentText = includeBody ? ReaderString(reader, "content_text") : "";
|
||||
List<Dictionary<string, object>> attachments = includeAttachmentMetadata ? ReadAttachmentsForMessage(connection, columnMap, messageId) : new List<Dictionary<string, object>>();
|
||||
messages.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "message_id", messageId },
|
||||
{ "id", messageId },
|
||||
{ "conversation_id", ReaderString(reader, "conversation_id") },
|
||||
{ "conversation_type", "group" },
|
||||
{ "sender_id", ReaderString(reader, "sender_id") },
|
||||
{ "sender_name", ReaderString(reader, "sender_name") },
|
||||
{ "receiver_id", "" },
|
||||
{ "text", contentText },
|
||||
{ "content_text", contentText },
|
||||
{ "content_type", ContentType(contentText, attachments) },
|
||||
{ "timestamp", timestamp },
|
||||
{ "created_at", timestamp },
|
||||
{ "subject", "" },
|
||||
{ "read", ReaderBool(reader, "read_state") },
|
||||
{ "source", "local_readonly" },
|
||||
{ "raw_ref", "msglib:" + table },
|
||||
{ "attachments", attachments },
|
||||
{ "sort_key", timestamp + "|" + messageId }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceTables.Add(table);
|
||||
}
|
||||
|
||||
private static void ReadSystemMessages(
|
||||
DbConnection connection,
|
||||
Dictionary<string, HashSet<string>> columnMap,
|
||||
string conversationId,
|
||||
string query,
|
||||
string since,
|
||||
int limit,
|
||||
bool includeBody,
|
||||
bool includeAttachmentMetadata,
|
||||
List<Dictionary<string, object>> messages,
|
||||
HashSet<string> sourceTables)
|
||||
{
|
||||
const string table = "TD_SystemMessageRecord";
|
||||
string idColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgGuid", "Id" });
|
||||
if (string.IsNullOrWhiteSpace(idColumn)) return;
|
||||
|
||||
string senderIdColumn = FirstExistingColumn(columnMap, table, new string[] { "SendPersonJid", "PersonID", "SourceID" });
|
||||
string senderNameColumn = FirstExistingColumn(columnMap, table, new string[] { "SendPersonName", "PersonName", "SourceName" });
|
||||
string titleColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgTitle", "Title" });
|
||||
string timeColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgTime", "ActionTime", "CreateTime", "SendTime" });
|
||||
string readColumn = FirstExistingColumn(columnMap, table, new string[] { "MsgReadState", "IsRead", "ReadState" });
|
||||
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
var where = new List<string>();
|
||||
where.Add("coalesce(" + QuoteIdent(idColumn) + ", '') <> ''");
|
||||
AddListMessageWhere(command, where, table, columnMap, conversationId, query, since, timeColumn,
|
||||
new string[] { senderIdColumn },
|
||||
new string[] { idColumn, senderIdColumn, senderNameColumn, titleColumn },
|
||||
includeBody);
|
||||
|
||||
command.CommandText =
|
||||
"select " +
|
||||
QuoteIdent(idColumn) + " as message_id, " +
|
||||
SelectColumn(senderIdColumn, "sender_id") + ", " +
|
||||
SelectColumn(senderNameColumn, "sender_name") + ", " +
|
||||
SelectColumn(titleColumn, "subject") + ", " +
|
||||
SelectColumn(timeColumn, "timestamp") + ", " +
|
||||
SelectColumn(readColumn, "read_state") + ", " +
|
||||
BodySelect(columnMap, table, includeBody) +
|
||||
" from " + QuoteIdent(table) +
|
||||
" where " + string.Join(" and ", where.ToArray()) +
|
||||
" order by " + OrderByExpression(timeColumn) + " desc limit " + Convert.ToString(limit);
|
||||
|
||||
using (DbDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read() && messages.Count < limit * 3)
|
||||
{
|
||||
string messageId = ReaderString(reader, "message_id");
|
||||
string timestamp = ReaderString(reader, "timestamp");
|
||||
string contentText = includeBody ? ReaderString(reader, "content_text") : "";
|
||||
List<Dictionary<string, object>> attachments = includeAttachmentMetadata ? ReadAttachmentsForMessage(connection, columnMap, messageId) : new List<Dictionary<string, object>>();
|
||||
messages.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "message_id", messageId },
|
||||
{ "id", messageId },
|
||||
{ "conversation_id", ReaderString(reader, "sender_id") },
|
||||
{ "conversation_type", "system" },
|
||||
{ "sender_id", ReaderString(reader, "sender_id") },
|
||||
{ "sender_name", ReaderString(reader, "sender_name") },
|
||||
{ "receiver_id", "" },
|
||||
{ "text", contentText },
|
||||
{ "content_text", contentText },
|
||||
{ "content_type", ContentType(contentText, attachments) },
|
||||
{ "timestamp", timestamp },
|
||||
{ "created_at", timestamp },
|
||||
{ "subject", ReaderString(reader, "subject") },
|
||||
{ "read", ReaderBool(reader, "read_state") },
|
||||
{ "source", "local_readonly" },
|
||||
{ "raw_ref", "msglib:" + table },
|
||||
{ "attachments", attachments },
|
||||
{ "sort_key", timestamp + "|" + messageId }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceTables.Add(table);
|
||||
}
|
||||
|
||||
private static void AddListMessageWhere(
|
||||
DbCommand command,
|
||||
List<string> where,
|
||||
string table,
|
||||
Dictionary<string, HashSet<string>> columnMap,
|
||||
string conversationId,
|
||||
string query,
|
||||
string since,
|
||||
string timeColumn,
|
||||
string[] conversationColumns,
|
||||
string[] queryColumns,
|
||||
bool includeBody)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(conversationId))
|
||||
{
|
||||
var parts = new List<string>();
|
||||
foreach (string column in conversationColumns)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(column) && HasColumns(columnMap, table, new string[] { column }))
|
||||
{
|
||||
parts.Add(QuoteIdent(column) + " = @conversation_id");
|
||||
}
|
||||
}
|
||||
if (parts.Count > 0)
|
||||
{
|
||||
where.Add("(" + string.Join(" or ", parts.ToArray()) + ")");
|
||||
AddParameter(command, "@conversation_id", conversationId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
var parts = new List<string>();
|
||||
foreach (string column in queryColumns)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(column) && HasColumns(columnMap, table, new string[] { column }))
|
||||
{
|
||||
parts.Add(QuoteIdent(column) + " like @query");
|
||||
}
|
||||
}
|
||||
if (includeBody)
|
||||
{
|
||||
if (HasColumns(columnMap, table, new string[] { "MsgText" })) parts.Add(QuoteIdent("MsgText") + " like @query");
|
||||
if (HasColumns(columnMap, table, new string[] { "MessageBody" })) parts.Add(QuoteIdent("MessageBody") + " like @query");
|
||||
}
|
||||
if (parts.Count > 0)
|
||||
{
|
||||
where.Add("(" + string.Join(" or ", parts.ToArray()) + ")");
|
||||
AddParameter(command, "@query", "%" + query + "%");
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(since) && !string.IsNullOrWhiteSpace(timeColumn) && HasColumns(columnMap, table, new string[] { timeColumn }))
|
||||
{
|
||||
where.Add(QuoteIdent(timeColumn) + " >= @since");
|
||||
AddParameter(command, "@since", since);
|
||||
}
|
||||
}
|
||||
|
||||
private static string BodySelect(Dictionary<string, HashSet<string>> columnMap, string table, bool includeBody)
|
||||
{
|
||||
if (!includeBody)
|
||||
{
|
||||
return "'' as content_text";
|
||||
}
|
||||
|
||||
var expressions = new List<string>();
|
||||
if (HasColumns(columnMap, table, new string[] { "MsgText" })) expressions.Add(QuoteIdent("MsgText"));
|
||||
if (HasColumns(columnMap, table, new string[] { "MessageBody" })) expressions.Add(QuoteIdent("MessageBody"));
|
||||
if (expressions.Count == 0)
|
||||
{
|
||||
return "'' as content_text";
|
||||
}
|
||||
expressions.Add("''");
|
||||
return "coalesce(" + string.Join(", ", expressions.ToArray()) + ") as content_text";
|
||||
}
|
||||
|
||||
private static List<Dictionary<string, object>> ReadAttachmentsForMessage(DbConnection connection, Dictionary<string, HashSet<string>> columnMap, string messageId)
|
||||
{
|
||||
var attachments = new List<Dictionary<string, object>>();
|
||||
const string table = "TD_ReceiveFileRecord";
|
||||
if (string.IsNullOrWhiteSpace(messageId)) return attachments;
|
||||
if (!HasColumns(columnMap, table, new string[] { "ReceiveMsgGuid", "FileName" })) return attachments;
|
||||
|
||||
string sizeColumn = FirstExistingColumn(columnMap, table, new string[] { "FileSize", "Size" });
|
||||
string sourceColumn = FirstExistingColumn(columnMap, table, new string[] { "SourceID", "FileGuid", "FileId" });
|
||||
using (DbCommand command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText =
|
||||
"select " +
|
||||
SelectColumn(sourceColumn, "source_id") + ", " +
|
||||
SelectColumn("FileName", "file_name") + ", " +
|
||||
SelectColumn(sizeColumn, "size_bytes") +
|
||||
" from " + QuoteIdent(table) +
|
||||
" where " + QuoteIdent("ReceiveMsgGuid") + " = @message_id limit 20";
|
||||
AddParameter(command, "@message_id", messageId);
|
||||
|
||||
using (DbDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
string fileName = SafeFileName(ReaderString(reader, "file_name"));
|
||||
if (string.IsNullOrWhiteSpace(fileName)) continue;
|
||||
string sourceId = ReaderString(reader, "source_id");
|
||||
attachments.Add(new Dictionary<string, object>
|
||||
{
|
||||
{ "file_id", FirstNonEmpty(sourceId, messageId + ":" + fileName) },
|
||||
{ "file_name", fileName },
|
||||
{ "size_bytes", ReaderInt64(reader, "size_bytes") },
|
||||
{ "download_ref", "" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private static bool HasColumns(Dictionary<string, HashSet<string>> columnMap, string table, string[] columns)
|
||||
{
|
||||
if (!columnMap.ContainsKey(table)) return false;
|
||||
foreach (string column in columns)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(column)) return false;
|
||||
if (!columnMap[table].Contains(column)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string FirstExistingColumn(Dictionary<string, HashSet<string>> columnMap, string table, string[] columns)
|
||||
{
|
||||
if (!columnMap.ContainsKey(table)) return "";
|
||||
foreach (string column in columns)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(column) && columnMap[table].Contains(column))
|
||||
{
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static string SelectColumn(string column, string alias)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(column))
|
||||
{
|
||||
return "'' as " + QuoteIdent(alias);
|
||||
}
|
||||
return QuoteIdent(column) + " as " + QuoteIdent(alias);
|
||||
}
|
||||
|
||||
private static string OrderByExpression(string column)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(column))
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
return QuoteIdent(column);
|
||||
}
|
||||
|
||||
private static string ReaderString(DbDataReader reader, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
object value = reader[name];
|
||||
if (value == null || value == DBNull.Value) return "";
|
||||
return Convert.ToString(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ReaderBool(DbDataReader reader, string name)
|
||||
{
|
||||
string value = ReaderString(reader, name).Trim();
|
||||
if (string.IsNullOrWhiteSpace(value)) return false;
|
||||
return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "read", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "yes", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static long ReaderInt64(DbDataReader reader, string name)
|
||||
{
|
||||
string value = ReaderString(reader, name).Trim();
|
||||
long parsed;
|
||||
if (long.TryParse(value, out parsed)) return parsed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
foreach (string value in values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value)) return value;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static string ContentType(string contentText, List<Dictionary<string, object>> attachments)
|
||||
{
|
||||
if (attachments != null && attachments.Count > 0) return "file";
|
||||
if (string.IsNullOrWhiteSpace(contentText)) return "unknown";
|
||||
return "text";
|
||||
}
|
||||
|
||||
private static int CompareMessagesDescending(Dictionary<string, object> left, Dictionary<string, object> right)
|
||||
{
|
||||
string leftKey = left.ContainsKey("sort_key") ? Convert.ToString(left["sort_key"]) : "";
|
||||
string rightKey = right.ContainsKey("sort_key") ? Convert.ToString(right["sort_key"]) : "";
|
||||
return string.Compare(rightKey, leftKey, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string[] ToSortedArray(HashSet<string> values)
|
||||
{
|
||||
var list = new List<string>();
|
||||
foreach (string value in values)
|
||||
{
|
||||
list.Add(value);
|
||||
}
|
||||
list.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static void AddParameter(DbCommand command, string name, object value)
|
||||
{
|
||||
DbParameter parameter = command.CreateParameter();
|
||||
parameter.ParameterName = name;
|
||||
parameter.Value = value ?? DBNull.Value;
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
|
||||
private static string SafeFileName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return "";
|
||||
try
|
||||
{
|
||||
return Path.GetFileName(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
int slash = Math.Max(value.LastIndexOf('/'), value.LastIndexOf('\\'));
|
||||
if (slash >= 0 && slash + 1 < value.Length) return value.Substring(slash + 1);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private sealed class DisplayEntitySpec
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user