ement.el icon indicating copy to clipboard operation
ement.el copied to clipboard

Customize which events can (or cannot) affect the "latest timestamp" for a room

Open phil-s opened this issue 2 years ago • 5 comments

I'd like it if the "Latest" value in the room list represented the most recent message, but I think it reflects any event, including things like "someone changed their username", making it annoying to check the room to see what's new and find that the answer is "absolutely nothing of interest".

It would be really nice if you could constrain which events triggered a change to the timestamp for the room list purposes.

I got as far as looking at ement--push-joined-room-events and the push-events / setf latest-timestamp code, but I don't understand the Matrix API so I can only hand-wave about some kind of conditional code at that point (or maybe a second, new timestamp just for the room listing).

phil-s avatar Jun 27 '23 03:06 phil-s

You could try using the notifications settings and then only checking rooms that have unread notifications.

Other than that, it seems like a reasonable idea, although it could affect performance. I'm willing to entertain patches to that effect, at least.

alphapapa avatar Jun 27 '23 10:06 alphapapa

I wrote an interim fix for this today. Needs some thought regarding how to make the configuration easy to use, but I'll chuck the diff in here for now.

modified   ement.el
@@ -735,6 +735,31 @@ ement--update-room-buffers
             (ement-room--process-events new-events)
             (setf (alist-get 'new-account-data-events (ement-room-local ement-room)) nil)))))))
 
+(defcustom ement-room-latest-timestamp-event-types t
+  "Event types which affect the latest timestamp of a room."
+  :type '(choice (const :tag "All events" t)
+                 (repeat :tag "Specified events" string))
+  ;; Event names one might want to consider:
+  ;;            "m.reaction"
+  ;;            "m.room.avatar"
+  ;;            "m.room.canonical_alias"
+  ;;            "m.room.create"
+  ;;            "m.room.guest_access"
+  ;;            "m.room.history_visibility"
+  ;;            "m.room.join_rules"
+  ;;            "m.room.member"
+  ;;            "m.room.message"
+  ;;            "m.room.name"
+  ;;            "m.room.power_levels"
+  ;;            "m.room.redaction"
+  ;;            "m.room.related_groups"
+  ;;            "m.room.third_party_invite"
+  ;;            "m.room.tombstone"
+  ;;            "m.room.topic"
+  ;;            "m.space.child"
+  :group 'ement-room)
+
 (cl-defun ement--push-joined-room-events (session joined-room &optional (status 'join))
   "Push events for JOINED-ROOM into that room in SESSION.
 Also used for left rooms, in which case STATUS should be set to
@@ -802,7 +827,10 @@ ement--push-joined-room-events
                                 do (push event (,accessor room))
                                 (when (ement--sync-messages-p session)
                                   (ement-progress-update))
-                                (when (> (ement-event-origin-server-ts event) ts)
+                                (when (and (> (ement-event-origin-server-ts event) ts)
+                                           (or (eq ement-room-latest-timestamp-event-types t)
+                                               (member (ement-event-type event)
+                                                       ement-room-latest-timestamp-event-types)))
                                   (setf ts (ement-event-origin-server-ts event))))
                        ;; One would think that one should use `maximizing' here, but, completely
                        ;; inexplicably, it sometimes returns nil, even when every single value it's comparing

The commented event list I extracted from my ement-session. I also had a look at the API docs and made some notes, but have put that aside for the moment. The most notable thing is that the list of valid event names is not fixed or determined by the spec -- events may be added or deprecated, and custom events are possible, so it would be pointless to try to present a comprehensive list. It would probably be worth calling out some common cases in the docs, though. I also thought I might change it to an alist purely so that I could present those common event names as :options in the customize UI. (I have no idea why :options is limited to so few types; that is sometimes quite frustrating.)

I'm using the following for now:

(setopt ement-room-latest-timestamp-event-types
        '("m.room.create"
          "m.room.message"
          "m.room.name"
          "m.room.power_levels"
          "m.room.redaction"
          "m.room.topic"))

It tends to make rooms without relevant activity in the initial sync appear to not have had any updates for ages (maybe since m.room.create), and acquiring retro events doesn't change that, but I'm ok with that for the moment.

phil-s avatar Oct 29 '24 09:10 phil-s

Not tested, but I think the retro part might mean doing the same things in ement-room-retro-callback as it's not using ement--push-joined-room-events:

modified   ement-room.el
@@ -2735,7 +2735,8 @@ ement-room-retro-callback
                ;; likely very few compared to the number of timeline events, which is
                ;; what the user is interested in (e.g. when loading 1000 earlier
                ;; messages in #emacs:matrix.org, only 31 state events were received).
-               (progress-max-value (* 3 num-events)))
+               (progress-max-value (* 3 num-events))
+               (ts 0))
     ;; NOTE: Put the newly retrieved events at the end of the slots, because they should be
     ;; older events.  But reverse them first, because we're using "dir=b", which the
     ;; spec says causes the events to be returned in reverse-chronological order, and we
@@ -2750,6 +2751,11 @@ ement-room-retro-callback
     ;; Append state events.
     (cl-loop for event across-ref state
              do (setf event (ement--make-event event))
+             (when (and (> (ement-event-origin-server-ts event) ts)
+                        (or (eq ement-room-latest-timestamp-event-types t)
+                            (member (ement-event-type event)
+                                    ement-room-latest-timestamp-event-types)))
+               (setf ts (ement-event-origin-server-ts event)))
              finally do (setf (ement-room-state room)
                               (append (ement-room-state room) (append state nil))))
     (ement-with-progress-reporter (:reporter ("Ement: Processing earlier events..." 0 progress-max-value))
@@ -2766,13 +2772,21 @@ ement-room-retro-callback
                     ;; New event.
                     (setf event (ement--make-event event))
                     ;; HACK: Put events on events table.  See FIXME above about using the event hook.
-                    (ement--put-event event nil session))
+                    (ement--put-event event nil session)
+                    (when (and (> (ement-event-origin-server-ts event) ts)
+                               (or (eq ement-room-latest-timestamp-event-types t)
+                                   (member (ement-event-type event)
+                                           ement-room-latest-timestamp-event-types)))
+                      (setf ts (ement-event-origin-server-ts event))))
                (ement-progress-update)
                finally do
                (setf chunk (seq-remove #'null chunk)
                      (ement-room-timeline room) (append (ement-room-timeline room) chunk)))
+      ;; Update room's latest-timestamp slot.
+      (when (> ts (or (ement-room-latest-ts room) 0))
+        (setf (ement-room-latest-ts room) ts))
+      ;; Insert events into the room's buffer.
       (when buffer
-        ;; Insert events into the room's buffer.
         (with-current-buffer buffer
           (save-window-excursion
             ;; NOTE: See note in `ement--update-room-buffers'.

Now tested/fixed. Seems to do the trick.

phil-s avatar Oct 29 '24 09:10 phil-s

This has been working nicely since then, and definitely makes things better for me.

Once I get some time to experiment more with customization types, I'll make a PR.

phil-s avatar Nov 13 '24 05:11 phil-s

Thanks. Would you be willing to do a quick benchmark comparing using a list of event type strings with member and using a hash-table with gethash? It probably doesn't matter, but with how often that comparison would be done when processing large numbers of events, I'd like to be careful.

alphapapa avatar Nov 13 '24 05:11 alphapapa