1255 lines
61 KiB
C#
1255 lines
61 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Common;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
|
|
namespace MsgLibReadSidecar
|
|
{
|
|
internal static class Program
|
|
{
|
|
private const string Version = "0.4.0";
|
|
private const string DefaultPassword = "123";
|
|
|
|
private static readonly HashSet<string> DefaultTargetTables = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"TD_Roster", "TD_CustomEffigy", "tblRecent", "tblChatLevel", "tblPersonMsg", "tblMsgGroupPersonMsg",
|
|
"TD_WorkGroupAuth", "TD_ReceiveFileRecord", "TD_SendFileRecord", "TD_SystemMessageRecord", "TD_MsgReadBurn"
|
|
};
|
|
|
|
[STAThread]
|
|
private static int Main(string[] args)
|
|
{
|
|
string requestText = Console.In.ReadToEnd();
|
|
Dictionary<string, object> request;
|
|
string requestId = "";
|
|
string op = "";
|
|
|
|
if (!SidecarProtocol.TryParseRequest(requestText, out request, out requestId, out op))
|
|
{
|
|
Console.WriteLine(SidecarProtocol.ToJson(SidecarProtocol.Failure(requestId, op, "INVALID_REQUEST", "request must be a valid isphere.msglib.v1 JSON object")));
|
|
return 0;
|
|
}
|
|
|
|
try
|
|
{
|
|
Dictionary<string, object> opArgs = SidecarProtocol.GetObject(request, "args");
|
|
Dictionary<string, object> response;
|
|
switch (op)
|
|
{
|
|
case "self_check":
|
|
response = SidecarProtocol.Success(requestId, op, SelfCheckData());
|
|
break;
|
|
case "schema_summary":
|
|
response = SidecarProtocol.Success(requestId, op, SchemaSummary(opArgs, false));
|
|
break;
|
|
case "display_sources":
|
|
response = SidecarProtocol.Success(requestId, op, SchemaSummary(opArgs, true));
|
|
break;
|
|
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;
|
|
default:
|
|
response = SidecarProtocol.Failure(requestId, op, "UNSUPPORTED_OP", "unsupported op: " + op);
|
|
break;
|
|
}
|
|
Console.WriteLine(SidecarProtocol.ToJson(response));
|
|
return 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine(ex.ToString());
|
|
Console.WriteLine(SidecarProtocol.ToJson(SidecarProtocol.Failure(requestId, op, "SIDECAR_FAILED", ex.Message)));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private static Dictionary<string, object> SelfCheckData()
|
|
{
|
|
return new Dictionary<string, object>
|
|
{
|
|
{ "sidecar_name", "MsgLibReadSidecar" },
|
|
{ "sidecar_version", Version },
|
|
{ "protocol", SidecarProtocol.Protocol },
|
|
{ "runtime", ".NET Framework " + Environment.Version },
|
|
{ "process_bits", IntPtr.Size * 8 },
|
|
{ "requires_process_bits", 32 },
|
|
{ "read_only", true },
|
|
{ "mutates_db", false },
|
|
{ "db_mutation_allowed", false },
|
|
{ "message_body_reads_allowed", true }
|
|
};
|
|
}
|
|
|
|
private static Dictionary<string, object> SchemaSummary(Dictionary<string, object> args, bool displayOnly)
|
|
{
|
|
string sqliteDllPath = SidecarProtocol.GetString(args, "sqlite_dll_path", "");
|
|
string dbPath = SidecarProtocol.GetString(args, "db_path", "");
|
|
string password = SidecarProtocol.GetString(args, "password", DefaultPassword);
|
|
bool includeCounts = SidecarProtocol.GetBool(args, "include_counts", true);
|
|
int maxTables = Clamp(SidecarProtocol.GetInt(args, "max_tables", 128), 1, 256);
|
|
List<string> requestedTargets = SidecarProtocol.GetStringList(args, "target_tables");
|
|
HashSet<string> targetTables = requestedTargets.Count == 0 ? DefaultTargetTables : new HashSet<string>(requestedTargets, StringComparer.OrdinalIgnoreCase);
|
|
|
|
ValidateInputs(sqliteDllPath, dbPath);
|
|
|
|
var result = new Dictionary<string, object>
|
|
{
|
|
{ "metadata_only", true },
|
|
{ "read_only", true },
|
|
{ "message_body_values_returned", false },
|
|
{ "sqlite_dll_path", sqliteDllPath },
|
|
{ "db_path", dbPath },
|
|
{ "provider", "System.Data.SQLite" },
|
|
{ "process_bits", IntPtr.Size * 8 }
|
|
};
|
|
|
|
using (DbConnection connection = OpenSqliteConnection(sqliteDllPath, dbPath, password))
|
|
{
|
|
connection.Open();
|
|
TryExecuteNonQuery(connection, "PRAGMA query_only=ON");
|
|
result["sqlite_version"] = ExecuteScalar(connection, "select sqlite_version()");
|
|
List<Dictionary<string, object>> tables = ReadMasterTables(connection, maxTables);
|
|
List<Dictionary<string, object>> columns = ReadColumns(connection, tables, displayOnly ? targetTables : null);
|
|
result["tables"] = tables;
|
|
result["columns"] = columns;
|
|
result["target_row_counts"] = includeCounts ? ReadTargetCounts(connection, tables, targetTables) : new List<Dictionary<string, object>>();
|
|
if (displayOnly)
|
|
{
|
|
result["display_sources"] = BuildDisplaySources(tables, columns);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static Dictionary<string, object> MessageSources(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);
|
|
|
|
ValidateInputs(sqliteDllPath, dbPath);
|
|
|
|
var result = new Dictionary<string, object>
|
|
{
|
|
{ "metadata_only", true },
|
|
{ "read_only", true },
|
|
{ "message_body_values_returned", false },
|
|
{ "raw_rows_returned", false },
|
|
{ "file_paths_returned", false },
|
|
{ "provider", "System.Data.SQLite" },
|
|
{ "process_bits", IntPtr.Size * 8 }
|
|
};
|
|
|
|
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> messageTables = BuildMessageSourceTableSet();
|
|
List<Dictionary<string, object>> columns = ReadColumns(connection, tables, messageTables);
|
|
Dictionary<string, HashSet<string>> columnMap = BuildColumnMap(columns);
|
|
Dictionary<string, string> rowCounts = ReadTargetCountMap(connection, tables, messageTables);
|
|
result["message_sources"] = BuildMessageSources(tables, columnMap, rowCounts);
|
|
}
|
|
|
|
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)
|
|
{
|
|
string sqliteDllPath = SidecarProtocol.GetString(args, "sqlite_dll_path", "");
|
|
string dbPath = SidecarProtocol.GetString(args, "db_path", "");
|
|
string password = SidecarProtocol.GetString(args, "password", DefaultPassword);
|
|
string entityType = SidecarProtocol.GetString(args, "entity_type", "").Trim().ToLowerInvariant();
|
|
string query = SidecarProtocol.GetString(args, "query", "").Trim();
|
|
int limit = Clamp(SidecarProtocol.GetInt(args, "limit", 25), 1, 100);
|
|
|
|
if (!string.Equals(entityType, "contacts", StringComparison.OrdinalIgnoreCase) &&
|
|
!string.Equals(entityType, "groups", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new ArgumentException("entity_type must be contacts or groups");
|
|
}
|
|
|
|
ValidateInputs(sqliteDllPath, dbPath);
|
|
|
|
var result = new Dictionary<string, object>
|
|
{
|
|
{ "metadata_only", true },
|
|
{ "read_only", true },
|
|
{ "message_body_values_returned", false },
|
|
{ "raw_rows_returned", false },
|
|
{ "entity_type", entityType },
|
|
{ "limit", limit },
|
|
{ "provider", "System.Data.SQLite" },
|
|
{ "process_bits", IntPtr.Size * 8 }
|
|
};
|
|
|
|
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> entityTables = BuildDisplayEntityTableSet(entityType);
|
|
List<Dictionary<string, object>> columns = ReadColumns(connection, tables, entityTables);
|
|
Dictionary<string, HashSet<string>> columnMap = BuildColumnMap(columns);
|
|
List<Dictionary<string, object>> entities = ReadDisplayEntities(connection, entityType, query, limit, columnMap);
|
|
result["entities"] = entities;
|
|
result["result_count"] = entities.Count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void ValidateInputs(string sqliteDllPath, string dbPath)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sqliteDllPath) || !File.Exists(sqliteDllPath))
|
|
{
|
|
throw new ArgumentException("sqlite_dll_path must point to an existing System.Data.SQLite.dll");
|
|
}
|
|
if (string.IsNullOrWhiteSpace(dbPath) || !File.Exists(dbPath))
|
|
{
|
|
throw new ArgumentException("db_path must point to an existing copied MsgLib.db");
|
|
}
|
|
if (!string.Equals(Path.GetFileName(dbPath), "MsgLib.db", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new ArgumentException("db_path must point to a copied MsgLib.db file");
|
|
}
|
|
}
|
|
|
|
private static DbConnection OpenSqliteConnection(string sqliteDllPath, string dbPath, string password)
|
|
{
|
|
Assembly assembly = Assembly.LoadFrom(sqliteDllPath);
|
|
Type builderType = assembly.GetType("System.Data.SQLite.SQLiteConnectionStringBuilder", true);
|
|
object builder = Activator.CreateInstance(builderType);
|
|
SetPropertyIfExists(builderType, builder, "DataSource", dbPath);
|
|
SetPropertyIfExists(builderType, builder, "Password", password);
|
|
SetPropertyIfExists(builderType, builder, "ReadOnly", true);
|
|
SetPropertyIfExists(builderType, builder, "Pooling", false);
|
|
SetPropertyIfExists(builderType, builder, "FailIfMissing", true);
|
|
string connectionString = Convert.ToString(builderType.GetProperty("ConnectionString").GetValue(builder, null));
|
|
Type connectionType = assembly.GetType("System.Data.SQLite.SQLiteConnection", true);
|
|
object connection = Activator.CreateInstance(connectionType, new object[] { connectionString });
|
|
DbConnection dbConnection = connection as DbConnection;
|
|
if (dbConnection == null)
|
|
{
|
|
throw new InvalidOperationException("SQLiteConnection is not a DbConnection");
|
|
}
|
|
return dbConnection;
|
|
}
|
|
|
|
private static void SetPropertyIfExists(Type type, object target, string name, object value)
|
|
{
|
|
PropertyInfo property = type.GetProperty(name);
|
|
if (property != null && property.CanWrite)
|
|
{
|
|
property.SetValue(target, value, null);
|
|
}
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> ReadMasterTables(DbConnection connection, int maxTables)
|
|
{
|
|
var rows = new List<Dictionary<string, object>>();
|
|
using (DbCommand command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = "select type, name, tbl_name from sqlite_master where type in ('table','view') order by type, name";
|
|
using (DbDataReader reader = command.ExecuteReader())
|
|
{
|
|
while (reader.Read() && rows.Count < maxTables)
|
|
{
|
|
rows.Add(new Dictionary<string, object>
|
|
{
|
|
{ "type", Convert.ToString(reader["type"]) },
|
|
{ "name", Convert.ToString(reader["name"]) },
|
|
{ "tbl_name", Convert.ToString(reader["tbl_name"]) }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> ReadColumns(DbConnection connection, List<Dictionary<string, object>> tables, HashSet<string> onlyTables)
|
|
{
|
|
var rows = new List<Dictionary<string, object>>();
|
|
foreach (Dictionary<string, object> table in tables)
|
|
{
|
|
string tableName = Convert.ToString(table["name"]);
|
|
string tableType = Convert.ToString(table["type"]);
|
|
if (!string.Equals(tableType, "table", StringComparison.OrdinalIgnoreCase)) continue;
|
|
if (onlyTables != null && !onlyTables.Contains(tableName)) continue;
|
|
|
|
using (DbCommand command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = "PRAGMA table_info(" + QuoteIdent(tableName) + ")";
|
|
using (DbDataReader reader = command.ExecuteReader())
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
rows.Add(new Dictionary<string, object>
|
|
{
|
|
{ "table", tableName },
|
|
{ "cid", Convert.ToString(reader["cid"]) },
|
|
{ "name", Convert.ToString(reader["name"]) },
|
|
{ "type", Convert.ToString(reader["type"]) },
|
|
{ "notnull", Convert.ToString(reader["notnull"]) },
|
|
{ "pk", Convert.ToString(reader["pk"]) }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> ReadTargetCounts(DbConnection connection, List<Dictionary<string, object>> tables, HashSet<string> targetTables)
|
|
{
|
|
var rows = new List<Dictionary<string, object>>();
|
|
foreach (Dictionary<string, object> table in tables)
|
|
{
|
|
string tableName = Convert.ToString(table["name"]);
|
|
string tableType = Convert.ToString(table["type"]);
|
|
if (!string.Equals(tableType, "table", StringComparison.OrdinalIgnoreCase)) continue;
|
|
if (!targetTables.Contains(tableName)) continue;
|
|
rows.Add(new Dictionary<string, object>
|
|
{
|
|
{ "table", tableName },
|
|
{ "row_count", ExecuteScalar(connection, "select count(*) from " + QuoteIdent(tableName)) }
|
|
});
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
private static Dictionary<string, string> ReadTargetCountMap(DbConnection connection, List<Dictionary<string, object>> tables, HashSet<string> targetTables)
|
|
{
|
|
var rowCounts = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (Dictionary<string, object> item in ReadTargetCounts(connection, tables, targetTables))
|
|
{
|
|
rowCounts[Convert.ToString(item["table"])] = Convert.ToString(item["row_count"]);
|
|
}
|
|
return rowCounts;
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> BuildDisplaySources(List<Dictionary<string, object>> tables, List<Dictionary<string, object>> columns)
|
|
{
|
|
var tableSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (Dictionary<string, object> table in tables)
|
|
{
|
|
if (string.Equals(Convert.ToString(table["type"]), "table", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
tableSet.Add(Convert.ToString(table["name"]));
|
|
}
|
|
}
|
|
|
|
var columnMap = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (Dictionary<string, object> column in columns)
|
|
{
|
|
string table = Convert.ToString(column["table"]);
|
|
string name = Convert.ToString(column["name"]);
|
|
if (!columnMap.ContainsKey(table)) columnMap[table] = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
columnMap[table].Add(name);
|
|
}
|
|
|
|
var sources = new List<Dictionary<string, object>>();
|
|
AddSource(sources, tableSet, columnMap, "contacts_roster", "TD_Roster", new string[] { "JId", "Nick", "GroupName" });
|
|
AddSource(sources, tableSet, columnMap, "contacts_effigy", "TD_CustomEffigy", new string[] { "PersonJid", "PersonName" });
|
|
AddSource(sources, tableSet, columnMap, "recent_display", "tblRecent", new string[] { "PersonId", "PersonName", "FType" });
|
|
AddSource(sources, tableSet, columnMap, "person_message_names", "tblPersonMsg", new string[] { "SndPerson", "SndPersonId", "RecvPerson", "RecvPersonId", "ObjPerson", "ObjPersonId" });
|
|
AddSource(sources, tableSet, columnMap, "group_message_names", "tblMsgGroupPersonMsg", new string[] { "MsgGroupId", "MsgGroupName", "SessionPersonId", "SessionPersonName", "GroupType" });
|
|
AddSource(sources, tableSet, columnMap, "work_group_auth", "TD_WorkGroupAuth", new string[] { "GroupJID", "GroupName", "ChildGroupJid" });
|
|
AddSource(sources, tableSet, columnMap, "received_file_metadata", "TD_ReceiveFileRecord", new string[] { "FileName", "SendPersonJid", "SendPersonName" });
|
|
AddSource(sources, tableSet, columnMap, "sent_file_metadata", "TD_SendFileRecord", new string[] { "FileName", "ReceivePersonJid", "ReceivePersonName" });
|
|
return sources;
|
|
}
|
|
|
|
private sealed class MessageSourceSpec
|
|
{
|
|
public string Source;
|
|
public string Table;
|
|
public string Role;
|
|
public string[] RequiredColumns;
|
|
}
|
|
|
|
private static HashSet<string> BuildMessageSourceTableSet()
|
|
{
|
|
var tables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (MessageSourceSpec spec in BuildMessageSourceSpecs())
|
|
{
|
|
tables.Add(spec.Table);
|
|
}
|
|
return tables;
|
|
}
|
|
|
|
private static List<MessageSourceSpec> BuildMessageSourceSpecs()
|
|
{
|
|
return new List<MessageSourceSpec>
|
|
{
|
|
new MessageSourceSpec
|
|
{
|
|
Source = "direct_messages",
|
|
Table = "tblPersonMsg",
|
|
Role = "direct",
|
|
RequiredColumns = new string[] { "Id", "SndPersonId", "RecvPersonId", "ObjPersonId", "ActionTime", "MsgText", "MessageBody" }
|
|
},
|
|
new MessageSourceSpec
|
|
{
|
|
Source = "group_messages",
|
|
Table = "tblMsgGroupPersonMsg",
|
|
Role = "group",
|
|
RequiredColumns = new string[] { "Id", "SndPersonId", "MsgGroupId", "MsgGroupName", "MsgTime", "MsgText", "MessageBody" }
|
|
},
|
|
new MessageSourceSpec
|
|
{
|
|
Source = "system_messages",
|
|
Table = "TD_SystemMessageRecord",
|
|
Role = "system",
|
|
RequiredColumns = new string[] { "MsgGuid", "MsgType", "MsgTitle", "MsgText", "SendPersonJid", "MsgTime", "MsgReadState" }
|
|
},
|
|
new MessageSourceSpec
|
|
{
|
|
Source = "group_receipts",
|
|
Table = "TD_GroupReceiptMessage",
|
|
Role = "receipt_metadata",
|
|
RequiredColumns = new string[] { "MsgGuid", "PersonID", "ReceiptTime" }
|
|
},
|
|
new MessageSourceSpec
|
|
{
|
|
Source = "received_file_metadata",
|
|
Table = "TD_ReceiveFileRecord",
|
|
Role = "attachment_metadata",
|
|
RequiredColumns = new string[] { "ReceiveMsgGuid", "FileName", "FileSize", "SourceID", "SourceName", "FileType", "SendPersonJid", "SendPersonName", "SendTime" }
|
|
},
|
|
new MessageSourceSpec
|
|
{
|
|
Source = "recent_conversation_index",
|
|
Table = "tblRecent",
|
|
Role = "conversation_index",
|
|
RequiredColumns = new string[] { "Id", "PersonId", "PersonName", "ActionTime", "FType" }
|
|
}
|
|
};
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> BuildMessageSources(List<Dictionary<string, object>> tables, Dictionary<string, HashSet<string>> columnMap, Dictionary<string, string> rowCounts)
|
|
{
|
|
var tableSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (Dictionary<string, object> table in tables)
|
|
{
|
|
if (string.Equals(Convert.ToString(table["type"]), "table", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
tableSet.Add(Convert.ToString(table["name"]));
|
|
}
|
|
}
|
|
|
|
var sources = new List<Dictionary<string, object>>();
|
|
foreach (MessageSourceSpec spec in BuildMessageSourceSpecs())
|
|
{
|
|
AddMessageSource(sources, tableSet, columnMap, rowCounts, spec);
|
|
}
|
|
return sources;
|
|
}
|
|
|
|
private static void AddMessageSource(List<Dictionary<string, object>> sources, HashSet<string> tableSet, Dictionary<string, HashSet<string>> columnMap, Dictionary<string, string> rowCounts, MessageSourceSpec spec)
|
|
{
|
|
var presentColumns = new List<string>();
|
|
if (columnMap.ContainsKey(spec.Table))
|
|
{
|
|
foreach (string column in spec.RequiredColumns)
|
|
{
|
|
if (columnMap[spec.Table].Contains(column)) presentColumns.Add(column);
|
|
}
|
|
}
|
|
string rowCount = rowCounts.ContainsKey(spec.Table) ? rowCounts[spec.Table] : "0";
|
|
sources.Add(new Dictionary<string, object>
|
|
{
|
|
{ "source", spec.Source },
|
|
{ "table", spec.Table },
|
|
{ "role", spec.Role },
|
|
{ "available", tableSet.Contains(spec.Table) && presentColumns.Count == spec.RequiredColumns.Length },
|
|
{ "required_columns", spec.RequiredColumns },
|
|
{ "present_columns", presentColumns.ToArray() },
|
|
{ "row_count", rowCount }
|
|
});
|
|
}
|
|
|
|
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
|
|
{
|
|
public string EntityType;
|
|
public string SourceTable;
|
|
public string JidColumn;
|
|
public string NameColumn;
|
|
public double Confidence;
|
|
}
|
|
|
|
private static HashSet<string> BuildDisplayEntityTableSet(string entityType)
|
|
{
|
|
var tables = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (DisplayEntitySpec spec in BuildDisplayEntitySpecs(entityType))
|
|
{
|
|
tables.Add(spec.SourceTable);
|
|
}
|
|
return tables;
|
|
}
|
|
|
|
private static Dictionary<string, HashSet<string>> BuildColumnMap(List<Dictionary<string, object>> columns)
|
|
{
|
|
var columnMap = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (Dictionary<string, object> column in columns)
|
|
{
|
|
string table = Convert.ToString(column["table"]);
|
|
string name = Convert.ToString(column["name"]);
|
|
if (!columnMap.ContainsKey(table)) columnMap[table] = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
columnMap[table].Add(name);
|
|
}
|
|
return columnMap;
|
|
}
|
|
|
|
private static List<DisplayEntitySpec> BuildDisplayEntitySpecs(string entityType)
|
|
{
|
|
var specs = new List<DisplayEntitySpec>();
|
|
if (string.Equals(entityType, "contacts", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
AddEntitySpec(specs, entityType, "TD_Roster", "JId", "Nick", 0.95);
|
|
AddEntitySpec(specs, entityType, "TD_CustomEffigy", "PersonJid", "PersonName", 0.90);
|
|
AddEntitySpec(specs, entityType, "tblRecent", "PersonId", "PersonName", 0.70);
|
|
AddEntitySpec(specs, entityType, "tblChatLevel", "PersonId", "PersonName", 0.70);
|
|
AddEntitySpec(specs, entityType, "tblPersonMsg", "SndPersonId", "SndPerson", 0.55);
|
|
AddEntitySpec(specs, entityType, "tblPersonMsg", "RecvPersonId", "RecvPerson", 0.55);
|
|
AddEntitySpec(specs, entityType, "tblPersonMsg", "ObjPersonId", "ObjPerson", 0.55);
|
|
}
|
|
else if (string.Equals(entityType, "groups", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
AddEntitySpec(specs, entityType, "TD_WorkGroupAuth", "GroupJID", "GroupName", 0.90);
|
|
AddEntitySpec(specs, entityType, "TD_WorkGroupAuth", "ChildGroupJid", "GroupName", 0.80);
|
|
AddEntitySpec(specs, entityType, "tblMsgGroupPersonMsg", "MsgGroupId", "MsgGroupName", 0.70);
|
|
AddEntitySpec(specs, entityType, "tblMsgGroupPersonMsg", "SessionPersonId", "SessionPersonName", 0.60);
|
|
}
|
|
return specs;
|
|
}
|
|
|
|
private static void AddEntitySpec(List<DisplayEntitySpec> specs, string entityType, string table, string jidColumn, string nameColumn, double confidence)
|
|
{
|
|
specs.Add(new DisplayEntitySpec
|
|
{
|
|
EntityType = entityType,
|
|
SourceTable = table,
|
|
JidColumn = jidColumn,
|
|
NameColumn = nameColumn,
|
|
Confidence = confidence
|
|
});
|
|
}
|
|
|
|
private static List<Dictionary<string, object>> ReadDisplayEntities(DbConnection connection, string entityType, string query, int limit, Dictionary<string, HashSet<string>> columnMap)
|
|
{
|
|
var rows = new List<Dictionary<string, object>>();
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (DisplayEntitySpec spec in BuildDisplayEntitySpecs(entityType))
|
|
{
|
|
if (rows.Count >= limit) break;
|
|
if (!columnMap.ContainsKey(spec.SourceTable)) continue;
|
|
if (!columnMap[spec.SourceTable].Contains(spec.JidColumn)) continue;
|
|
if (!columnMap[spec.SourceTable].Contains(spec.NameColumn)) continue;
|
|
ReadDisplayEntitiesForSpec(connection, spec, query, limit - rows.Count, rows, seen);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
private static void ReadDisplayEntitiesForSpec(DbConnection connection, DisplayEntitySpec spec, string query, int limit, List<Dictionary<string, object>> rows, HashSet<string> seen)
|
|
{
|
|
if (limit <= 0) return;
|
|
using (DbCommand command = connection.CreateCommand())
|
|
{
|
|
string jid = QuoteIdent(spec.JidColumn);
|
|
string name = QuoteIdent(spec.NameColumn);
|
|
command.CommandText =
|
|
"select " + jid + " as jid, " + name + " as display_name from " + QuoteIdent(spec.SourceTable) +
|
|
" where (coalesce(" + jid + ", '') <> '' or coalesce(" + name + ", '') <> '')";
|
|
if (!string.IsNullOrWhiteSpace(query))
|
|
{
|
|
command.CommandText += " and (" + jid + " like @query or " + name + " like @query)";
|
|
DbParameter queryParam = command.CreateParameter();
|
|
queryParam.ParameterName = "@query";
|
|
queryParam.Value = "%" + query + "%";
|
|
command.Parameters.Add(queryParam);
|
|
}
|
|
command.CommandText += " limit " + Convert.ToString(limit);
|
|
|
|
using (DbDataReader reader = command.ExecuteReader())
|
|
{
|
|
while (reader.Read() && rows.Count < limit)
|
|
{
|
|
string entityJid = Convert.ToString(reader["jid"]);
|
|
string displayName = Convert.ToString(reader["display_name"]);
|
|
if (string.IsNullOrWhiteSpace(entityJid) && string.IsNullOrWhiteSpace(displayName)) continue;
|
|
string key = spec.EntityType + "|" + entityJid + "|" + displayName + "|" + spec.SourceTable;
|
|
if (seen.Contains(key)) continue;
|
|
seen.Add(key);
|
|
rows.Add(new Dictionary<string, object>
|
|
{
|
|
{ "entity_type", spec.EntityType },
|
|
{ "source_table", spec.SourceTable },
|
|
{ "jid", entityJid },
|
|
{ "display_name", displayName },
|
|
{ "confidence", spec.Confidence },
|
|
{ "matched_columns", new string[] { spec.JidColumn, spec.NameColumn } }
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddSource(List<Dictionary<string, object>> sources, HashSet<string> tableSet, Dictionary<string, HashSet<string>> columnMap, string sourceName, string table, string[] columns)
|
|
{
|
|
var presentColumns = new List<string>();
|
|
if (columnMap.ContainsKey(table))
|
|
{
|
|
foreach (string column in columns)
|
|
{
|
|
if (columnMap[table].Contains(column)) presentColumns.Add(column);
|
|
}
|
|
}
|
|
sources.Add(new Dictionary<string, object>
|
|
{
|
|
{ "source", sourceName },
|
|
{ "table", table },
|
|
{ "available", tableSet.Contains(table) && presentColumns.Count > 0 },
|
|
{ "required_columns", columns },
|
|
{ "present_columns", presentColumns.ToArray() }
|
|
});
|
|
}
|
|
|
|
private static string ExecuteScalar(DbConnection connection, string sql)
|
|
{
|
|
using (DbCommand command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = sql;
|
|
object value = command.ExecuteScalar();
|
|
return Convert.ToString(value);
|
|
}
|
|
}
|
|
|
|
private static void TryExecuteNonQuery(DbConnection connection, string sql)
|
|
{
|
|
try
|
|
{
|
|
using (DbCommand command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = sql;
|
|
command.ExecuteNonQuery();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
private static string QuoteIdent(string value)
|
|
{
|
|
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
|
}
|
|
|
|
private static int Clamp(int value, int min, int max)
|
|
{
|
|
if (value < min) return min;
|
|
if (value > max) return max;
|
|
return value;
|
|
}
|
|
}
|
|
}
|