diff --git a/src/Filter.c b/src/Filter.c index dc72596..e269ef7 100644 --- a/src/Filter.c +++ b/src/Filter.c @@ -32,42 +32,95 @@ #include +/* Verifies whenever an item passes through a set of blacklisted and + * whitelisted groups(e.g "not_rooms"/"rooms"), and makes the calling + * function return true/false if it is explicitely in a filtered list, + * or does nothing otherwise, to allow for chaining multiple filters. */ +#define DAFiltered(blacklist, whitelist, item) \ + do \ + { \ + size_t i, count; \ + count = ArraySize(blacklist); \ + for (i = 0; i < count; i++) \ + { \ + char *notItem = ArrayGet(blacklist, i); \ + if (StrEquals(item, notItem)) \ + { \ + return true; \ + } \ + } \ + count = ArraySize(whitelist); \ + if (count) \ + { \ + for (i = 0; i < count; i++) \ + { \ + char *yesItem = ArrayGet(whitelist, i); \ + if (StrEquals(yesItem, item)) \ + { \ + return false; \ + } \ + } \ + return true; \ + } \ + } while (0) +static bool +IsSenderFiltered(Filter *filter, char *senderID) +{ + if (!filter || !senderID) + { + return false; + } + + DAFiltered( + filter->room.account_data.not_senders, + filter->room.account_data.senders, + senderID + ); + DAFiltered( + filter->room.state.not_senders, + filter->room.state.senders, + senderID + ); + DAFiltered( + filter->room.ephemeral.not_senders, + filter->room.ephemeral.senders, + senderID + ); + return false; +} bool IsRoomFiltered(Filter *filter, char *roomID) { - size_t i, count; if (!filter || !roomID) { return false; } - count = ArraySize(filter->room.not_rooms); - for (i = 0; i < count; i++) - { - char *notRoom = ArrayGet(filter->room.not_rooms, i); - if (StrEquals(roomID, notRoom)) - { - return true; - } - } - - count = ArraySize(filter->room.rooms); - if (count) - { - for (i = 0; i < count; i++) - { - char *room = ArrayGet(filter->room.rooms, i); - if (StrEquals(roomID, room)) - { - return false; - } - } - return true; - } + DAFiltered( + filter->room.not_rooms, + filter->room.rooms, + roomID + ); + DAFiltered( + filter->room.account_data.not_rooms, + filter->room.account_data.rooms, + roomID + ); + DAFiltered( + filter->room.state.not_rooms, + filter->room.state.rooms, + roomID + ); + DAFiltered( + filter->room.ephemeral.not_rooms, + filter->room.ephemeral.rooms, + roomID + ); return false; } +/* TODO: MORE FILTERS! */ HashMap * FilterApply(Filter * filter, HashMap * event) { @@ -86,15 +139,17 @@ FilterApply(Filter * filter, HashMap * event) sender = JsonValueAsString(HashMapGet(event, "sender")); room = JsonValueAsString(HashMapGet(event, "room_id")); - if (IsRoomFiltered(filter, room)) + if (IsRoomFiltered(filter, room) || + IsSenderFiltered(filter, sender)) { return NULL; } copy = JsonDuplicate(event); - (void) sender; return copy; } + + Filter * FilterDecode(Db *db, char *user, char *filterStr) { diff --git a/src/Routes/RouteSync.c b/src/Routes/RouteSync.c index 89ff425..0660363 100644 --- a/src/Routes/RouteSync.c +++ b/src/Routes/RouteSync.c @@ -138,23 +138,9 @@ ROUTE_IMPL(RouteSync, path, argp) else if (timeout && timeoutDuration) { char *name = StrDuplicate(UserGetName(user)); - int passed = 0; UserUnlock(user); - - /* TODO: Using pthreads' condition variables with a timeout would be - * the best way to proceed (as in Parsee's ParseeAwaitStanza function). - * Also, how would Cytoplasm proceed if the all of HTTP threads are used - * for syncing? That sounds like a really bad bottleneck... */ - while (passed < timeoutDuration) - { - if (UserGetNotification(name)) - { - break; - } - UtilSleepMillis(100); - passed += 100; - } + UserAwaitNotification(name, timeoutDuration); Free(name); user = UserAuthenticate(db, token); } diff --git a/src/User.c b/src/User.c index bf4c763..4c1e7e5 100644 --- a/src/User.c +++ b/src/User.c @@ -34,8 +34,9 @@ #include #include -#include #include +#include +#include struct User { @@ -1637,6 +1638,16 @@ UserSyncExists(User *user, char *sync) return DbLock(user->db, 4, "users", user->name, "sync", sync); } +typedef struct NotificationEntry { + enum { + NOTIF_AWAIT, + NOTIF_GOTTEN + } type; + + pthread_mutex_t lock; + pthread_cond_t cond; + bool notified; +} NotificationEntry; extern void UserInitialisePushTable(void) @@ -1655,29 +1666,34 @@ UserInitialisePushTable(void) void UserNotifyUser(User *user) { + NotificationEntry *entry; if (!user || !pushTable) { return; } pthread_mutex_lock(&pushLock); - HashMapSet(pushTable, user->name, user->name); - pthread_mutex_unlock(&pushLock); -} - -bool -UserGetNotification(char *user) -{ - bool ret; - if (!user || !pushTable) + entry = HashMapGet(pushTable, user->name); + if (entry && entry->type == NOTIF_AWAIT) { - return false; - } - pthread_mutex_lock(&pushLock); - ret = !!HashMapGet(pushTable, user); - HashMapDelete(pushTable, user); - pthread_mutex_unlock(&pushLock); + pthread_mutex_lock(&entry->lock); + entry->notified = true; + pthread_cond_signal(&entry->cond); + pthread_mutex_unlock(&entry->lock); - return ret; + pthread_mutex_unlock(&pushLock); + return; + } + else if (!entry) + { + entry = Malloc(sizeof(*entry)); + entry->type = NOTIF_GOTTEN; + + pthread_mutex_unlock(&pushLock); + return; + } + + /* There was already a notification on the line. */ + pthread_mutex_unlock(&pushLock); } void @@ -1694,3 +1710,94 @@ UserDestroyPushTable(void) pthread_mutex_unlock(&pushLock); pthread_mutex_destroy(&pushLock); } + +bool +UserAwaitNotification(char *user, int await) +{ + NotificationEntry *entry, ownEntry; + struct timespec timeout; + int code; + bool timedout = false, notified = false; + if (!user) + { + return false; + } + + if (await < 0) + { + /* 30 seconds */ + await = 30000; + } + + pthread_mutex_lock(&pushLock); + + /* Check if we got any notifications yet. */ + entry = HashMapGet(pushTable, user); + if (entry && entry->type == NOTIF_GOTTEN) + { + /* Got a notification entry already. */ + Free(entry); + HashMapDelete(pushTable, user); + pthread_mutex_unlock(&pushLock); + + return true; + } + else if (entry) + { + /* Another thread's awaiting... + * TODO: Manage these conditions. */ + Log(LOG_ERR, + "Unimplemented feature: awaiting for other threads." + ); + + pthread_mutex_unlock(&pushLock); + return false; + } + + /* No one's waiting or notifying; let's create our own entry, + * and await for something(NOTE that we're not allocating from + * the heap, since I've noticed some strange behaviour with + * Cytoplasm on ARM64... This is a hack and deserves to be fixed). */ + entry = &ownEntry; + entry->type = NOTIF_AWAIT; + entry->notified = false; + pthread_mutex_init(&entry->lock, NULL); + pthread_cond_init(&entry->cond, NULL); + HashMapSet(pushTable, user, entry); + pthread_mutex_unlock(&pushLock); + + /* Now, it's time for us to wait. */ + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += await / 1000; + timeout.tv_nsec += (await % 1000) * 1000000; + if (timeout.tv_nsec > 999999999) + { + uint64_t driftSeconds = timeout.tv_nsec / 1000000000; + uint64_t driftMicros = timeout.tv_nsec % 1000000000; + + timeout.tv_nsec = driftMicros; + timeout.tv_sec += driftSeconds; + } + + pthread_mutex_lock(&entry->lock); + while (!entry->notified) + { + code = pthread_cond_timedwait(&entry->cond, &entry->lock, &timeout); + if ((code == ETIMEDOUT)) + { + timedout = true; + break; + } + } + pthread_mutex_unlock(&entry->lock); + pthread_mutex_destroy(&entry->lock); + pthread_cond_destroy(&entry->cond); + + notified = !timedout && entry->notified; + + pthread_mutex_lock(&pushLock); + HashMapDelete(pushTable, user); + pthread_mutex_unlock(&pushLock); + + return notified; +} diff --git a/src/include/User.h b/src/include/User.h index 51d36c5..06eb326 100644 --- a/src/include/User.h +++ b/src/include/User.h @@ -471,10 +471,11 @@ extern void UserInitialisePushTable(void); extern void UserNotifyUser(User *); /** - * Verifies if the user has been notified, and if it is, then - * removes the notification + * Waits for a notification for a specific user for a specific + * duration in milliseconds to wait (with 30000ms being the default, + * if the value is negative), and returns true if anything came up. */ -extern bool UserGetNotification(char *); +extern bool UserAwaitNotification(char *, int); /** * Destroys the temporary push table created by