diff --git a/Signal-Windows.Lib/Events/SignalMessageEventArgs.cs b/Signal-Windows.Lib/Events/SignalMessageEventArgs.cs index 3296602..639c920 100644 --- a/Signal-Windows.Lib/Events/SignalMessageEventArgs.cs +++ b/Signal-Windows.Lib/Events/SignalMessageEventArgs.cs @@ -1,19 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Signal_Windows.Models; - -namespace Signal_Windows.Lib.Events -{ - public class SignalMessageEventArgs : EventArgs - { - public SignalMessage Message { get; private set; } - - public SignalMessageEventArgs(SignalMessage message) - { - Message = message; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Signal_Windows.Models; + +namespace Signal_Windows.Lib.Events +{ + public enum SignalMessageType + { + NormalMessage, + PipeEmptyMessage + } + public class SignalMessageEventArgs : EventArgs + { + public SignalMessage Message { get; private set; } + public SignalMessageType MessageType { get; private set; } + + public SignalMessageEventArgs(SignalMessage message, SignalMessageType type) + { + Message = message; + MessageType = type; + } + } +} diff --git a/Signal-Windows.Lib/IncomingMessages.cs b/Signal-Windows.Lib/IncomingMessages.cs index d50c915..99d67da 100644 --- a/Signal-Windows.Lib/IncomingMessages.cs +++ b/Signal-Windows.Lib/IncomingMessages.cs @@ -81,6 +81,10 @@ public void OnMessage(SignalServiceMessagePipeMessage message) Logger.LogWarning("OnMessage() could not handle unknown message type {0}", envelope.getType()); } } + else if (message is SignalServiceMessagePipeEmptyMessage) + { + Handle.DispatchPipeEmptyMessage(); + } } finally { diff --git a/Signal-Windows.Lib/LibUtils.cs b/Signal-Windows.Lib/LibUtils.cs index 25ac809..71b76e1 100644 --- a/Signal-Windows.Lib/LibUtils.cs +++ b/Signal-Windows.Lib/LibUtils.cs @@ -1,4 +1,6 @@ -using libsignalservice.push; +using libsignalservice; +using libsignalservice.push; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -58,24 +60,93 @@ await dispatcher.RunAsync(priority, () => public class LibUtils { - public const string GlobalSemaphoreName = "SignalWindowsPrivateMessenger_Mutex"; + private static readonly ILogger Logger = LibsignalLogging.CreateLogger(); + public const string GlobalMutexName = "SignalWindowsPrivateMessenger_Mutex"; + public const string GlobalEventWaitHandleName = "SignalWindowsPrivateMessenger_EventWaitHandle"; public static string URL = "https://textsecure-service.whispersystems.org"; public static SignalServiceUrl[] ServiceUrls = new SignalServiceUrl[] { new SignalServiceUrl(URL, null) }; public static bool MainPageActive = false; public static string USER_AGENT = "Signal-Windows"; public static uint PREKEY_BATCH_SIZE = 100; public static bool WindowActive = false; - public static Semaphore GlobalSemaphore; + public static Mutex GlobalLock; + private static SynchronizationContext GlobalLockContext; internal static void Lock() { - GlobalSemaphore = new Semaphore(1, 1, GlobalSemaphoreName, out bool b); - GlobalSemaphore.WaitOne(); + Logger.LogTrace("System lock locking, sync context = {0}", SynchronizationContext.Current); + GlobalLock = new Mutex(false, GlobalMutexName, out bool createdNew); + GlobalLockContext = SynchronizationContext.Current; + try + { + GlobalLock.WaitOne(); + } + catch (AbandonedMutexException e) + { + Logger.LogWarning("System lock was abandoned! {0}", e.Message); + } + Logger.LogTrace("System lock locked"); + } + + public static bool Lock(int timeout) + { + GlobalLock = new Mutex(false, GlobalMutexName, out bool createdNew); + GlobalLockContext = SynchronizationContext.Current; + Logger.LogTrace("System lock locking with timeout, sync context = {0}", SynchronizationContext.Current); + bool success = false; + try + { + success = GlobalLock.WaitOne(timeout); + } + catch(AbandonedMutexException e) + { + Logger.LogWarning("System lock was abandoned! {0}", e.Message); + success = true; + } + Logger.LogTrace("System lock locked = {}", success); + return success; + } + + public static void Unlock() + { + Logger.LogTrace("System lock releasing, sync context = {0}", SynchronizationContext.Current); + try + { + if(GlobalLockContext != null) + { + GlobalLockContext.Post((a) => + { + GlobalLock.ReleaseMutex(); + }, null); + } + else + { + GlobalLock.ReleaseMutex(); + } + } + catch(Exception e) + { + Logger.LogWarning("System lock failed to unlock! {0}\n{1}", e.Message, e.StackTrace); + } + Logger.LogTrace("System lock released"); + } + + public static EventWaitHandle OpenResetEventSet() + { + Logger.LogTrace("OpenResetEventSet()"); + var handle = new EventWaitHandle(true, EventResetMode.ManualReset, GlobalEventWaitHandleName, out bool createdNew); + if(!createdNew) + { + Logger.LogTrace("OpenResetEventSet() setting old event"); + handle.Set(); + } + return handle; } - internal static void Unlock() + public static EventWaitHandle OpenResetEventUnset() { - GlobalSemaphore.Release(); + Logger.LogTrace("OpenResetEventUnset()"); + return new EventWaitHandle(false, EventResetMode.ManualReset, GlobalEventWaitHandleName, out bool createdNew); } } } diff --git a/Signal-Windows.Lib/SignalLibHandle.cs b/Signal-Windows.Lib/SignalLibHandle.cs index cc695d8..2391491 100644 --- a/Signal-Windows.Lib/SignalLibHandle.cs +++ b/Signal-Windows.Lib/SignalLibHandle.cs @@ -44,6 +44,7 @@ public class SignalLibHandle private SignalServiceMessageSender MessageSender; private SignalServiceMessageReceiver MessageReceiver; public BlockingCollection OutgoingQueue = new BlockingCollection(new ConcurrentQueue()); + private EventWaitHandle GlobalResetEvent; public event EventHandler SignalMessageEvent; @@ -99,7 +100,9 @@ public async Task Acquire(CoreDispatcher d, ISignalFrontend w) //TODO wrap tryca Logger.LogTrace("Acquire() locking"); CancelSource = new CancellationTokenSource(); SemaphoreSlim.Wait(CancelSource.Token); + GlobalResetEvent = LibUtils.OpenResetEventSet(); LibUtils.Lock(); + GlobalResetEvent.Reset(); var getConversationsTask = Task.Run(() => { return GetConversations(); // we want to display the conversations asap! @@ -146,7 +149,9 @@ public async Task Reacquire() Logger.LogTrace("Reacquire() locking"); CancelSource = new CancellationTokenSource(); SemaphoreSlim.Wait(CancelSource.Token); + GlobalResetEvent = LibUtils.OpenResetEventSet(); LibUtils.Lock(); + GlobalResetEvent.Reset(); LibsignalDBContext.ClearSessionCache(); Instance = this; await Task.Run(() => @@ -178,9 +183,11 @@ public void Release() IncomingMessagesTask?.Wait(); OutgoingMessagesTask?.Wait(); Instance = null; - Logger.LogTrace("Release() releasing (global and local)"); + Logger.LogTrace("Release() releasing global)"); LibUtils.Unlock(); + Logger.LogTrace("Release() releasing local)"); SemaphoreSlim.Release(); + Logger.LogTrace("Release() released"); } public void BackgroundRelease() @@ -277,10 +284,15 @@ internal void DispatchHandleMessage(SignalMessage message, SignalConversation co Frames[dispatcher].HandleMessage(message, conversation); })); } - SignalMessageEvent?.Invoke(this, new SignalMessageEventArgs(message)); + SignalMessageEvent?.Invoke(this, new SignalMessageEventArgs(message, Events.SignalMessageType.NormalMessage)); Task.WaitAll(operations.ToArray()); } + internal void DispatchPipeEmptyMessage() + { + SignalMessageEvent?.Invoke(this, new SignalMessageEventArgs(null, Events.SignalMessageType.PipeEmptyMessage)); + } + internal void HandleMessageSentLocked(SignalMessage msg) { Logger.LogTrace("HandleMessageSentLocked() locking"); diff --git a/Signal-Windows.RC/SignalBackgroundTask.cs b/Signal-Windows.RC/SignalBackgroundTask.cs index f7e3c4f..af06986 100644 --- a/Signal-Windows.RC/SignalBackgroundTask.cs +++ b/Signal-Windows.RC/SignalBackgroundTask.cs @@ -1,151 +1,136 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using libsignalservice; -using Microsoft.Extensions.Logging; -using Microsoft.Toolkit.Uwp.Notifications; -using Signal_Windows.Lib; -using Signal_Windows.Lib.Events; -using Signal_Windows.Models; -using Signal_Windows.Storage; -using Windows.ApplicationModel.Background; -using Windows.UI.Notifications; - -namespace Signal_Windows.RC -{ - public sealed class SignalBackgroundTask : IBackgroundTask - { - private const string TaskName = "SignalMessageBackgroundTask"; - private const string SemaphoreName = "Signal_Windows_Semaphore"; - - private readonly ILogger Logger = LibsignalLogging.CreateLogger(); - - private BackgroundTaskDeferral deferral; - - private Semaphore semaphore; - private DateTime taskStartTime; - private DateTime taskEndTime; - private SignalLibHandle handle; - private ToastNotifier toastNotifier; - - public async void Run(IBackgroundTaskInstance taskInstance) - { - taskStartTime = DateTime.Now; - taskEndTime = taskStartTime + TimeSpan.FromSeconds(25); - taskInstance.Canceled += TaskInstance_Canceled; - deferral = taskInstance.GetDeferral(); - SignalLogging.SetupLogging(false); - toastNotifier = ToastNotificationManager.CreateToastNotifier(); - Logger.LogInformation("Background task starting"); - bool appRunning = IsAppRunning(); - if (appRunning) - { - Logger.LogWarning("App is running, background task shutting down"); - deferral.Complete(); - return; - } - handle = new SignalLibHandle(true); - handle.SignalMessageEvent += Handle_SignalMessageEvent; - handle.BackgroundAcquire(); - await CheckTimer(); - Shutdown(); - deferral.Complete(); - } - - private async Task CheckTimer() - { - Logger.LogInformation("Started listening for messages"); - while (true) - { - if (DateTime.Now >= taskEndTime) - { - return; - } - await Task.Delay(TimeSpan.FromMilliseconds(250)); - } - } - - private void TaskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) - { - Logger.LogError($"Background task cancelled: {reason}"); - Shutdown(); - deferral.Complete(); - } - - private bool IsAppRunning() - { - semaphore = null; - try - { - semaphore = Semaphore.OpenExisting(LibUtils.GlobalSemaphoreName); - } - catch (WaitHandleCannotBeOpenedException) - { - semaphore = new Semaphore(1, 1, LibUtils.GlobalSemaphoreName); - } - - bool gotSignal = semaphore.WaitOne(TimeSpan.FromSeconds(5)); - return !gotSignal; - } - - private void Shutdown() - { - Logger.LogInformation("Background task shutting down"); - handle.BackgroundRelease(); - semaphore.Release(); - } - - private void Handle_SignalMessageEvent(object sender, SignalMessageEventArgs e) - { - string notificationId = e.Message.ThreadId; - ToastBindingGeneric toastBinding = new ToastBindingGeneric(); - - var notificationText = GetNotificationText(e.Message.Author.ThreadDisplayName, e.Message.Content.Content); - foreach (var item in notificationText) - { - toastBinding.Children.Add(item); - } - - ToastContent toastContent = new ToastContent() - { - Launch = notificationId, - Visual = new ToastVisual() - { - BindingGeneric = toastBinding - }, - DisplayTimestamp = DateTimeOffset.FromUnixTimeMilliseconds(e.Message.ReceivedTimestamp) - }; - - ToastNotification toastNotification = new ToastNotification(toastContent.GetXml()); - uint expiresIn = e.Message.ExpiresAt; - if (expiresIn > 0) - { - toastNotification.ExpirationTime = DateTime.Now.Add(TimeSpan.FromSeconds(expiresIn)); - } - toastNotification.Tag = notificationId; - toastNotifier.Show(toastNotification); - } - - private IList GetNotificationText(string authorName, string content) - { - List text = new List(); - AdaptiveText title = new AdaptiveText() - { - Text = authorName, - HintMaxLines = 1 - }; - AdaptiveText messageText = new AdaptiveText() - { - Text = content, - HintWrap = true - }; - text.Add(title); - text.Add(messageText); - return text; - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using libsignalservice; +using Microsoft.Extensions.Logging; +using Microsoft.Toolkit.Uwp.Notifications; +using Signal_Windows.Lib; +using Signal_Windows.Lib.Events; +using Signal_Windows.Models; +using Signal_Windows.Storage; +using Windows.ApplicationModel.Background; +using Windows.UI.Notifications; + +namespace Signal_Windows.RC +{ + public sealed class SignalBackgroundTask : IBackgroundTask + { + private const string TaskName = "SignalMessageBackgroundTask"; + private readonly ILogger Logger = LibsignalLogging.CreateLogger(); + private BackgroundTaskDeferral Deferral; + private SignalLibHandle Handle; + private ToastNotifier ToastNotifier; + private AutoResetEvent ResetEvent = new AutoResetEvent(false); + private EventWaitHandle GlobalResetEvent; + + public void Run(IBackgroundTaskInstance taskInstance) + { + + Logger.LogInformation("Background task starting"); + Deferral = taskInstance.GetDeferral(); + SignalLogging.SetupLogging(false); + ToastNotifier = ToastNotificationManager.CreateToastNotifier(); + taskInstance.Canceled += OnCanceled; + bool locked = LibUtils.Lock(5000); + Logger.LogTrace("Locking global finished, locked = {0}", locked); + if (!locked) + { + Logger.LogWarning("App is running, background task shutting down"); + Deferral.Complete(); + return; + } + GlobalResetEvent = LibUtils.OpenResetEventUnset(); + Task.Run(() => + { + GlobalResetEvent.WaitOne(); + Logger.LogInformation("Background task received app startup signal"); + ResetEvent.Set(); + }); + try + { + Handle = new SignalLibHandle(true); + Handle.SignalMessageEvent += Handle_SignalMessageEvent; + Handle.BackgroundAcquire(); + ResetEvent.WaitOne(); + } + catch (Exception e) + { + Logger.LogError("Background task failed: {0}\n{1}", e.Message, e.StackTrace); + } + finally + { + Logger.LogInformation("Background task shutting down"); + Handle.BackgroundRelease(); + LibUtils.Unlock(); + Deferral.Complete(); + } + } + + private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) + { + Logger.LogInformation("Background task received cancel request"); + ResetEvent.Set(); + } + + private void Handle_SignalMessageEvent(object sender, SignalMessageEventArgs e) + { + if (e.MessageType == Lib.Events.SignalMessageType.NormalMessage) + { + string notificationId = e.Message.ThreadId; + ToastBindingGeneric toastBinding = new ToastBindingGeneric(); + + var notificationText = GetNotificationText(e.Message.Author.ThreadDisplayName, e.Message.Content.Content); + foreach (var item in notificationText) + { + toastBinding.Children.Add(item); + } + + ToastContent toastContent = new ToastContent() + { + Launch = notificationId, + Visual = new ToastVisual() + { + BindingGeneric = toastBinding + }, + DisplayTimestamp = DateTimeOffset.FromUnixTimeMilliseconds(e.Message.ReceivedTimestamp) + }; + + ToastNotification toastNotification = new ToastNotification(toastContent.GetXml()); + uint expiresIn = e.Message.ExpiresAt; + if (expiresIn > 0) + { + toastNotification.ExpirationTime = DateTime.Now.Add(TimeSpan.FromSeconds(expiresIn)); + } + toastNotification.Tag = notificationId; + ToastNotifier.Show(toastNotification); + } + else if (e.MessageType == Lib.Events.SignalMessageType.PipeEmptyMessage) + { + Logger.LogInformation("Background task has drained the pipe"); + ResetEvent.Set(); + } + } + + private IList GetNotificationText(string authorName, string content) + { + List text = new List(); + AdaptiveText title = new AdaptiveText() + { + Text = authorName, + HintMaxLines = 1 + }; + AdaptiveText messageText = new AdaptiveText() + { + Text = content, + HintWrap = true + }; + text.Add(title); + text.Add(messageText); + return text; + } + } +} diff --git a/Signal-Windows/App.xaml.cs b/Signal-Windows/App.xaml.cs index cfd99b5..aa2fc67 100644 --- a/Signal-Windows/App.xaml.cs +++ b/Signal-Windows/App.xaml.cs @@ -130,30 +130,38 @@ await Views[currentId].Dispatcher.RunTaskAsync(() => protected override async void OnLaunched(LaunchActivatedEventArgs e) { - string taskName = "SignalMessageBackgroundTask"; - BackgroundExecutionManager.RemoveAccess(); - - var builder = new BackgroundTaskBuilder(); - builder.Name = taskName; - builder.TaskEntryPoint = "Signal_Windows.RC.SignalBackgroundTask"; - builder.IsNetworkRequested = true; - builder.SetTrigger(new TimeTrigger(15, false)); - builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable)); - var requestStatus = await BackgroundExecutionManager.RequestAccessAsync(); - if (requestStatus != BackgroundAccessStatus.DeniedBySystemPolicy || - requestStatus != BackgroundAccessStatus.DeniedByUser || - requestStatus != BackgroundAccessStatus.Unspecified) + try { - backgroundTaskRegistration = builder.Register(); + string taskName = "SignalMessageBackgroundTask"; + BackgroundExecutionManager.RemoveAccess(); + + var builder = new BackgroundTaskBuilder(); + builder.Name = taskName; + builder.TaskEntryPoint = "Signal_Windows.RC.SignalBackgroundTask"; + builder.IsNetworkRequested = true; + builder.SetTrigger(new TimeTrigger(15, false)); + builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable)); + var requestStatus = await BackgroundExecutionManager.RequestAccessAsync(); + if (requestStatus != BackgroundAccessStatus.DeniedBySystemPolicy || + requestStatus != BackgroundAccessStatus.DeniedByUser || + requestStatus != BackgroundAccessStatus.Unspecified) + { + backgroundTaskRegistration = builder.Register(); + } + else + { + Logger.LogWarning($"Unable to register background task: {requestStatus}"); + } + + backgroundTaskRegistration.Completed += BackgroundTaskRegistration_Completed; } - else + catch(Exception ex) { - Logger.LogWarning($"Unable to register background task: {requestStatus}"); + Logger.LogError("Cannot setup bg task: {0}\n{1}", ex.Message, ex.StackTrace); } - backgroundTaskRegistration.Completed += BackgroundTaskRegistration_Completed; - Logger.LogInformation("Launching ({0})", e.PreviousExecutionState); + Logger.LogInformation("Launching (PreviousExecutionState={0})", e.PreviousExecutionState); Logger.LogDebug(LocalCacheFolder.Path); bool createdMainWindow = await CreateMainWindow(null); diff --git a/Signal-Windows/ViewModels/MainPageViewModel.cs b/Signal-Windows/ViewModels/MainPageViewModel.cs index 2de8a00..a47aac5 100644 --- a/Signal-Windows/ViewModels/MainPageViewModel.cs +++ b/Signal-Windows/ViewModels/MainPageViewModel.cs @@ -273,12 +273,14 @@ public void ReplaceConversationList(List conversations) } if (SelectedThread != null) { + Logger.LogDebug("SelectedThread is != null, refreshing"); SelectedThread = ConversationsDictionary[SelectedThread.ThreadId]; - View.Thread.Collection.Conversation = SelectedThread; + SelectConversation(SelectedThread.ThreadId); } if (RequestedConversationId != null && RequestedConversationId != "") { + Logger.LogDebug("RequestedConversationId is != null, refreshing"); SelectConversation(RequestedConversationId); } }