diff --git a/clientapi/producers/syncapi.go b/clientapi/producers/syncapi.go index 047e787c0..6e869c312 100644 --- a/clientapi/producers/syncapi.go +++ b/clientapi/producers/syncapi.go @@ -177,11 +177,11 @@ func (p *SyncAPIProducer) SendTyping( } func (p *SyncAPIProducer) SendPresence( - ctx context.Context, userID, presence string, statusMsg *string, + ctx context.Context, userID string, presence types.Presence, statusMsg *string, ) error { m := nats.NewMsg(p.TopicPresenceEvent) m.Header.Set(jetstream.UserID, userID) - m.Header.Set("presence", presence) + m.Header.Set("presence", presence.String()) if statusMsg != nil { m.Header.Set("status_msg", *statusMsg) } diff --git a/clientapi/routing/presence.go b/clientapi/routing/presence.go index 83ff92e8c..2d0672e91 100644 --- a/clientapi/routing/presence.go +++ b/clientapi/routing/presence.go @@ -18,7 +18,6 @@ import ( "fmt" "net/http" "strconv" - "strings" "time" "github.com/matrix-org/dendrite/clientapi/httputil" @@ -63,15 +62,16 @@ func SetPresence( if parseErr != nil { return *parseErr } - p := strings.ToLower(presence.Presence) - if _, ok := types.PresenceToInt[p]; !ok { + + presenceStatus, ok := types.PresenceFromString(presence.Presence) + if !ok { return util.JSONResponse{ Code: http.StatusBadRequest, - JSON: jsonerror.Unknown(fmt.Sprintf("Unknown presence '%s'.", p)), + JSON: jsonerror.Unknown(fmt.Sprintf("Unknown presence '%s'.", presence.Presence)), } } - err := producer.SendPresence(req.Context(), userID, presence.Presence, presence.StatusMsg) + err := producer.SendPresence(req.Context(), userID, presenceStatus, presence.StatusMsg) if err != nil { log.WithError(err).Errorf("failed to update presence") return util.JSONResponse{ @@ -112,7 +112,7 @@ func GetPresence( return util.JSONResponse{ Code: http.StatusOK, JSON: types.PresenceClientResponse{ - Presence: "unavailable", + Presence: types.PresenceUnavailable.String(), }, } } @@ -124,7 +124,7 @@ func GetPresence( } } - p := types.Presence{LastActiveTS: gomatrixserverlib.Timestamp(lastActive)} + p := types.PresenceInternal{LastActiveTS: gomatrixserverlib.Timestamp(lastActive)} currentlyActive := p.CurrentlyActive() return util.JSONResponse{ Code: http.StatusOK, diff --git a/federationapi/consumers/presence.go b/federationapi/consumers/presence.go index 8439cc6e8..f52d9a772 100644 --- a/federationapi/consumers/presence.go +++ b/federationapi/consumers/presence.go @@ -105,7 +105,7 @@ func (t *OutputPresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) b statusMsg = &status } - p := types.Presence{LastActiveTS: gomatrixserverlib.Timestamp(ts)} + p := types.PresenceInternal{LastActiveTS: gomatrixserverlib.Timestamp(ts)} content := fedTypes.Presence{ Push: []fedTypes.PresenceContent{ diff --git a/federationapi/producers/syncapi.go b/federationapi/producers/syncapi.go index e80d50521..494150036 100644 --- a/federationapi/producers/syncapi.go +++ b/federationapi/producers/syncapi.go @@ -146,11 +146,11 @@ func (p *SyncAPIProducer) SendTyping( } func (p *SyncAPIProducer) SendPresence( - ctx context.Context, userID, presence string, statusMsg *string, lastActiveAgo int64, + ctx context.Context, userID string, presence types.Presence, statusMsg *string, lastActiveAgo int64, ) error { m := nats.NewMsg(p.TopicPresenceEvent) m.Header.Set(jetstream.UserID, userID) - m.Header.Set("presence", presence) + m.Header.Set("presence", presence.String()) if statusMsg != nil { m.Header.Set("status_msg", *statusMsg) } diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index 007f6d8ba..a575040e2 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -30,6 +30,7 @@ import ( keyapi "github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" + syncTypes "github.com/matrix-org/dendrite/syncapi/types" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/prometheus/client_golang/prometheus" @@ -406,7 +407,12 @@ func (t *txnReq) processPresence(ctx context.Context, e gomatrixserverlib.EDU) e return err } for _, content := range payload.Push { - if err := t.producer.SendPresence(ctx, content.UserID, content.Presence, content.StatusMsg, content.LastActiveAgo); err != nil { + presence, ok := syncTypes.PresenceFromString(content.Presence) + if !ok { + logrus.Warnf("invalid presence '%s', skipping.", content.Presence) + continue + } + if err := t.producer.SendPresence(ctx, content.UserID, presence, content.StatusMsg, content.LastActiveAgo); err != nil { return err } } diff --git a/syncapi/consumers/presence.go b/syncapi/consumers/presence.go index e4be2477d..2eb5784e5 100644 --- a/syncapi/consumers/presence.go +++ b/syncapi/consumers/presence.go @@ -139,8 +139,9 @@ func (s *PresenceConsumer) onMessage(ctx context.Context, msg *nats.Msg) bool { newMsg := msg.Header.Get("status_msg") statusMsg = &newMsg } - - pos, err := s.db.UpdatePresence(ctx, userID, presence, statusMsg, gomatrixserverlib.Timestamp(ts), fromSync) + // OK is already checked, so no need to do it again + p, _ := types.PresenceFromString(presence) + pos, err := s.db.UpdatePresence(ctx, userID, p, statusMsg, gomatrixserverlib.Timestamp(ts), fromSync) if err != nil { return true } diff --git a/syncapi/producers/federationapi_presence.go b/syncapi/producers/federationapi_presence.go index 980e793d6..dc03457e3 100644 --- a/syncapi/producers/federationapi_presence.go +++ b/syncapi/producers/federationapi_presence.go @@ -16,10 +16,10 @@ package producers import ( "strconv" - "strings" "time" "github.com/matrix-org/dendrite/setup/jetstream" + "github.com/matrix-org/dendrite/syncapi/types" "github.com/matrix-org/gomatrixserverlib" "github.com/nats-io/nats.go" ) @@ -31,11 +31,11 @@ type FederationAPIPresenceProducer struct { } func (f *FederationAPIPresenceProducer) SendPresence( - userID, presence string, statusMsg *string, + userID string, presence types.Presence, statusMsg *string, ) error { msg := nats.NewMsg(f.Topic) msg.Header.Set(jetstream.UserID, userID) - msg.Header.Set("presence", strings.ToLower(presence)) + msg.Header.Set("presence", presence.String()) msg.Header.Set("from_sync", "true") // only update last_active_ts and presence msg.Header.Set("last_active_ts", strconv.Itoa(int(gomatrixserverlib.AsTimestamp(time.Now())))) diff --git a/syncapi/storage/interface.go b/syncapi/storage/interface.go index 618aad95c..0b3ab235b 100644 --- a/syncapi/storage/interface.go +++ b/syncapi/storage/interface.go @@ -153,8 +153,8 @@ type Database interface { } type Presence interface { - UpdatePresence(ctx context.Context, userID, presence string, statusMsg *string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (types.StreamPosition, error) - GetPresence(ctx context.Context, userID string) (*types.Presence, error) - PresenceAfter(ctx context.Context, after types.StreamPosition) (map[string]*types.Presence, error) + UpdatePresence(ctx context.Context, userID string, presence types.Presence, statusMsg *string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (types.StreamPosition, error) + GetPresence(ctx context.Context, userID string) (*types.PresenceInternal, error) + PresenceAfter(ctx context.Context, after types.StreamPosition) (map[string]*types.PresenceInternal, error) MaxStreamPositionForPresence(ctx context.Context) (types.StreamPosition, error) } diff --git a/syncapi/storage/postgres/presence_table.go b/syncapi/storage/postgres/presence_table.go index 135cba052..49336c4eb 100644 --- a/syncapi/storage/postgres/presence_table.go +++ b/syncapi/storage/postgres/presence_table.go @@ -103,17 +103,16 @@ func (p *presenceStatements) UpsertPresence( txn *sql.Tx, userID string, statusMsg *string, - presence string, + presence types.Presence, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool, ) (pos types.StreamPosition, err error) { - presenceStatusID := types.PresenceToInt[presence] if fromSync { stmt := sqlutil.TxStmt(txn, p.upsertPresenceFromSyncStmt) - err = stmt.QueryRowContext(ctx, userID, presenceStatusID, lastActiveTS).Scan(&pos) + err = stmt.QueryRowContext(ctx, userID, presence, lastActiveTS).Scan(&pos) } else { stmt := sqlutil.TxStmt(txn, p.upsertPresenceStmt) - err = stmt.QueryRowContext(ctx, userID, presenceStatusID, statusMsg, lastActiveTS).Scan(&pos) + err = stmt.QueryRowContext(ctx, userID, presence, statusMsg, lastActiveTS).Scan(&pos) } return } @@ -122,14 +121,13 @@ func (p *presenceStatements) UpsertPresence( func (p *presenceStatements) GetPresenceForUser( ctx context.Context, txn *sql.Tx, userID string, -) (*types.Presence, error) { - result := &types.Presence{ +) (*types.PresenceInternal, error) { + result := &types.PresenceInternal{ UserID: userID, } stmt := sqlutil.TxStmt(txn, p.selectPresenceForUsersStmt) - var presenceStatusID int - err := stmt.QueryRowContext(ctx, userID).Scan(&presenceStatusID, &result.ClientFields.StatusMsg, &result.LastActiveTS) - result.ClientFields.Presence = types.PresenceToString[presenceStatusID] + err := stmt.QueryRowContext(ctx, userID).Scan(&result.Presence, &result.ClientFields.StatusMsg, &result.LastActiveTS) + result.ClientFields.Presence = result.Presence.String() return result, err } @@ -143,8 +141,8 @@ func (p *presenceStatements) GetMaxPresenceID(ctx context.Context, txn *sql.Tx) func (p *presenceStatements) GetPresenceAfter( ctx context.Context, txn *sql.Tx, after types.StreamPosition, -) (presences map[string]*types.Presence, err error) { - presences = make(map[string]*types.Presence) +) (presences map[string]*types.PresenceInternal, err error) { + presences = make(map[string]*types.PresenceInternal) stmt := sqlutil.TxStmt(txn, p.selectPresenceAfterStmt) rows, err := stmt.QueryContext(ctx, after) @@ -152,14 +150,13 @@ func (p *presenceStatements) GetPresenceAfter( return nil, err } defer internal.CloseAndLogIfError(ctx, rows, "GetPresenceAfter: failed to close rows") - var presenceStatusID int for rows.Next() { - presence := &types.Presence{} - if err := rows.Scan(&presence.StreamPos, &presence.UserID, &presenceStatusID, &presence.ClientFields.StatusMsg, &presence.LastActiveTS); err != nil { + qryRes := &types.PresenceInternal{} + if err := rows.Scan(&qryRes.StreamPos, &qryRes.UserID, &qryRes.Presence, &qryRes.ClientFields.StatusMsg, &qryRes.LastActiveTS); err != nil { return nil, err } - presence.ClientFields.Presence = types.PresenceToString[presenceStatusID] - presences[presence.UserID] = presence + qryRes.ClientFields.Presence = qryRes.Presence.String() + presences[qryRes.UserID] = qryRes } return presences, rows.Err() } diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 7f47a7e48..7c4786fc6 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -1004,15 +1004,15 @@ func (s *Database) SelectContextAfterEvent(ctx context.Context, id int, roomID s return s.OutputEvents.SelectContextAfterEvent(ctx, nil, id, roomID, filter) } -func (s *Database) UpdatePresence(ctx context.Context, userID, presence string, statusMsg *string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (types.StreamPosition, error) { +func (s *Database) UpdatePresence(ctx context.Context, userID string, presence types.Presence, statusMsg *string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (types.StreamPosition, error) { return s.Presence.UpsertPresence(ctx, nil, userID, statusMsg, presence, lastActiveTS, fromSync) } -func (s *Database) GetPresence(ctx context.Context, userID string) (*types.Presence, error) { +func (s *Database) GetPresence(ctx context.Context, userID string) (*types.PresenceInternal, error) { return s.Presence.GetPresenceForUser(ctx, nil, userID) } -func (s *Database) PresenceAfter(ctx context.Context, after types.StreamPosition) (map[string]*types.Presence, error) { +func (s *Database) PresenceAfter(ctx context.Context, after types.StreamPosition) (map[string]*types.PresenceInternal, error) { return s.Presence.GetPresenceAfter(ctx, nil, after) } diff --git a/syncapi/storage/sqlite3/presence_table.go b/syncapi/storage/sqlite3/presence_table.go index 7510aa903..e7b78a705 100644 --- a/syncapi/storage/sqlite3/presence_table.go +++ b/syncapi/storage/sqlite3/presence_table.go @@ -107,7 +107,7 @@ func (p *presenceStatements) UpsertPresence( txn *sql.Tx, userID string, statusMsg *string, - presence string, + presence types.Presence, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool, ) (pos types.StreamPosition, err error) { @@ -116,19 +116,18 @@ func (p *presenceStatements) UpsertPresence( return pos, err } - presenceStatusID := types.PresenceToInt[presence] if fromSync { stmt := sqlutil.TxStmt(txn, p.upsertPresenceFromSyncStmt) err = stmt.QueryRowContext(ctx, - pos, userID, presenceStatusID, + pos, userID, presence, lastActiveTS, pos, - presenceStatusID, lastActiveTS).Scan(&pos) + presence, lastActiveTS).Scan(&pos) } else { stmt := sqlutil.TxStmt(txn, p.upsertPresenceStmt) err = stmt.QueryRowContext(ctx, - pos, userID, presenceStatusID, + pos, userID, presence, statusMsg, lastActiveTS, pos, - presenceStatusID, statusMsg, lastActiveTS).Scan(&pos) + presence, statusMsg, lastActiveTS).Scan(&pos) } return } @@ -137,14 +136,13 @@ func (p *presenceStatements) UpsertPresence( func (p *presenceStatements) GetPresenceForUser( ctx context.Context, txn *sql.Tx, userID string, -) (*types.Presence, error) { - result := &types.Presence{ +) (*types.PresenceInternal, error) { + result := &types.PresenceInternal{ UserID: userID, } stmt := sqlutil.TxStmt(txn, p.selectPresenceForUsersStmt) - var presenceStatusID int - err := stmt.QueryRowContext(ctx, userID).Scan(&presenceStatusID, &result.ClientFields.StatusMsg, &result.LastActiveTS) - result.ClientFields.Presence = types.PresenceToString[presenceStatusID] + err := stmt.QueryRowContext(ctx, userID).Scan(&result.Presence, &result.ClientFields.StatusMsg, &result.LastActiveTS) + result.ClientFields.Presence = result.Presence.String() return result, err } @@ -158,8 +156,8 @@ func (p *presenceStatements) GetMaxPresenceID(ctx context.Context, txn *sql.Tx) func (p *presenceStatements) GetPresenceAfter( ctx context.Context, txn *sql.Tx, after types.StreamPosition, -) (presences map[string]*types.Presence, err error) { - presences = make(map[string]*types.Presence) +) (presences map[string]*types.PresenceInternal, err error) { + presences = make(map[string]*types.PresenceInternal) stmt := sqlutil.TxStmt(txn, p.selectPresenceAfterStmt) rows, err := stmt.QueryContext(ctx, after) @@ -167,14 +165,13 @@ func (p *presenceStatements) GetPresenceAfter( return nil, err } defer internal.CloseAndLogIfError(ctx, rows, "GetPresenceAfter: failed to close rows") - var presenceStatusID int for rows.Next() { - presence := &types.Presence{} - if err := rows.Scan(&presence.StreamPos, &presence.UserID, &presenceStatusID, &presence.ClientFields.StatusMsg, &presence.LastActiveTS); err != nil { + qryRes := &types.PresenceInternal{} + if err := rows.Scan(&qryRes.StreamPos, &qryRes.UserID, &qryRes.Presence, &qryRes.ClientFields.StatusMsg, &qryRes.LastActiveTS); err != nil { return nil, err } - presence.ClientFields.Presence = types.PresenceToString[presenceStatusID] - presences[presence.UserID] = presence + qryRes.ClientFields.Presence = qryRes.Presence.String() + presences[qryRes.UserID] = qryRes } return presences, rows.Err() } diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 585515328..ef0587bb8 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -184,8 +184,8 @@ type NotificationData interface { } type Presence interface { - UpsertPresence(ctx context.Context, txn *sql.Tx, userID string, statusMsg *string, presence string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (pos types.StreamPosition, err error) - GetPresenceForUser(ctx context.Context, txn *sql.Tx, userID string) (presence *types.Presence, err error) + UpsertPresence(ctx context.Context, txn *sql.Tx, userID string, statusMsg *string, presence types.Presence, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (pos types.StreamPosition, err error) + GetPresenceForUser(ctx context.Context, txn *sql.Tx, userID string) (presence *types.PresenceInternal, err error) GetMaxPresenceID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) - GetPresenceAfter(ctx context.Context, txn *sql.Tx, after types.StreamPosition) (presences map[string]*types.Presence, err error) + GetPresenceAfter(ctx context.Context, txn *sql.Tx, after types.StreamPosition) (presences map[string]*types.PresenceInternal, err error) } diff --git a/syncapi/streams/stream_presence.go b/syncapi/streams/stream_presence.go index f5a5b6074..032a2ce5a 100644 --- a/syncapi/streams/stream_presence.go +++ b/syncapi/streams/stream_presence.go @@ -116,7 +116,7 @@ func (p *PresenceStreamProvider) IncrementalSync( pres, ok := p.cache.Load(cacheKey) if ok { // skip already sent presence - prevPresence := pres.(*types.Presence) + prevPresence := pres.(*types.PresenceInternal) currentlyActive := prevPresence.CurrentlyActive() skip := prevPresence.Equals(presence) && currentlyActive && req.Device.UserID != presence.UserID if skip { diff --git a/syncapi/sync/requestpool.go b/syncapi/sync/requestpool.go index e1004dd7e..4ca6e4563 100644 --- a/syncapi/sync/requestpool.go +++ b/syncapi/sync/requestpool.go @@ -56,7 +56,7 @@ type RequestPool struct { } type PresencePublisher interface { - SendPresence(userID, presence string, statusMsg *string) error + SendPresence(userID string, presence types.Presence, statusMsg *string) error } // NewRequestPool makes a new RequestPool @@ -97,9 +97,9 @@ func (rp *RequestPool) cleanLastSeen() { func (rp *RequestPool) cleanPresence(db storage.Presence, cleanupTime time.Duration) { for { rp.presence.Range(func(key interface{}, v interface{}) bool { - p := v.(types.Presence) + p := v.(types.PresenceInternal) if time.Since(p.LastActiveTS.Time()) > cleanupTime { - rp.updatePresence(db, "unavailable", p.UserID) + rp.updatePresence(db, types.PresenceUnavailable.String(), p.UserID) rp.presence.Delete(key) } return true @@ -114,13 +114,20 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user return } if presence == "" { - presence = "online" + presence = types.PresenceOnline.String() } - newPresence := types.Presence{ + presenceID, ok := types.PresenceFromString(presence) + if !ok { // this should almost never happen + logrus.Errorf("unknown presence '%s'", presence) + return + } + + newPresence := types.PresenceInternal{ ClientFields: types.PresenceClientResponse{ - Presence: presence, + Presence: presenceID.String(), }, + Presence: presenceID, UserID: userID, LastActiveTS: gomatrixserverlib.AsTimestamp(time.Now()), } @@ -128,7 +135,7 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user // avoid spamming presence updates when syncing existingPresence, ok := rp.presence.LoadOrStore(userID, newPresence) if ok { - p := existingPresence.(types.Presence) + p := existingPresence.(types.PresenceInternal) if p.ClientFields.Presence == newPresence.ClientFields.Presence { return } @@ -140,7 +147,7 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user return } - if err := rp.producer.SendPresence(userID, strings.ToLower(presence), dbPresence.ClientFields.StatusMsg); err != nil { + if err := rp.producer.SendPresence(userID, presenceID, dbPresence.ClientFields.StatusMsg); err != nil { logrus.WithError(err).Error("Unable to publish presence message from sync") return } diff --git a/syncapi/sync/requestpool_test.go b/syncapi/sync/requestpool_test.go index f2e89bd99..81022b50d 100644 --- a/syncapi/sync/requestpool_test.go +++ b/syncapi/sync/requestpool_test.go @@ -15,23 +15,23 @@ type dummyPublisher struct { count int } -func (d *dummyPublisher) SendPresence(userID, presence string, statusMsg *string) error { +func (d *dummyPublisher) SendPresence(userID string, presence types.Presence, statusMsg *string) error { d.count++ return nil } type dummyDB struct{} -func (d dummyDB) UpdatePresence(ctx context.Context, userID, presence string, statusMsg *string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (types.StreamPosition, error) { +func (d dummyDB) UpdatePresence(ctx context.Context, userID string, presence types.Presence, statusMsg *string, lastActiveTS gomatrixserverlib.Timestamp, fromSync bool) (types.StreamPosition, error) { return 0, nil } -func (d dummyDB) GetPresence(ctx context.Context, userID string) (*types.Presence, error) { - return &types.Presence{}, nil +func (d dummyDB) GetPresence(ctx context.Context, userID string) (*types.PresenceInternal, error) { + return &types.PresenceInternal{}, nil } -func (d dummyDB) PresenceAfter(ctx context.Context, after types.StreamPosition) (map[string]*types.Presence, error) { - return map[string]*types.Presence{}, nil +func (d dummyDB) PresenceAfter(ctx context.Context, after types.StreamPosition) (map[string]*types.PresenceInternal, error) { + return map[string]*types.PresenceInternal{}, nil } func (d dummyDB) MaxStreamPositionForPresence(ctx context.Context) (types.StreamPosition, error) { diff --git a/syncapi/types/presence.go b/syncapi/types/presence.go new file mode 100644 index 000000000..40aa29cf5 --- /dev/null +++ b/syncapi/types/presence.go @@ -0,0 +1,75 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "strings" + "time" + + "github.com/matrix-org/gomatrixserverlib" +) + +//go:generate stringer -type=Presence -linecomment +type Presence uint8 + +const ( + PresenceUnavailable Presence = iota + 1 // unavailable + PresenceOnline // online + PresenceOffline // offline +) + +// PresenceFromString returns the integer representation of the given input presence. +// Returns false for ok, if input is not a valid presence value. +func PresenceFromString(input string) (p Presence, ok bool) { + for i := 0; i < len(_Presence_index)-1; i++ { + l, r := _Presence_index[i], _Presence_index[i+1] + if strings.EqualFold(input, _Presence_name[l:r]) { + return Presence(i + 1), true + } + } + return 0, false +} + +type PresenceInternal struct { + ClientFields PresenceClientResponse + StreamPos StreamPosition `json:"-"` + UserID string `json:"-"` + LastActiveTS gomatrixserverlib.Timestamp `json:"-"` + Presence Presence `json:"-"` +} + +// Equals compares p1 with p2. +func (p1 *PresenceInternal) Equals(p2 *PresenceInternal) bool { + return p1.ClientFields.Presence == p2.ClientFields.Presence && + p1.ClientFields.StatusMsg == p2.ClientFields.StatusMsg && + p1.UserID == p2.UserID +} + +// CurrentlyActive returns the current active state. +func (p *PresenceInternal) CurrentlyActive() bool { + return time.Since(p.LastActiveTS.Time()).Minutes() < 5 +} + +// LastActiveAgo returns the time since the LastActiveTS in milliseconds. +func (p *PresenceInternal) LastActiveAgo() int64 { + return time.Since(p.LastActiveTS.Time()).Milliseconds() +} + +type PresenceClientResponse struct { + CurrentlyActive *bool `json:"currently_active,omitempty"` + LastActiveAgo int64 `json:"last_active_ago,omitempty"` + Presence string `json:"presence"` + StatusMsg *string `json:"status_msg,omitempty"` +} diff --git a/syncapi/types/presence_string.go b/syncapi/types/presence_string.go new file mode 100644 index 000000000..467b463b8 --- /dev/null +++ b/syncapi/types/presence_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=Presence -linecomment"; DO NOT EDIT. + +package types + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[PresenceUnavailable-1] + _ = x[PresenceOnline-2] + _ = x[PresenceOffline-3] +} + +const _Presence_name = "unavailableonlineoffline" + +var _Presence_index = [...]uint8{0, 11, 17, 24} + +func (i Presence) String() string { + i -= 1 + if i >= Presence(len(_Presence_index)-1) { + return "Presence(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _Presence_name[_Presence_index[i]:_Presence_index[i+1]] +} diff --git a/syncapi/types/presence_test.go b/syncapi/types/presence_test.go new file mode 100644 index 000000000..dbc201c5c --- /dev/null +++ b/syncapi/types/presence_test.go @@ -0,0 +1,42 @@ +package types + +import "testing" + +func TestPresenceFromString(t *testing.T) { + tests := []struct { + name string + input string + wantStatus Presence + wantOk bool + }{ + { + name: "presence unavailable", + input: "unavailable", + wantStatus: PresenceUnavailable, + wantOk: true, + }, + { + name: "presence online", + input: "OnLINE", + wantStatus: PresenceOnline, + wantOk: true, + }, + { + name: "unknown presence", + input: "unknown", + wantStatus: 0, + wantOk: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1 := PresenceFromString(tt.input) + if got != tt.wantStatus { + t.Errorf("PresenceFromString() got = %v, want %v", got, tt.wantStatus) + } + if got1 != tt.wantOk { + t.Errorf("PresenceFromString() got1 = %v, want %v", got1, tt.wantOk) + } + }) + } +} diff --git a/syncapi/types/types.go b/syncapi/types/types.go index 3402efac0..d21203b52 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -20,7 +20,6 @@ import ( "fmt" "strconv" "strings" - "time" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" @@ -519,46 +518,3 @@ type OutputSendToDeviceEvent struct { DeviceID string `json:"device_id"` gomatrixserverlib.SendToDeviceEvent } - -type Presence struct { - ClientFields PresenceClientResponse - StreamPos StreamPosition `json:"-"` - UserID string `json:"-"` - LastActiveTS gomatrixserverlib.Timestamp `json:"-"` -} - -// Equals compares p1 with p2. -func (p1 *Presence) Equals(p2 *Presence) bool { - return p1.ClientFields.Presence == p2.ClientFields.Presence && - p1.ClientFields.StatusMsg == p2.ClientFields.StatusMsg && - p1.UserID == p2.UserID -} - -// CurrentlyActive returns the current active state. -func (p *Presence) CurrentlyActive() bool { - return time.Since(p.LastActiveTS.Time()).Minutes() < 5 -} - -// LastActiveAgo returns the time since the LastActiveTS in milliseconds. -func (p *Presence) LastActiveAgo() int64 { - return time.Since(p.LastActiveTS.Time()).Milliseconds() -} - -type PresenceClientResponse struct { - CurrentlyActive *bool `json:"currently_active,omitempty"` - LastActiveAgo int64 `json:"last_active_ago,omitempty"` - Presence string `json:"presence"` - StatusMsg *string `json:"status_msg,omitempty"` -} - -var PresenceToInt = map[string]int{ - "unavailable": 1, - "online": 2, - "offline": 3, -} - -var PresenceToString = map[int]string{ - 1: "unavailable", - 2: "online", - 3: "offline", -}