From 9971b141774c05aff8f4b320e26131d0e47a7068 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Sat, 27 Dec 2025 19:52:28 +0800 Subject: [PATCH] Share Location --- .gitignore | 3 +- LightlessAPI | 2 +- .../Hubs/LightlessHub.ClientStubs.cs | 1 + .../Hubs/LightlessHub.Functions.cs | 5 ++ .../Hubs/LightlessHub.Groups.cs | 11 ++- .../Hubs/LightlessHub.Permissions.cs | 4 + .../Hubs/LightlessHub.User.cs | 82 ++++++++++++++++++- .../LightlessSyncServer/Hubs/LightlessHub.cs | 5 ++ .../Services/PairService.cs | 2 + .../LightlessSyncServer/Utils/Extensions.cs | 2 + .../Models/GroupPairPreferredPermission.cs | 1 + .../Models/UserPermissionSet.cs | 1 + 12 files changed, 114 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index d9cf9a4..be64548 100644 --- a/.gitignore +++ b/.gitignore @@ -350,4 +350,5 @@ MigrationBackup/ .ionide/ # docker run data -Docker/run/data/ \ No newline at end of file +Docker/run/data/ +*.idea diff --git a/LightlessAPI b/LightlessAPI index 5656600..9feb0b3 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 56566003e0e93bba05dcef49fd3ce23c6a204d81 +Subproject commit 9feb0b3c35f67d153607304f93aa3b855d02f445 diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.ClientStubs.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.ClientStubs.cs index 4e5afcb..a880268 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.ClientStubs.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.ClientStubs.cs @@ -40,5 +40,6 @@ namespace LightlessSyncServer.Hubs public Task Client_GposeLobbyPushPoseData(UserData userData, PoseData poseData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); public Task Client_GposeLobbyPushWorldData(UserData userData, WorldData worldData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); public Task Client_ChatReceive(ChatMessageDto message) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); + public Task Client_SendLocationToClient(LocationDto locationDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); } } \ No newline at end of file diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Functions.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Functions.cs index a329f8e..b2c71fa 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Functions.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Functions.cs @@ -585,6 +585,11 @@ public partial class LightlessHub return await result.Distinct().AsNoTracking().ToListAsync().ConfigureAwait(false); } + + private async Task CleanVisibilityCacheFromRedis() + { + await _redis.RemoveAsync($"Visibility:{UserUID}", CommandFlags.FireAndForget).ConfigureAwait(false); + } public record UserInfo( string Alias, diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs index e5d63df..2fa52f2 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs @@ -504,6 +504,7 @@ public partial class LightlessHub preferredPermissions.DisableVFX = dto.GroupUserPreferredPermissions.IsDisableVFX(); preferredPermissions.DisableAnimations = dto.GroupUserPreferredPermissions.IsDisableAnimations(); preferredPermissions.IsPaused = false; + preferredPermissions.ShareLocation = dto.GroupUserPreferredPermissions.IsSharingLocation(); DbContext.Update(preferredPermissions); } @@ -549,7 +550,8 @@ public partial class LightlessHub DisableSounds = preferredPermissions.DisableSounds, DisableVFX = preferredPermissions.DisableVFX, IsPaused = preferredPermissions.IsPaused, - Sticky = false + Sticky = false, + ShareLocation = preferredPermissions.ShareLocation, }; await DbContext.Permissions.AddAsync(ownPermissionsToOther).ConfigureAwait(false); @@ -561,6 +563,7 @@ public partial class LightlessHub existingPermissionsOnDb.DisableVFX = preferredPermissions.DisableVFX; existingPermissionsOnDb.IsPaused = false; existingPermissionsOnDb.Sticky = false; + existingPermissionsOnDb.ShareLocation = preferredPermissions.ShareLocation; DbContext.Update(existingPermissionsOnDb); @@ -576,6 +579,7 @@ public partial class LightlessHub ownPermissionsToOther.DisableVFX = preferredPermissions.DisableVFX; ownPermissionsToOther.DisableSounds = preferredPermissions.DisableSounds; ownPermissionsToOther.IsPaused = false; + ownPermissionsToOther.ShareLocation = preferredPermissions.ShareLocation; DbContext.Update(ownPermissionsToOther); } @@ -597,7 +601,8 @@ public partial class LightlessHub DisableSounds = otherPreferred.DisableSounds, DisableVFX = otherPreferred.DisableVFX, IsPaused = otherPreferred.IsPaused, - Sticky = false + Sticky = false, + ShareLocation = otherPreferred.ShareLocation, }; await DbContext.AddAsync(otherExistingPermsOnDb).ConfigureAwait(false); @@ -609,6 +614,7 @@ public partial class LightlessHub otherExistingPermsOnDb.DisableSounds = otherPreferred.DisableSounds; otherExistingPermsOnDb.DisableVFX = otherPreferred.DisableVFX; otherExistingPermsOnDb.IsPaused = otherPreferred.IsPaused; + otherExistingPermsOnDb.ShareLocation = otherPreferred.ShareLocation; DbContext.Update(otherExistingPermsOnDb); } @@ -622,6 +628,7 @@ public partial class LightlessHub otherPermissionToSelf.DisableSounds = otherPreferred.DisableSounds; otherPermissionToSelf.DisableVFX = otherPreferred.DisableVFX; otherPermissionToSelf.IsPaused = otherPreferred.IsPaused; + otherPermissionToSelf.ShareLocation = otherPreferred.ShareLocation; DbContext.Update(otherPermissionToSelf); } diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Permissions.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Permissions.cs index f4b7687..299d0bd 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Permissions.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Permissions.cs @@ -66,6 +66,7 @@ public partial class LightlessHub prevPermissions.DisableSounds = newPerm.IsDisableSounds(); prevPermissions.DisableVFX = newPerm.IsDisableVFX(); prevPermissions.Sticky = newPerm.IsSticky() || setSticky; + prevPermissions.ShareLocation = newPerm.IsSharingLocation(); DbContext.Update(prevPermissions); // send updated data to pair @@ -112,6 +113,7 @@ public partial class LightlessHub groupPreferredPermissions.DisableAnimations = group.Value.IsDisableAnimations(); groupPreferredPermissions.IsPaused = group.Value.IsPaused(); groupPreferredPermissions.DisableVFX = group.Value.IsDisableVFX(); + groupPreferredPermissions.ShareLocation = group.Value.IsSharingLocation(); var nonStickyPairs = allUsers.Where(u => !u.Value.OwnPermissions.Sticky).ToList(); var affectedGroupPairs = nonStickyPairs.Where(u => u.Value.GIDs.Contains(group.Key, StringComparer.Ordinal)).ToList(); @@ -126,6 +128,7 @@ public partial class LightlessHub perm.DisableAnimations = groupPreferredPermissions.DisableAnimations; perm.IsPaused = groupPreferredPermissions.IsPaused; perm.DisableVFX = groupPreferredPermissions.DisableVFX; + perm.ShareLocation = groupPreferredPermissions.ShareLocation; } UserPermissions permissions = UserPermissions.NoneSet; @@ -133,6 +136,7 @@ public partial class LightlessHub permissions.SetDisableAnimations(groupPreferredPermissions.DisableAnimations); permissions.SetDisableSounds(groupPreferredPermissions.DisableSounds); permissions.SetDisableVFX(groupPreferredPermissions.DisableVFX); + permissions.SetShareLocation(groupPreferredPermissions.ShareLocation); await Clients.Users(affectedGroupPairs .Select(k => k.Key)) diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs index e278124..595555b 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs @@ -86,7 +86,8 @@ public partial class LightlessHub DisableSounds = ownDefaultPermissions.DisableIndividualSounds, DisableVFX = ownDefaultPermissions.DisableIndividualVFX, IsPaused = false, - Sticky = true + Sticky = true, + ShareLocation = false, }; var existingDbPerms = await DbContext.Permissions.SingleOrDefaultAsync(u => u.UserUID == UserUID && u.OtherUserUID == otherUser.UID, cancellationToken: RequestAbortedToken).ConfigureAwait(false); @@ -101,6 +102,7 @@ public partial class LightlessHub existingDbPerms.DisableVFX = permissions.DisableVFX; existingDbPerms.IsPaused = false; existingDbPerms.Sticky = true; + existingDbPerms.ShareLocation = permissions.ShareLocation; DbContext.Permissions.Update(existingDbPerms); } @@ -1232,6 +1234,84 @@ public partial class LightlessHub errorMessage = string.Empty; return true; } + + + [Authorize(Policy = "Identified")] + public async Task UpdateLocation(LocationDto dto, bool offline = false) + { + _logger.LogCallInfo(LightlessHubLogger.Args(UserUID,dto)); + if (string.IsNullOrEmpty(dto.user.UID)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("LocationDto with no userinfo :",UserUID, dto)); + return; + } + + if (!string.Equals(UserUID, dto.user.UID, StringComparison.Ordinal)) + { + _logger.LogCallWarning(LightlessHubLogger.Args("LocationDto with another UID :",UserUID, dto)); + return; + } + + var visibilityCacheKey = $"Visibility:{UserUID}"; + + var allUsers = await _redis.GetAsync>(visibilityCacheKey).ConfigureAwait(false); + if (allUsers == null) + { + var permissibleGroupGIDsQuery = DbContext.GroupPairPreferredPermissions.AsNoTracking() + .Where(gpp => gpp.UserUID == dto.user.UID && !gpp.IsPaused && gpp.ShareLocation == true) + .Select(gpp => gpp.GroupGID); + + var groupUserUIDsQuery = DbContext.GroupPairs.AsNoTracking() + .Where(gp => permissibleGroupGIDsQuery.Contains(gp.GroupGID)) + .Select(gp => gp.GroupUserUID); + + var directlySharedUserUIDsQuery = DbContext.Permissions.AsNoTracking() + .Where(p => p.UserUID == dto.user.UID && !p.IsPaused && p.ShareLocation == true) + .Select(p => p.OtherUserUID); + + allUsers = await directlySharedUserUIDsQuery + .Union(groupUserUIDsQuery) + .Distinct() + .ToListAsync().ConfigureAwait(false); + + await _redis.AddAsync( + visibilityCacheKey, + allUsers, + TimeSpan.FromMinutes(30), + StackExchange.Redis.When.Always, + StackExchange.Redis.CommandFlags.FireAndForget + ).ConfigureAwait(false); + } + + var key = $"Location:{UserUID}"; + + if (offline) + { + await _redis.RemoveAsync(key, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false); + await Clients.Users(allUsers).Client_SendLocationToClient(dto).ConfigureAwait(false); + } + else + { + var currentLocation = await _redis.GetAsync(key).ConfigureAwait(false); + + await _redis.AddAsync(key, dto).ConfigureAwait(false); + if (allUsers.Count != 0 && currentLocation != dto) + { + await Clients.Users(allUsers).Client_SendLocationToClient(dto).ConfigureAwait(false); + } + } + } + + [Authorize(Policy = "Identified")] + public async Task> RequestAllLocationInfo() + { + _logger.LogCallInfo(); + var uids = DbContext.Permissions.AsNoTracking().Where(x => x.OtherUserUID == UserUID && x.ShareLocation == true) + .Select(x => x.UserUID).ToList(); + var data =await _redis.GetAllAsync(uids.Select(x => $"Location:{x}").ToHashSet(StringComparer.Ordinal)) + .ConfigureAwait(false); + return data.Where(x => x.Value is not null).Select(x => x.Value).ToList(); + } [GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)] private static partial Regex GamePathRegex(); diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs index 99963d6..2781d2e 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs @@ -16,6 +16,8 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using StackExchange.Redis.Extensions.Core.Abstractions; using System.Collections.Concurrent; +using LightlessSync.API.Dto.CharaData; +using LightlessSync.API.Dto.User; using LightlessSyncServer.Services.Interfaces; namespace LightlessSyncServer.Hubs; @@ -219,6 +221,9 @@ public partial class LightlessHub : Hub, ILightlessHub _lightlessCensus.ClearStatistics(UserUID); await SendOfflineToAllPairedUsers().ConfigureAwait(false); + + await UpdateLocation(new LocationDto(new UserData(UserUID), new LocationInfo()), offline: true).ConfigureAwait(false); + await CleanVisibilityCacheFromRedis().ConfigureAwait(false); DbContext.RemoveRange(DbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == UserUID)); await DbContext.SaveChangesAsync().ConfigureAwait(false); diff --git a/LightlessSyncServer/LightlessSyncServer/Services/PairService.cs b/LightlessSyncServer/LightlessSyncServer/Services/PairService.cs index 854472f..27b678b 100644 --- a/LightlessSyncServer/LightlessSyncServer/Services/PairService.cs +++ b/LightlessSyncServer/LightlessSyncServer/Services/PairService.cs @@ -67,6 +67,7 @@ public class PairService DisableVFX = defaultPerms.DisableIndividualVFX, IsPaused = false, Sticky = true, + ShareLocation = false, }); modified = true; } @@ -88,6 +89,7 @@ public class PairService DisableVFX = defaultPerms.DisableIndividualVFX, IsPaused = false, Sticky = true, + ShareLocation = false, }); modified = true; } diff --git a/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs b/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs index 546cf0c..e9f931d 100644 --- a/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs +++ b/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs @@ -129,6 +129,7 @@ public static class Extensions permissions.SetDisableSounds(groupPair.DisableSounds); permissions.SetPaused(groupPair.IsPaused); permissions.SetDisableVFX(groupPair.DisableVFX); + permissions.SetShareLocation(groupPair.ShareLocation); return permissions; } @@ -149,6 +150,7 @@ public static class Extensions perm.SetDisableAnimations(permissions.DisableAnimations); perm.SetDisableSounds(permissions.DisableSounds); perm.SetDisableVFX(permissions.DisableVFX); + perm.SetShareLocation(permissions.ShareLocation); if (setSticky) perm.SetSticky(permissions.Sticky); return perm; diff --git a/LightlessSyncServer/LightlessSyncShared/Models/GroupPairPreferredPermission.cs b/LightlessSyncServer/LightlessSyncShared/Models/GroupPairPreferredPermission.cs index 6946810..6bebdc4 100644 --- a/LightlessSyncServer/LightlessSyncShared/Models/GroupPairPreferredPermission.cs +++ b/LightlessSyncServer/LightlessSyncShared/Models/GroupPairPreferredPermission.cs @@ -10,4 +10,5 @@ public class GroupPairPreferredPermission public bool DisableAnimations { get; set; } public bool DisableSounds { get; set; } public bool DisableVFX { get; set; } + public bool ShareLocation { get; set; } } diff --git a/LightlessSyncServer/LightlessSyncShared/Models/UserPermissionSet.cs b/LightlessSyncServer/LightlessSyncShared/Models/UserPermissionSet.cs index 5a00f3a..4e1f78f 100644 --- a/LightlessSyncServer/LightlessSyncShared/Models/UserPermissionSet.cs +++ b/LightlessSyncServer/LightlessSyncShared/Models/UserPermissionSet.cs @@ -15,4 +15,5 @@ public class UserPermissionSet public bool DisableAnimations { get; set; } = false; public bool DisableVFX { get; set; } = false; public bool DisableSounds { get; set; } = false; + public bool ShareLocation { get; set; } = false; }