diff --git a/BuildNumber.txt b/BuildNumber.txt index 1447642..1cb1a4d 100644 --- a/BuildNumber.txt +++ b/BuildNumber.txt @@ -1 +1 @@ -1976 +1988 diff --git a/src/Clients/Ollama/Ollama.cs b/src/Clients/Ollama/Ollama.cs index d2902d0..b6e58d4 100644 --- a/src/Clients/Ollama/Ollama.cs +++ b/src/Clients/Ollama/Ollama.cs @@ -20,6 +20,7 @@ internal class QueuedProcess : IHavePriority public int Priority { get; set; } public string? UserId { get; set; } public string? UserBio { get; set; } + public bool IsFriend { get; set; } public string MD5Hash { @@ -116,6 +117,7 @@ public static async Task ProfileCheckTask(ConcurrentPriorityQueue userGroups = serviceRegistry.GetVRChatAPIClient().GetProfileGroups(item.UserId); string fullProfile = $"DisplayName: {profile.DisplayName}\nStatusDesc: {profile.StatusDescription}\nPronowns: {profile.Pronouns}\nProfileBio: {profile.Bio}\n"; + item.IsFriend = profile.IsFriend; item.UserBio = fullProfile; serviceRegistry.GetPlayerManager().UpdatePlayerUserFromVRCProfile(profile, item.MD5Hash); @@ -139,7 +141,7 @@ public static async Task ProfileCheckTask(ConcurrentPriorityQueue GetUserGroupInformation(ServiceRegistry serviceRegistry, TailgrabDBContext dBContext, List userGroups, QueuedProcess item) { + bool saveGroups = ConfigStore.GetStoredKeyBool(CommonConst.Registry_Discovered_Group_Caching, true); logger.Debug($"Processing User Group subscription for userId: {item.UserId}"); Player? player = serviceRegistry.GetPlayerManager().GetPlayerByUserId(item.UserId ?? string.Empty); if (player != null) @@ -175,19 +178,23 @@ private async static Task GetUserGroupInformation(ServiceRegistry serviceR GroupInfo? groupInfo = dBContext.GroupInfos.Find(group.GroupId); if (groupInfo == null) { - groupInfo = new GroupInfo + if( saveGroups ) { - GroupId = group.GroupId, - GroupName = group.Name, - AlertType = AlertTypeEnum.None, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow - }; - dBContext.GroupInfos.Add(groupInfo); - dBContext.SaveChanges(); + groupInfo = new GroupInfo + { + GroupId = group.GroupId, + GroupName = group.Name, + AlertType = AlertTypeEnum.None, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + dBContext.GroupInfos.Add(groupInfo); + dBContext.SaveChanges(); + } } else { + // We will update the group name on each lookup in case it changes, but not reset the alert level as that is user defined groupInfo.GroupName = group.Name; dBContext.GroupInfos.Update(groupInfo); dBContext.SaveChanges(); @@ -199,6 +206,7 @@ private async static Task GetUserGroupInformation(ServiceRegistry serviceR maxAlertType = maxAlertType < groupInfo.AlertType ? groupInfo.AlertType : maxAlertType; } } + } if (player != null && player.IsWatched) @@ -242,6 +250,7 @@ await ollamaApi.GenerateAsync(request).StreamToEndAsync(responseTask => { player.UserBio = item.UserBio; player.AIEval = response; + player.IsFriend = item.IsFriend; ProfileViewUpdate(serviceRegistry, player); } @@ -256,23 +265,25 @@ await ollamaApi.GenerateAsync(request).StreamToEndAsync(responseTask => await Task.Delay(5000); } - private static void GetEvaluationFromStore(ServiceRegistry serviceRegistry, ProfileEvaluation evaluated, string? userId) + private static void GetEvaluationFromStore(ServiceRegistry serviceRegistry, ProfileEvaluation evaluated, QueuedProcess item) { - if (userId != null) + if (item != null && item.UserId != null) { - Player? player = serviceRegistry.GetPlayerManager().GetPlayerByUserId(userId ?? string.Empty); + Player? player = serviceRegistry.GetPlayerManager().GetPlayerByUserId(item.UserId); if (player != null) { player.AIEval = System.Text.Encoding.UTF8.GetString(evaluated.Evaluation); player.UserBio = System.Text.Encoding.UTF8.GetString(evaluated.ProfileText); + player.IsFriend = item.IsFriend; + ProfileViewUpdate(serviceRegistry, player); - logger.Debug($"User profile already processed for userId: {userId}"); + logger.Debug($"User profile already processed for userId: {item.UserId}"); } else { - logger.Debug($"User profile lookup fails for userId: {userId}"); + logger.Debug($"User profile lookup fails for userId: {item.UserId}"); } } diff --git a/src/Common/CommonConst.cs b/src/Common/CommonConst.cs index fdb5325..deea7c5 100644 --- a/src/Common/CommonConst.cs +++ b/src/Common/CommonConst.cs @@ -46,5 +46,8 @@ public static class CommonConst public const string Sound_Alert_Key = "Sound"; public const string Color_Alert_Key = "Color"; + public const string Registry_Discovered_Avatar_Caching = "DISCOVERED_AVATAR_CACHING"; + public const string Registry_Moderated_Avatar_Caching = "MODERATED_AVATAR_CACHING"; + public const string Registry_Discovered_Group_Caching = "DISCOVERED_GROUP_CACHING"; } } diff --git a/src/Common/ConfigStore.cs b/src/Common/ConfigStore.cs index 3ca1154..821ab87 100644 --- a/src/Common/ConfigStore.cs +++ b/src/Common/ConfigStore.cs @@ -59,6 +59,10 @@ public static void DeleteSecret(string name) { return GetStoredKeyString(CommonConst.ConfigRegistryPath, keyName); } + public static bool GetStoredKeyBool(string keyName, bool defaultValue) + { + return GetStoredKeyBool(CommonConst.ConfigRegistryPath, keyName, defaultValue); + } public static string? GetStoredKeyString(string keyPath, string keyName) { @@ -88,12 +92,46 @@ public static void DeleteSecret(string name) return null; } } + public static bool GetStoredKeyBool(string keyPath, string keyName, bool defaultValue ) + { + try + { + using (RegistryKey? key = Registry.CurrentUser.OpenSubKey(keyPath)) + { + if (key == null) + { + logger.Debug($"Registry key does not exist {keyName}"); + return defaultValue; + } + + string? value = key.GetValue(keyName) as string; + if (string.IsNullOrEmpty(value)) + { + logger.Debug($"No Value stored in registry. {keyName}"); + return defaultValue; + } + + bool.TryParse(value, out bool result); + return result; + } + } + catch (Exception ex) + { + logger.Error(ex, "Failed to read value from registry."); + return defaultValue; + } + } public static void PutStoredKeyString(string keyName, string keyValue) { PutStoredKeyString(CommonConst.ConfigRegistryPath, keyName, keyValue); } + public static void PutStoredKeyBool(string keyName, bool keyValue) + { + PutStoredKeyBool(CommonConst.ConfigRegistryPath, keyName, keyValue); + } + public static void PutStoredKeyString(string keyPath, string keyName, string keyValue) { try @@ -108,6 +146,21 @@ public static void PutStoredKeyString(string keyPath, string keyName, string key logger.Error(ex, $"Failed to save value to registry. {keyName}"); } } + public static void PutStoredKeyBool(string keyPath, string keyName, bool keyValue) + { + try + { + using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath)) + { + key.SetValue(keyName, keyValue, RegistryValueKind.String); + } + } + catch (Exception ex) + { + logger.Error(ex, $"Failed to save value to registry. {keyName}"); + } + } + public static void RemoveStoredKeyString(string keyPath, string keyName) { diff --git a/src/Models/GroupInfo.cs b/src/Models/GroupInfo.cs index bf4086c..42d191c 100644 --- a/src/Models/GroupInfo.cs +++ b/src/Models/GroupInfo.cs @@ -1,6 +1,7 @@ // This file has been auto generated by EF Core Power Tools. #nullable disable using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using Tailgrab.Common; diff --git a/src/Models/TailgrabDBContext.cs b/src/Models/TailgrabDBContext.cs index 31ffe23..5a3f56b 100644 --- a/src/Models/TailgrabDBContext.cs +++ b/src/Models/TailgrabDBContext.cs @@ -71,6 +71,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasKey(e => e.AvatarId); entity.ToTable("AvatarInfo"); + + entity.HasIndex(a => a.AvatarName); + entity.HasIndex(a => a.UserName); + entity.HasIndex(a => a.AlertType); }); modelBuilder.Entity(entity => @@ -81,6 +85,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.CreatedAt).HasColumnName("createDate"); entity.Property(e => e.UpdatedAt).HasColumnName("updateDate"); + + entity.HasIndex(g => g.GroupName); + entity.HasIndex(g => g.AlertType); }); modelBuilder.Entity(entity => @@ -119,11 +126,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public void UpgradeDatabase() { + // CREATE INDEX ix_avtr_aname ON AvatarInfo(AvatarName); + // CREATE INDEX ix_avtr_uname ON AvatarInfo(UserName); + // CREATE INDEX ix_avtr_alert ON AvatarInfo(alertType); + // CREATE INDEX ix_grp_gname ON GroupInfo(groupName); + // CREATE INDEX ix_grp_alert ON GroupInfo(alertType); + ExecuteSqlTransaction( - "ALTER TABLE AvatarInfo ADD COLUMN alertType INTEGER NOT NULL DEFAULT 0", - "UPDATE AvatarInfo SET alertType = 1 WHERE IsBOS = 1", - "ALTER TABLE GroupInfo ADD COLUMN alertType INTEGER NOT NULL DEFAULT 0", - "UPDATE GroupInfo SET alertType = 1 WHERE IsBOS = 1" + "CREATE INDEX IF NOT EXISTS ix_avtr_aname ON AvatarInfo(AvatarName)", + "CREATE INDEX IF NOT EXISTS ix_avtr_uname ON AvatarInfo(UserName)", + "CREATE INDEX IF NOT EXISTS ix_avtr_alert ON AvatarInfo(alertType)", + "CREATE INDEX IF NOT EXISTS ix_grp_gname ON GroupInfo(groupName)", + "CREATE INDEX IF NOT EXISTS ix_grp_alert ON GroupInfo(alertType)" ); } @@ -135,18 +149,18 @@ private void ExecuteSql(string sql) private void ExecuteSqlTransaction(params string[] sqlStatements) { using var transaction = Database.BeginTransaction(); - try + foreach (var sql in sqlStatements) { - foreach (var sql in sqlStatements) + try { Database.ExecuteSqlRaw(sql); } - transaction.Commit(); - } - catch - { - transaction.Rollback(); - throw; + catch + { + logger.Error($"Error executing SQL transaction. Rolling back changes. {sql}"); + transaction.Rollback(); + throw; + } } } diff --git a/src/PlayerManagement/AvatarCollection.cs b/src/PlayerManagement/AvatarCollection.cs index 60dcdac..93b30c8 100644 --- a/src/PlayerManagement/AvatarCollection.cs +++ b/src/PlayerManagement/AvatarCollection.cs @@ -82,8 +82,14 @@ private void EnsureCount() if (!string.IsNullOrWhiteSpace(_filterText)) { - var filterLower = _filterText.ToLower(); - query = query.Where(a => a.AvatarName.ToLower().Contains(filterLower)); + if (_filterText.StartsWith("avtr_", StringComparison.OrdinalIgnoreCase)) + { + query = query.Where(a => a.AvatarId == _filterText); + } + else + { + query = query.Where(a => EF.Functions.Like(a.AvatarName, $"%{_filterText}%")); + } } var items = query.OrderBy(a => a.AvatarName).Skip(skip).Take(_pageSize).ToList(); diff --git a/src/PlayerManagement/PlayerManagement.cs b/src/PlayerManagement/PlayerManagement.cs index 9cc7b1f..563fc53 100644 --- a/src/PlayerManagement/PlayerManagement.cs +++ b/src/PlayerManagement/PlayerManagement.cs @@ -165,7 +165,6 @@ public string AlertMessage public AlertTypeEnum MaxAlertType { get; private set; } = AlertTypeEnum.None; - private DateOnly? _dateJoined; public DateOnly? DateJoined { @@ -241,6 +240,22 @@ public bool IsWatched } } + + private bool _isFriend = false; + public bool IsFriend { + get + { + return _isFriend; + } + set + { + if( value == true) + { + AlertColor = "Friend"; + } + _isFriend = value; + } } + public Player(string userId, string displayName, SessionInfo session) { UserId = userId; @@ -263,7 +278,10 @@ public void AddAlertMessage(AlertClassEnum alertClass, AlertTypeEnum alertType, if (alert.AlertType > MaxAlertType) { MaxAlertType = alert.AlertType; - AlertColor = alert.Color; + if( _isFriend == false) + { + AlertColor = alert.Color; + } } } } diff --git a/src/PlayerManagement/TailgrabPanel.xaml b/src/PlayerManagement/TailgrabPanel.xaml index ef018e4..3dc95d9 100644 --- a/src/PlayerManagement/TailgrabPanel.xaml +++ b/src/PlayerManagement/TailgrabPanel.xaml @@ -48,6 +48,10 @@ Title="Tailgrab Player Panel" + + + + @@ -306,6 +310,10 @@ Title="Tailgrab Player Panel" + + + + @@ -1046,7 +1054,7 @@ Title="Tailgrab Player Panel" - + @@ -1219,7 +1227,31 @@ Title="Tailgrab Player Panel" - + + + + + + + + + + + + + + + + +