Class CaldavPendingInvitationService
PARTSTAT=NEEDS-ACTION (EXO-89681).
Until this ran, a copy only ever reached the account because the user acted in a browser — and the act that pushed it was accepting, so a meeting the user was still deciding about was exactly the one their phone never showed. This pass runs server-side inside the background sync, so a pending invitation becomes visible on the user's devices while they are deciding — which is the precondition for answering it there at all: the verification pass reads the answer back off the copy.
It seeds; it does not maintain. An event that already has a mapping row is left to the machinery that owns it — the browser-triggered push for edits, the verification pass for drift and answers.
What it seeds, and why that is not only meetings (EXO-89796)
It used to be only meetings: the pass listed the events the user is an attendee of, and refused any event living in a calendar the user owns. Both halves of that were wrong, and together they made the add-on answer two different questions depending on when an event had been created.
An event a user creates in their own calendar with nobody invited has
no attendee row at all — agenda writes attendee rows only for the
attendees it is given (AgendaEventServiceImpl.createEvent saves them
under if (attendees != null && !attendees.isEmpty())) — and agenda's
window query joins on those rows (EventDAO.getEventIds does
INNER JOIN ev.attendees att ... AND att.identityId IN (:attendeeIds)).
So such an event was invisible to this pass whatever response the filter
asked for, and the own-calendar refusal would have turned it away even if it
had been listed. Created while the account was connected it was copied
anyway, by the browser's own push on save; created before, it was copied by
nothing, for ever. The same event, copied or not according only to when it
came into being.
So the pass now asks two questions — the events the user attends, and the
events of the user's own calendars — and stops refusing the second. Nothing
is mixed by that: CaldavPushService.pushAgendaEvent(long, String, long) is
what routes an event, and it sends an event of the user's own calendar to
that calendar's own collection, never to the mirror, answering null when
there is no collection to write into.
The one question both paths ask
seedOne(long, String, long) is the whole decision, and it is deliberately
the only one: the background pass reaches it through
pushUpcomingMeetings(long, String) and the creation listener reaches it
through seedMeeting(long, String, long), so an event that would be copied
were it created now is the same event this pass backfills. The listing above
is candidate selection and nothing more — a cheap way to name events, never
a second set of rules. That is why the "not DECLINED" rule lives in
seedOne and not only in the query.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionintpushUpcomingMeetings(long userIdentityId, String username) Copies the user's upcoming events — meetings they have not answered yet and events of their own calendars alike — into their connected account, skipping everything a copy already exists for.booleanseedMeeting(long userIdentityId, String username, long eventId) Writes the copy of one named meeting into one user's account, for a caller that already knows which meeting and which user.
-
Constructor Details
-
CaldavPendingInvitationService
public CaldavPendingInvitationService()
-
-
Method Details
-
pushUpcomingMeetings
Copies the user's upcoming events — meetings they have not answered yet and events of their own calendars alike — into their connected account, skipping everything a copy already exists for.Two questions, because agenda cannot answer them as one: its window query ANDs the calendar-owner predicate with the attendee predicate rather than ORing them (
EventDAO.getEventIds), so "events I attend or events in my calendars" is two calls. Their answers are merged and asked about once.- Parameters:
userIdentityId- identity of the user whose account receives copiesusername- their eXo login, which the credentials provider maps to their account on the server- Returns:
- how many events were written this pass
-
seedMeeting
Writes the copy of one named meeting into one user's account, for a caller that already knows which meeting and which user.The same decision as the pass above, asked one meeting at a time. It exists because a meeting being created is a fact the platform already tells us (
exo.agenda.event.created, EXO-89754), and waiting for the next sweep to rediscover it by listing a 60-day window is both slower and narrower: the window is where a meeting further out, or created in the past, used to fall through and never get a copy at all.Every refusal the pass makes is made here too, and that is the point of routing through this service rather than calling the push directly: the account has to be connected with copies enabled, the meeting has to be CONFIRMED, the user must not have declined it, and a meeting that already has a copy is left to the machinery that owns it. That last one is what makes a second trigger on the same creation write nothing.
- Parameters:
userIdentityId- identity of the user whose account receives the copyusername- their eXo login, which the credentials provider maps to their account on the servereventId- the agenda event — a series master or a single event- Returns:
- true when a copy was written
-