Clear macOS notifications in a separate thread.

Sometimes NSUserNotificationCenter -deliveredNotifications method call
freezes for a long time, so now we use it only in a separate thread and
we group all the requests for clearing while another clearing is done.
This commit is contained in:
John Preston 2017-04-12 15:50:35 +03:00
parent 50ea4e316e
commit 1725927aea
3 changed files with 96 additions and 38 deletions

View File

@ -587,7 +587,6 @@ namespace Global {
namespace internal {
struct Data {
uint64 LaunchId = 0;
SingleQueuedInvokation HandleHistoryUpdate = { [] { App::app()->call_handleHistoryUpdate(); } };
SingleQueuedInvokation HandleUnreadCounterUpdate = { [] { App::app()->call_handleUnreadCounterUpdate(); } };
SingleQueuedInvokation HandleDelayedPeerUpdates = { [] { App::app()->call_handleDelayedPeerUpdates(); } };
@ -697,8 +696,6 @@ bool started() {
void start() {
GlobalData = new internal::Data();
memset_rand(&GlobalData->LaunchId, sizeof(GlobalData->LaunchId));
}
void finish() {
@ -706,7 +703,6 @@ void finish() {
GlobalData = nullptr;
}
DefineReadOnlyVar(Global, uint64, LaunchId);
DefineRefVar(Global, SingleQueuedInvokation, HandleHistoryUpdate);
DefineRefVar(Global, SingleQueuedInvokation, HandleUnreadCounterUpdate);
DefineRefVar(Global, SingleQueuedInvokation, HandleDelayedPeerUpdates);

View File

@ -291,7 +291,6 @@ bool started();
void start();
void finish();
DeclareReadOnlyVar(uint64, LaunchId);
DeclareRefVar(SingleQueuedInvokation, HandleHistoryUpdate);
DeclareRefVar(SingleQueuedInvokation, HandleUnreadCounterUpdate);
DeclareRefVar(SingleQueuedInvokation, HandleDelayedPeerUpdates);

View File

@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_window.h"
#include "mainwindow.h"
#include "base/task_queue.h"
#include "base/variant.h"
#include <thread>
#include <Cocoa/Cocoa.h>
@ -55,7 +56,7 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm);
@interface NotificationDelegate : NSObject<NSUserNotificationCenterDelegate> {
}
- (id) initWithManager:(base::weak_unique_ptr<Manager>)manager;
- (id) initWithManager:(base::weak_unique_ptr<Manager>)manager managerId:(uint64)managerId;
- (void) userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification*)notification;
- (BOOL) userNotificationCenter:(NSUserNotificationCenter*)center shouldPresentNotification:(NSUserNotification*)notification;
@ -63,22 +64,24 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm);
@implementation NotificationDelegate {
base::weak_unique_ptr<Manager> _manager;
uint64 _managerId;
}
- (id) initWithManager:(base::weak_unique_ptr<Manager>)manager {
- (id) initWithManager:(base::weak_unique_ptr<Manager>)manager managerId:(uint64)managerId {
if (self = [super init]) {
_manager = manager;
_managerId = managerId;
}
return self;
}
- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"];
auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL;
DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationLaunchId).arg(Global::LaunchId()));
if (notificationLaunchId != Global::LaunchId()) { // other app instance notification
NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"];
auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL;
DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationManagerId).arg(_managerId));
if (notificationManagerId != _managerId) { // other app instance notification
base::TaskQueue::Main().Put([] {
// Usually we show and activate main window when the application
// is activated (receives applicationDidBecomeActive: notification).
@ -182,12 +185,37 @@ public:
~Private();
private:
template <typename Task>
void putClearTask(Task task);
void clearingThreadLoop();
const uint64 _managerId = 0;
QString _managerIdString;
NotificationDelegate *_delegate = nullptr;
std::thread _clearingThread;
std::mutex _clearingMutex;
std::condition_variable _clearingCondition;
struct ClearFromHistory {
PeerId peerId;
};
struct ClearAll {
};
struct ClearFinish {
};
using ClearTask = base::variant<ClearFromHistory, ClearAll, ClearFinish>;
std::vector<ClearTask> _clearingTasks;
};
Manager::Private::Private(Manager *manager)
: _delegate([[NotificationDelegate alloc] initWithManager:manager]) {
: _managerId(rand_value<uint64>())
, _managerIdString(QString::number(_managerId))
, _delegate([[NotificationDelegate alloc] initWithManager:manager managerId:_managerId])
, _clearingThread([this] { clearingThreadLoop(); }) {
updateDelegate();
subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) {
// We need to update the delegate _after_ the tray icon change was done in Qt.
@ -203,11 +231,11 @@ void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QStri
NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
if ([notification respondsToSelector:@selector(setIdentifier:)]) {
auto identifier = QString::number(Global::LaunchId()) + '_' + QString::number(peer->id) + '_' + QString::number(msgId);
auto identifier = _managerIdString + '_' + QString::number(peer->id) + '_' + QString::number(msgId);
auto identifierValue = Q2NSString(identifier);
[notification setIdentifier:identifierValue];
}
[notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:Global::LaunchId()],@"launch",nil]];
[notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:_managerId],@"manager",nil]];
[notification setTitle:Q2NSString(title)];
[notification setSubtitle:Q2NSString(subtitle)];
@ -230,34 +258,68 @@ void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QStri
}
}
void Manager::Private::clearAll() {
void Manager::Private::clearingThreadLoop() {
auto finished = false;
while (!finished) {
auto clearAll = false;
auto clearFromPeers = std::set<PeerId>(); // Better to use flatmap.
{
std::unique_lock<std::mutex> lock(_clearingMutex);
while (_clearingTasks.empty()) {
_clearingCondition.wait(lock);
}
for (auto &task : _clearingTasks) {
if (base::get_if<ClearFinish>(&task)) {
finished = true;
clearAll = true;
} else if (base::get_if<ClearAll>(&task)) {
clearAll = true;
} else if (auto fromHistory = base::get_if<ClearFromHistory>(&task)) {
clearFromPeers.insert(fromHistory->peerId);
}
}
_clearingTasks.clear();
}
auto clearByPeer = [&clearFromPeers](NSDictionary *notificationUserInfo) {
if (NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"]) {
auto notificationPeerId = [peerObject unsignedLongLongValue];
if (notificationPeerId) {
return (clearFromPeers.find(notificationPeerId) != clearFromPeers.cend());
}
}
return true;
};
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
NSArray *notificationsList = [center deliveredNotifications];
for (id notification in notificationsList) {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"];
auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL;
if (notificationLaunchId == Global::LaunchId()) {
NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"];
auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL;
if (notificationManagerId == _managerId) {
if (clearAll || clearByPeer(notificationUserInfo)) {
[center removeDeliveredNotification:notification];
}
}
}
}
}
void Manager::Private::clearFromHistory(History *history) {
unsigned long long peerId = history->peer->id;
template <typename Task>
void Manager::Private::putClearTask(Task task) {
std::unique_lock<std::mutex> lock(_clearingMutex);
_clearingTasks.push_back(task);
_clearingCondition.notify_one();
}
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
NSArray *notificationsList = [center deliveredNotifications];
for (id notification in notificationsList) {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"];
NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"];
auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL;
auto notificationPeerId = peerObject ? [peerObject unsignedLongLongValue] : 0ULL;
if (notificationPeerId == peerId && notificationLaunchId == Global::LaunchId()) {
[center removeDeliveredNotification:notification];
}
}
void Manager::Private::clearAll() {
putClearTask(ClearAll());
}
void Manager::Private::clearFromHistory(History *history) {
putClearTask(ClearFromHistory { history->peer->id });
}
void Manager::Private::updateDelegate() {
@ -266,7 +328,8 @@ void Manager::Private::updateDelegate() {
}
Manager::Private::~Private() {
clearAll();
putClearTask(ClearFinish());
_clearingThread.join();
[_delegate release];
}