using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Sdk.Account; using Ryujinx.Horizon.Sdk.OsTypes; using Ryujinx.Horizon.Sdk.Sf; using System; using System.Collections.Generic; namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc { partial class NotificationService : INotificationService, IDisposable { private readonly NotificationEventHandler _notificationEventHandler; private readonly Uid _userId; private readonly FriendsServicePermissionLevel _permissionLevel; private readonly object _lock = new(); private SystemEventType _notificationEvent; private readonly LinkedList _notifications; private bool _hasNewFriendRequest; private bool _hasFriendListUpdate; public NotificationService(NotificationEventHandler notificationEventHandler, Uid userId, FriendsServicePermissionLevel permissionLevel) { _notificationEventHandler = notificationEventHandler; _userId = userId; _permissionLevel = permissionLevel; _notifications = new LinkedList(); Os.CreateSystemEvent(out _notificationEvent, EventClearMode.AutoClear, interProcess: true).AbortOnFailure(); _hasNewFriendRequest = false; _hasFriendListUpdate = false; notificationEventHandler.RegisterNotificationService(this); } [CmifCommand(0)] public Result GetEvent([CopyHandle] out int eventHandle) { eventHandle = Os.GetReadableHandleOfSystemEvent(ref _notificationEvent); return Result.Success; } [CmifCommand(1)] public Result Clear() { lock (_lock) { _hasNewFriendRequest = false; _hasFriendListUpdate = false; _notifications.Clear(); } return Result.Success; } [CmifCommand(2)] public Result Pop(out SizedNotificationInfo sizedNotificationInfo) { lock (_lock) { if (_notifications.Count >= 1) { sizedNotificationInfo = _notifications.First.Value; _notifications.RemoveFirst(); if (sizedNotificationInfo.Type == NotificationEventType.FriendListUpdate) { _hasFriendListUpdate = false; } else if (sizedNotificationInfo.Type == NotificationEventType.NewFriendRequest) { _hasNewFriendRequest = false; } return Result.Success; } } sizedNotificationInfo = default; return FriendResult.NotificationQueueEmpty; } public void SignalFriendListUpdate(Uid targetId) { lock (_lock) { if (_userId == targetId) { if (!_hasFriendListUpdate) { SizedNotificationInfo friendListNotification = new(); if (_notifications.Count != 0) { friendListNotification = _notifications.First.Value; _notifications.RemoveFirst(); } friendListNotification.Type = NotificationEventType.FriendListUpdate; _hasFriendListUpdate = true; if (_hasNewFriendRequest) { SizedNotificationInfo newFriendRequestNotification = new(); if (_notifications.Count != 0) { newFriendRequestNotification = _notifications.First.Value; _notifications.RemoveFirst(); } newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest; _notifications.AddFirst(newFriendRequestNotification); } // We defer this to make sure we are on top of the queue. _notifications.AddFirst(friendListNotification); } Os.SignalSystemEvent(ref _notificationEvent); } } } public void SignalNewFriendRequest(Uid targetId) { lock (_lock) { if (_permissionLevel.HasFlag(FriendsServicePermissionLevel.ViewerMask) && _userId == targetId) { if (!_hasNewFriendRequest) { if (_notifications.Count == 100) { SignalFriendListUpdate(targetId); } SizedNotificationInfo newFriendRequestNotification = new() { Type = NotificationEventType.NewFriendRequest, }; _notifications.AddLast(newFriendRequestNotification); _hasNewFriendRequest = true; } Os.SignalSystemEvent(ref _notificationEvent); } } } protected virtual void Dispose(bool disposing) { if (disposing) { _notificationEventHandler.UnregisterNotificationService(this); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } }