diff --git a/.gitignore b/.gitignore index d9cf9a4..23fcee6 100644 --- a/.gitignore +++ b/.gitignore @@ -350,4 +350,7 @@ MigrationBackup/ .ionide/ # docker run data -Docker/run/data/ \ No newline at end of file +Docker/run/data/ + +#idea files +*.idea \ No newline at end of file diff --git a/LightlessAPI b/LightlessAPI index 5656600..f1817c5 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 56566003e0e93bba05dcef49fd3ce23c6a204d81 +Subproject commit f1817c597450bf2ee5425708b982a68916214e3a diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.ClientStubs.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.ClientStubs.cs index 4e5afcb..39e8040 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, DateTimeOffset expireAt) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); } } \ No newline at end of file diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs index e278124..29c3076 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.User.cs @@ -12,8 +12,6 @@ using LightlessSyncShared.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; using StackExchange.Redis; using System.Text; using System.Text.Json; @@ -1232,6 +1230,94 @@ 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 key = $"Location:{UserUID}"; + + if (offline) + { + var allUsers = await GetSharingUsers().ConfigureAwait(false); + await _redis.RemoveAsync(key, CommandFlags.FireAndForget).ConfigureAwait(false); + await Clients.Users(allUsers.Keys).Client_SendLocationToClient(dto, DateTimeOffset.MinValue).ConfigureAwait(false); + } + else + { + var currentLocation = await _redis.GetAsync(key).ConfigureAwait(false); + if (currentLocation != dto) + { + var allUsers = await GetSharingUsers().ConfigureAwait(false); + await _redis.AddAsync(key, dto).ConfigureAwait(false); + var sendTasks = allUsers.Select(pair => Clients.User(pair.Key).Client_SendLocationToClient(dto, pair.Value)); + await Task.WhenAll(sendTasks).ConfigureAwait(false); + } + } + } + + private async Task> GetSharingUsers() + { + return await DbContext.Permissions.AsNoTracking() + .Where(x => x.UserUID == UserUID && x.ShareLocationUntil > DateTimeOffset.UtcNow) + .ToDictionaryAsync(x => x.OtherUserUID, x => x.ShareLocationUntil, RequestAbortedToken) + .ConfigureAwait(false); + } + + [Authorize(Policy = "Identified")] + public async Task> RequestAllLocationInfo() + { + _logger.LogCallInfo(); + var dictionary = await DbContext.Permissions.AsNoTracking() + .Where(x => x.OtherUserUID == UserUID && x.ShareLocationUntil > DateTimeOffset.UtcNow) + .ToDictionaryAsync(x => x.UserUID, x => x.ShareLocationUntil, cancellationToken: RequestAbortedToken) + .ConfigureAwait(false); + if (dictionary.Count == 0) + { + return []; + } + + var redisKeys = dictionary.Keys.Select(uid => $"Location:{uid}").ToHashSet(StringComparer.Ordinal); + var data = await _redis.GetAllAsync(redisKeys).ConfigureAwait(false); + var result = new List(); + + foreach (var (userUid, expireAt) in dictionary) + { + var redisKey = $"Location:{userUid}"; + + if (data.TryGetValue(redisKey, out var locationDto) && locationDto is not null) + { + result.Add(new LocationWithTimeDto(locationDto, expireAt)); + } + } + return result; + } + + + + [Authorize(Policy = "Identified")] + public async Task ToggleLocationSharing(LocationSharingToggleDto dto) + { + _logger.LogCallInfo(LightlessHubLogger.Args(UserUID,dto)); + await DbContext.Permissions.Where(x => x.UserUID == UserUID && dto.users.Contains(x.OtherUserUID)) + .ExecuteUpdateAsync(setter => + setter.SetProperty(x => x.ShareLocationUntil, dto.duration.ToUniversalTime()), cancellationToken: RequestAbortedToken).ConfigureAwait(false); + + //update user's location for target users + var currentLocation = await _redis.GetAsync($"Location:{UserUID}").ConfigureAwait(false); + await Clients.Users(dto.users).Client_SendLocationToClient(currentLocation, dto.duration.ToUniversalTime()).ConfigureAwait(false); + } [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..96c5c2b 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; @@ -217,6 +219,8 @@ public partial class LightlessHub : Hub, ILightlessHub await RemoveUserFromRedis().ConfigureAwait(false); _lightlessCensus.ClearStatistics(UserUID); + + await UpdateLocation(new LocationDto(new UserData(UserUID), new LocationInfo()), offline: true).ConfigureAwait(false); await SendOfflineToAllPairedUsers().ConfigureAwait(false); diff --git a/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs b/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs index 546cf0c..92cf499 100644 --- a/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs +++ b/LightlessSyncServer/LightlessSyncServer/Utils/Extensions.cs @@ -151,6 +151,7 @@ public static class Extensions perm.SetDisableVFX(permissions.DisableVFX); if (setSticky) perm.SetSticky(permissions.Sticky); + perm.SetShareLocation(permissions.ShareLocationUntil > DateTimeOffset.UtcNow); return perm; } } diff --git a/LightlessSyncServer/LightlessSyncShared/Models/UserPermissionSet.cs b/LightlessSyncServer/LightlessSyncShared/Models/UserPermissionSet.cs index 5a00f3a..2f4ad5b 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 DateTimeOffset ShareLocationUntil { get; set; } = DateTimeOffset.MinValue; }