diff --git a/api4/channel.go b/api4/channel.go index 1bc8c1db06..63eefb58af 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -1491,7 +1491,7 @@ func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - c.App.UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) + c.App.Srv().Platform().UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) c.ExtendSessionExpiryIfNeeded(w, r) // Returning {"status": "OK", ...} for backwards compatibility diff --git a/api4/post.go b/api4/post.go index 0d9f369731..ff707c178c 100644 --- a/api4/post.go +++ b/api4/post.go @@ -103,7 +103,7 @@ func createPost(c *Context, w http.ResponseWriter, r *http.Request) { c.App.SetStatusOnline(c.AppContext.Session().UserId, false) } - c.App.UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) + c.App.Srv().Platform().UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) c.ExtendSessionExpiryIfNeeded(w, r) w.WriteHeader(http.StatusCreated) diff --git a/api4/resolver_user.go b/api4/resolver_user.go index a1058054cd..0d962a3fe0 100644 --- a/api4/resolver_user.go +++ b/api4/resolver_user.go @@ -56,7 +56,7 @@ func getGraphQLUser(ctx context.Context, id string) (*user, error) { } } - c.App.UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) + c.App.Srv().Platform().UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) return &user{*usr}, nil } diff --git a/api4/user.go b/api4/user.go index 9d3ca204d2..15af912ab7 100644 --- a/api4/user.go +++ b/api4/user.go @@ -221,7 +221,7 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) { } else { c.App.SanitizeProfile(user, c.IsSystemAdmin()) } - c.App.UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) + c.App.Srv().Platform().UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) w.Header().Set(model.HeaderEtagServer, etag) if err := json.NewEncoder(w).Encode(user); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) @@ -864,7 +864,7 @@ func getUsers(c *Context, w http.ResponseWriter, r *http.Request) { if etag != "" { w.Header().Set(model.HeaderEtagServer, etag) } - c.App.UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) + c.App.Srv().Platform().UpdateLastActivityAtIfNeeded(*c.AppContext.Session()) js, err := json.Marshal(profiles) if err != nil { diff --git a/app/app_iface.go b/app/app_iface.go index 781f12b7e1..c27ea210c1 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -886,7 +886,6 @@ type AppIface interface { IsLeader() bool IsPasswordValid(password string) *model.AppError IsPhase2MigrationCompleted() *model.AppError - IsUserAway(lastActivityAt int64) bool IsUserSignUpAllowed() *model.AppError JoinChannel(c request.CTX, channel *model.Channel, userID string) *model.AppError JoinDefaultChannels(c request.CTX, teamID string, user *model.User, shouldBeAdmin bool, userRequestorId string) *model.AppError @@ -1113,7 +1112,6 @@ type AppIface interface { UpdateHashedPassword(user *model.User, newHashedPassword string) *model.AppError UpdateHashedPasswordByUserId(userID, newHashedPassword string) *model.AppError UpdateIncomingWebhook(oldHook, updatedHook *model.IncomingWebhook) (*model.IncomingWebhook, *model.AppError) - UpdateLastActivityAtIfNeeded(session model.Session) UpdateMfa(c request.CTX, activate bool, userID, token string) *model.AppError UpdateMobileAppBadge(userID string) UpdateOAuthApp(oldApp, updatedApp *model.OAuthApp) (*model.OAuthApp, *model.AppError) diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 1e25574b70..2d60825ebc 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -11941,23 +11941,6 @@ func (a *OpenTracingAppLayer) IsPhase2MigrationCompleted() *model.AppError { return resultVar0 } -func (a *OpenTracingAppLayer) IsUserAway(lastActivityAt int64) bool { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.IsUserAway") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0 := a.app.IsUserAway(lastActivityAt) - - return resultVar0 -} - func (a *OpenTracingAppLayer) IsUserSignUpAllowed() *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.IsUserSignUpAllowed") @@ -17315,21 +17298,6 @@ func (a *OpenTracingAppLayer) UpdateIncomingWebhook(oldHook *model.IncomingWebho return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) UpdateLastActivityAtIfNeeded(session model.Session) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateLastActivityAtIfNeeded") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - a.app.UpdateLastActivityAtIfNeeded(session) -} - func (a *OpenTracingAppLayer) UpdateMfa(c request.CTX, activate bool, userID string, token string) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateMfa") diff --git a/app/platform/helper_test.go b/app/platform/helper_test.go index 9402f8def3..5b1a44d344 100644 --- a/app/platform/helper_test.go +++ b/app/platform/helper_test.go @@ -181,7 +181,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo th.Service.SetLicense(nil) } - err = th.Service.Start(th.Suite) + err = th.Service.Start() if err != nil { panic(err) } diff --git a/app/platform/mocks/SuiteIFace.go b/app/platform/mocks/SuiteIFace.go index 6b337a8059..b919999f6e 100644 --- a/app/platform/mocks/SuiteIFace.go +++ b/app/platform/mocks/SuiteIFace.go @@ -39,20 +39,6 @@ func (_m *SuiteIFace) GetSession(token string) (*model.Session, *model.AppError) return r0, r1 } -// IsUserAway provides a mock function with given fields: lastActivityAt -func (_m *SuiteIFace) IsUserAway(lastActivityAt int64) bool { - ret := _m.Called(lastActivityAt) - - var r0 bool - if rf, ok := ret.Get(0).(func(int64) bool); ok { - r0 = rf(lastActivityAt) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - // RolesGrantPermission provides a mock function with given fields: roleNames, permissionId func (_m *SuiteIFace) RolesGrantPermission(roleNames []string, permissionId string) bool { ret := _m.Called(roleNames, permissionId) @@ -67,31 +53,6 @@ func (_m *SuiteIFace) RolesGrantPermission(roleNames []string, permissionId stri return r0 } -// SetStatusAwayIfNeeded provides a mock function with given fields: userID, manual -func (_m *SuiteIFace) SetStatusAwayIfNeeded(userID string, manual bool) { - _m.Called(userID, manual) -} - -// SetStatusLastActivityAt provides a mock function with given fields: userID, activityAt -func (_m *SuiteIFace) SetStatusLastActivityAt(userID string, activityAt int64) { - _m.Called(userID, activityAt) -} - -// SetStatusOffline provides a mock function with given fields: userID, manual -func (_m *SuiteIFace) SetStatusOffline(userID string, manual bool) { - _m.Called(userID, manual) -} - -// SetStatusOnline provides a mock function with given fields: userID, manual -func (_m *SuiteIFace) SetStatusOnline(userID string, manual bool) { - _m.Called(userID, manual) -} - -// UpdateLastActivityAtIfNeeded provides a mock function with given fields: session -func (_m *SuiteIFace) UpdateLastActivityAtIfNeeded(session model.Session) { - _m.Called(session) -} - // UserCanSeeOtherUser provides a mock function with given fields: userID, otherUserId func (_m *SuiteIFace) UserCanSeeOtherUser(userID string, otherUserId string) (bool, *model.AppError) { ret := _m.Called(userID, otherUserId) diff --git a/app/platform/service.go b/app/platform/service.go index be3f4ed805..b2f02c2c77 100644 --- a/app/platform/service.go +++ b/app/platform/service.go @@ -304,8 +304,8 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { return ps, nil } -func (ps *PlatformService) Start(suite SuiteIFace) error { - ps.hubStart(suite) +func (ps *PlatformService) Start() error { + ps.hubStart() ps.configListenerId = ps.AddConfigListener(func(_, _ *model.Config) { ps.regenerateClientConfig() diff --git a/app/platform/status.go b/app/platform/status.go index b133d3e2f7..70074d5d0f 100644 --- a/app/platform/status.go +++ b/app/platform/status.go @@ -212,3 +212,198 @@ func (ps *PlatformService) GetStatus(userID string) (*model.Status, *model.AppEr return status, nil } + +// SetStatusLastActivityAt sets the last activity at for a user on the local app server and updates +// status to away if needed. Used by the WS to set status to away if an 'online' device disconnects +// while an 'away' device is still connected +func (ps *PlatformService) SetStatusLastActivityAt(userID string, activityAt int64) { + var status *model.Status + var err *model.AppError + if status, err = ps.GetStatus(userID); err != nil { + return + } + + status.LastActivityAt = activityAt + + ps.AddStatusCacheSkipClusterSend(status) + ps.SetStatusAwayIfNeeded(userID, false) +} + +func (ps *PlatformService) UpdateLastActivityAtIfNeeded(session model.Session) { + now := model.GetMillis() + + ps.UpdateWebConnUserActivity(session, now) + + if now-session.LastActivityAt < model.SessionActivityTimeout { + return + } + + if err := ps.Store.Session().UpdateLastActivityAt(session.Id, now); err != nil { + mlog.Warn("Failed to update LastActivityAt", mlog.String("user_id", session.UserId), mlog.String("session_id", session.Id), mlog.Err(err)) + } + + session.LastActivityAt = now + ps.AddSessionToCache(&session) +} + +func (ps *PlatformService) SetStatusOnline(userID string, manual bool) { + if !*ps.Config().ServiceSettings.EnableUserStatuses { + return + } + + broadcast := false + + var oldStatus string = model.StatusOffline + var oldTime int64 + var oldManual bool + var status *model.Status + var err *model.AppError + + if status, err = ps.GetStatus(userID); err != nil { + status = &model.Status{UserId: userID, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} + broadcast = true + } else { + if status.Manual && !manual { + return // manually set status always overrides non-manual one + } + + if status.Status != model.StatusOnline { + broadcast = true + } + + oldStatus = status.Status + oldTime = status.LastActivityAt + oldManual = status.Manual + + status.Status = model.StatusOnline + status.Manual = false // for "online" there's no manual setting + status.LastActivityAt = model.GetMillis() + } + + ps.AddStatusCache(status) + + // Only update the database if the status has changed, the status has been manually set, + // or enough time has passed since the previous action + if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.StatusMinUpdateTime { + if broadcast { + if err := ps.Store.Status().SaveOrUpdate(status); err != nil { + mlog.Warn("Failed to save status", mlog.String("user_id", userID), mlog.Err(err), mlog.String("user_id", userID)) + } + } else { + if err := ps.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt); err != nil { + mlog.Error("Failed to save status", mlog.String("user_id", userID), mlog.Err(err), mlog.String("user_id", userID)) + } + } + } + + if broadcast { + ps.BroadcastStatus(status) + } +} + +func (ps *PlatformService) SetStatusOffline(userID string, manual bool) { + if !*ps.Config().ServiceSettings.EnableUserStatuses { + return + } + + status, err := ps.GetStatus(userID) + if err == nil && status.Manual && !manual { + return // manually set status always overrides non-manual one + } + + status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""} + + ps.SaveAndBroadcastStatus(status) +} + +func (ps *PlatformService) SetStatusAwayIfNeeded(userID string, manual bool) { + if !*ps.Config().ServiceSettings.EnableUserStatuses { + return + } + + status, err := ps.GetStatus(userID) + + if err != nil { + status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: manual, LastActivityAt: 0, ActiveChannel: ""} + } + + if !manual && status.Manual { + return // manually set status always overrides non-manual one + } + + if !manual { + if status.Status == model.StatusAway { + return + } + + if !ps.isUserAway(status.LastActivityAt) { + return + } + } + + status.Status = model.StatusAway + status.Manual = manual + status.ActiveChannel = "" + + ps.SaveAndBroadcastStatus(status) +} + +// SetStatusDoNotDisturbTimed takes endtime in unix epoch format in UTC +// and sets status of given userId to dnd which will be restored back after endtime +func (ps *PlatformService) SetStatusDoNotDisturbTimed(userId string, endtime int64) { + if !*ps.Config().ServiceSettings.EnableUserStatuses { + return + } + + status, err := ps.GetStatus(userId) + + if err != nil { + status = &model.Status{UserId: userId, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""} + } + + status.PrevStatus = status.Status + status.Status = model.StatusDnd + status.Manual = true + + status.DNDEndTime = endtime + + ps.SaveAndBroadcastStatus(status) +} + +func (ps *PlatformService) SetStatusDoNotDisturb(userID string) { + if !*ps.Config().ServiceSettings.EnableUserStatuses { + return + } + + status, err := ps.GetStatus(userID) + + if err != nil { + status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""} + } + + status.Status = model.StatusDnd + status.Manual = true + + ps.SaveAndBroadcastStatus(status) +} + +func (ps *PlatformService) SetStatusOutOfOffice(userID string) { + if !*ps.Config().ServiceSettings.EnableUserStatuses { + return + } + + status, err := ps.GetStatus(userID) + + if err != nil { + status = &model.Status{UserId: userID, Status: model.StatusOutOfOffice, Manual: false, LastActivityAt: 0, ActiveChannel: ""} + } + + status.Status = model.StatusOutOfOffice + status.Manual = true + + ps.SaveAndBroadcastStatus(status) +} + +func (ps *PlatformService) isUserAway(lastActivityAt int64) bool { + return model.GetMillis()-lastActivityAt >= *ps.Config().TeamSettings.UserStatusAwayTimeout*1000 +} diff --git a/app/platform/web_conn.go b/app/platform/web_conn.go index 4956d49a73..312221da50 100644 --- a/app/platform/web_conn.go +++ b/app/platform/web_conn.go @@ -165,8 +165,8 @@ func (ps *PlatformService) PopulateWebConnConfig(s *model.Session, cfg *WebConnC func (ps *PlatformService) NewWebConn(cfg *WebConnConfig, suite SuiteIFace, runner HookRunner) *WebConn { if cfg.Session.UserId != "" { ps.Go(func() { - suite.SetStatusOnline(cfg.Session.UserId, false) - suite.UpdateLastActivityAtIfNeeded(cfg.Session) + ps.SetStatusOnline(cfg.Session.UserId, false) + ps.UpdateLastActivityAtIfNeeded(cfg.Session) }) } @@ -344,7 +344,7 @@ func (wc *WebConn) readPump() { } if wc.IsAuthenticated() { wc.Platform.Go(func() { - wc.Suite.SetStatusAwayIfNeeded(wc.UserId, false) + wc.Platform.SetStatusAwayIfNeeded(wc.UserId, false) }) } return nil diff --git a/app/platform/web_hub.go b/app/platform/web_hub.go index dc83d574ce..9e62da4264 100644 --- a/app/platform/web_hub.go +++ b/app/platform/web_hub.go @@ -21,12 +21,6 @@ const ( ) type SuiteIFace interface { - SetStatusLastActivityAt(userID string, activityAt int64) - SetStatusOffline(userID string, manual bool) - IsUserAway(lastActivityAt int64) bool - SetStatusOnline(userID string, manual bool) - UpdateLastActivityAtIfNeeded(session model.Session) - SetStatusAwayIfNeeded(userID string, manual bool) GetSession(token string) (*model.Session, *model.AppError) RolesGrantPermission(roleNames []string, permissionId string) bool UserCanSeeOtherUser(userID string, otherUserId string) (bool, *model.AppError) @@ -95,7 +89,7 @@ func newWebHub(ps *PlatformService) *Hub { } // hubStart starts all the hubs. -func (ps *PlatformService) hubStart(suite SuiteIFace) { +func (ps *PlatformService) hubStart() { // Total number of hubs is twice the number of CPUs. numberOfHubs := runtime.NumCPU() * 2 ps.logger.Info("Starting websocket hubs", mlog.Int("number_of_hubs", numberOfHubs)) @@ -105,7 +99,7 @@ func (ps *PlatformService) hubStart(suite SuiteIFace) { for i := 0; i < numberOfHubs; i++ { hubs[i] = newWebHub(ps) hubs[i].connectionIndex = i - hubs[i].Start(suite) + hubs[i].Start() } // Assigning to the hubs slice without any mutex is fine because it is only assigned once // during the start of the program and always read from after that. @@ -366,7 +360,7 @@ func (h *Hub) Stop() { } // Start starts the hub. -func (h *Hub) Start(suite SuiteIFace) { +func (h *Hub) Start() { var doStart func() var doRecoverableStart func() var doRecover func() @@ -439,7 +433,7 @@ func (h *Hub) Start(suite SuiteIFace) { conns := connIndex.ForUser(webConn.UserId) if len(conns) == 0 || areAllInactive(conns) { h.platform.Go(func() { - suite.SetStatusOffline(webConn.UserId, false) + h.platform.SetStatusOffline(webConn.UserId, false) }) continue } @@ -453,9 +447,9 @@ func (h *Hub) Start(suite SuiteIFace) { } } - if suite.IsUserAway(latestActivity) { + if h.platform.isUserAway(latestActivity) { h.platform.Go(func() { - suite.SetStatusLastActivityAt(webConn.UserId, latestActivity) + h.platform.SetStatusLastActivityAt(webConn.UserId, latestActivity) }) } case userID := <-h.invalidateUser: @@ -522,7 +516,7 @@ func (h *Hub) Start(suite SuiteIFace) { case <-h.stop: for webConn := range connIndex.All() { webConn.Close() - suite.SetStatusOffline(webConn.UserId, false) + h.platform.SetStatusOffline(webConn.UserId, false) } h.explicitStop = true diff --git a/app/platform/web_hub_test.go b/app/platform/web_hub_test.go index e3f6e4ddbe..5092e6cb0f 100644 --- a/app/platform/web_hub_test.go +++ b/app/platform/web_hub_test.go @@ -67,7 +67,7 @@ func TestHubStopWithMultipleConnections(t *testing.T) { }) require.NoError(t, err) - th.Service.Start(th.Suite) + th.Service.Start() wc1 := registerDummyWebConn(t, th, s.Listener.Addr(), session) wc2 := registerDummyWebConn(t, th, s.Listener.Addr(), session) wc3 := registerDummyWebConn(t, th, s.Listener.Addr(), session) @@ -90,7 +90,7 @@ func TestHubStopRaceCondition(t *testing.T) { }) require.NoError(t, err) - th.Service.Start(th.Suite) + th.Service.Start() wc1 := registerDummyWebConn(t, th, s.Listener.Addr(), session) defer wc1.Close() @@ -467,18 +467,13 @@ func TestHubIsRegistered(t *testing.T) { require.NoError(t, err) mockSuite := &platform_mocks.SuiteIFace{} - mockSuite.On("SetStatusOnline", th.BasicUser.Id, false).Return() - mockSuite.On("UpdateLastActivityAtIfNeeded", *session).Return() mockSuite.On("GetSession", session.Token).Return(session, nil) - mockSuite.On("IsUserAway", mock.Anything).Return(false) - mockSuite.On("SetStatusOffline", th.BasicUser.Id, false).Return() - th.Suite = mockSuite s := httptest.NewServer(dummyWebsocketHandler(t)) defer s.Close() - th.Service.Start(th.Suite) + th.Service.Start() wc1 := registerDummyWebConn(t, th, s.Listener.Addr(), session) wc2 := registerDummyWebConn(t, th, s.Listener.Addr(), session) wc3 := registerDummyWebConn(t, th, s.Listener.Addr(), session) @@ -486,9 +481,7 @@ func TestHubIsRegistered(t *testing.T) { defer wc2.Close() defer wc3.Close() - session1 := wc1.session.Load().(*model.Session) - - assert.True(t, th.Service.SessionIsRegistered(*session1)) + assert.True(t, th.Service.SessionIsRegistered(*wc1.session.Load().(*model.Session))) assert.True(t, th.Service.SessionIsRegistered(*wc2.session.Load().(*model.Session))) assert.True(t, th.Service.SessionIsRegistered(*wc3.session.Load().(*model.Session))) @@ -551,7 +544,7 @@ func BenchmarkGetHubForUserId(b *testing.B) { th := Setup(b).InitBasic() defer th.TearDown() - th.Service.Start(th.Suite) + th.Service.Start() b.ResetTimer() for i := 0; i < b.N; i++ { diff --git a/app/platform/websocket_router.go b/app/platform/websocket_router.go index 2b465dffb6..862f220dfb 100644 --- a/app/platform/websocket_router.go +++ b/app/platform/websocket_router.go @@ -59,8 +59,8 @@ func (wr *WebSocketRouter) ServeWebSocket(conn *WebConn, r *model.WebSocketReque conn.Platform.HubRegister(conn) conn.Platform.Go(func() { - conn.Suite.SetStatusOnline(session.UserId, false) - conn.Suite.UpdateLastActivityAtIfNeeded(*session) + conn.Platform.SetStatusOnline(session.UserId, false) + conn.Platform.UpdateLastActivityAtIfNeeded(*session) }) resp := model.NewWebSocketResponse(model.StatusOk, r.Seq, nil) diff --git a/app/server.go b/app/server.go index a5e01db9b7..80b252caff 100644 --- a/app/server.go +++ b/app/server.go @@ -287,7 +287,7 @@ func NewServer(options ...Option) (*Server, error) { // It is important to initialize the hub only after the global logger is set // to avoid race conditions while logging from inside the hub. // Step 5: Start hub in platform which the hub depends on s.Channels() (step 4) - s.platform.Start(New(ServerConnector(s.Channels()))) + s.platform.Start() // ------------------------------------------------------------------------- // Everything below this is not order sensitive and safe to be moved around. diff --git a/app/session.go b/app/session.go index 86ed75166b..5b69258baf 100644 --- a/app/session.go +++ b/app/session.go @@ -235,23 +235,6 @@ func (a *App) AttachDeviceId(sessionID string, deviceID string, expiresAt int64) return nil } -func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) { - now := model.GetMillis() - - a.UpdateWebConnUserActivity(session, now) - - if now-session.LastActivityAt < model.SessionActivityTimeout { - return - } - - if err := a.Srv().Store().Session().UpdateLastActivityAt(session.Id, now); err != nil { - mlog.Warn("Failed to update LastActivityAt", mlog.String("user_id", session.UserId), mlog.String("session_id", session.Id), mlog.Err(err)) - } - - session.LastActivityAt = now - a.ch.srv.platform.AddSessionToCache(&session) -} - // ExtendSessionExpiryIfNeeded extends Session.ExpiresAt based on session lengths in config. // A new ExpiresAt is only written if enough time has elapsed since last update. // Returns true only if the session was extended. diff --git a/app/status.go b/app/status.go index cdbd0759de..ecf17cedcc 100644 --- a/app/status.go +++ b/app/status.go @@ -21,174 +21,33 @@ func (a *App) GetUserStatusesByIds(userIDs []string) ([]*model.Status, *model.Ap // status to away if needed. Used by the WS to set status to away if an 'online' device disconnects // while an 'away' device is still connected func (a *App) SetStatusLastActivityAt(userID string, activityAt int64) { - var status *model.Status - var err *model.AppError - if status, err = a.GetStatus(userID); err != nil { - return - } - - status.LastActivityAt = activityAt - - a.Srv().Platform().AddStatusCacheSkipClusterSend(status) - a.SetStatusAwayIfNeeded(userID, false) + a.Srv().Platform().SetStatusLastActivityAt(userID, activityAt) } func (a *App) SetStatusOnline(userID string, manual bool) { - if !*a.Config().ServiceSettings.EnableUserStatuses { - return - } - - broadcast := false - - var oldStatus string = model.StatusOffline - var oldTime int64 - var oldManual bool - var status *model.Status - var err *model.AppError - - if status, err = a.GetStatus(userID); err != nil { - status = &model.Status{UserId: userID, Status: model.StatusOnline, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""} - broadcast = true - } else { - if status.Manual && !manual { - return // manually set status always overrides non-manual one - } - - if status.Status != model.StatusOnline { - broadcast = true - } - - oldStatus = status.Status - oldTime = status.LastActivityAt - oldManual = status.Manual - - status.Status = model.StatusOnline - status.Manual = false // for "online" there's no manual setting - status.LastActivityAt = model.GetMillis() - } - - a.Srv().Platform().AddStatusCache(status) - - // Only update the database if the status has changed, the status has been manually set, - // or enough time has passed since the previous action - if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.StatusMinUpdateTime { - if broadcast { - if err := a.Srv().Store().Status().SaveOrUpdate(status); err != nil { - mlog.Warn("Failed to save status", mlog.String("user_id", userID), mlog.Err(err), mlog.String("user_id", userID)) - } - } else { - if err := a.Srv().Store().Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt); err != nil { - mlog.Error("Failed to save status", mlog.String("user_id", userID), mlog.Err(err), mlog.String("user_id", userID)) - } - } - } - - if broadcast { - a.Srv().Platform().BroadcastStatus(status) - } + a.Srv().Platform().SetStatusOnline(userID, manual) } func (a *App) SetStatusOffline(userID string, manual bool) { - if !*a.Config().ServiceSettings.EnableUserStatuses { - return - } - - status, err := a.GetStatus(userID) - if err == nil && status.Manual && !manual { - return // manually set status always overrides non-manual one - } - - status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""} - - a.Srv().Platform().SaveAndBroadcastStatus(status) + a.Srv().Platform().SetStatusOffline(userID, manual) } func (a *App) SetStatusAwayIfNeeded(userID string, manual bool) { - if !*a.Config().ServiceSettings.EnableUserStatuses { - return - } - - status, err := a.GetStatus(userID) - - if err != nil { - status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: manual, LastActivityAt: 0, ActiveChannel: ""} - } - - if !manual && status.Manual { - return // manually set status always overrides non-manual one - } - - if !manual { - if status.Status == model.StatusAway { - return - } - - if !a.IsUserAway(status.LastActivityAt) { - return - } - } - - status.Status = model.StatusAway - status.Manual = manual - status.ActiveChannel = "" - - a.Srv().Platform().SaveAndBroadcastStatus(status) + a.Srv().Platform().SetStatusAwayIfNeeded(userID, manual) } // SetStatusDoNotDisturbTimed takes endtime in unix epoch format in UTC // and sets status of given userId to dnd which will be restored back after endtime func (a *App) SetStatusDoNotDisturbTimed(userId string, endtime int64) { - if !*a.Config().ServiceSettings.EnableUserStatuses { - return - } - - status, err := a.GetStatus(userId) - - if err != nil { - status = &model.Status{UserId: userId, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""} - } - - status.PrevStatus = status.Status - status.Status = model.StatusDnd - status.Manual = true - - status.DNDEndTime = endtime - - a.Srv().Platform().SaveAndBroadcastStatus(status) + a.Srv().Platform().SetStatusDoNotDisturbTimed(userId, endtime) } func (a *App) SetStatusDoNotDisturb(userID string) { - if !*a.Config().ServiceSettings.EnableUserStatuses { - return - } - - status, err := a.GetStatus(userID) - - if err != nil { - status = &model.Status{UserId: userID, Status: model.StatusOffline, Manual: false, LastActivityAt: 0, ActiveChannel: ""} - } - - status.Status = model.StatusDnd - status.Manual = true - - a.Srv().Platform().SaveAndBroadcastStatus(status) + a.Srv().Platform().SetStatusDoNotDisturb(userID) } func (a *App) SetStatusOutOfOffice(userID string) { - if !*a.Config().ServiceSettings.EnableUserStatuses { - return - } - - status, err := a.GetStatus(userID) - - if err != nil { - status = &model.Status{UserId: userID, Status: model.StatusOutOfOffice, Manual: false, LastActivityAt: 0, ActiveChannel: ""} - } - - status.Status = model.StatusOutOfOffice - status.Manual = true - - a.Srv().Platform().SaveAndBroadcastStatus(status) + a.Srv().Platform().SetStatusOutOfOffice(userID) } func (a *App) GetStatusFromCache(userID string) *model.Status { @@ -199,10 +58,6 @@ func (a *App) GetStatus(userID string) (*model.Status, *model.AppError) { return a.Srv().Platform().GetStatus(userID) } -func (a *App) IsUserAway(lastActivityAt int64) bool { - return model.GetMillis()-lastActivityAt >= *a.Config().TeamSettings.UserStatusAwayTimeout*1000 -} - // UpdateDNDStatusOfUsers is a recurring task which is started when server starts // which unsets dnd status of users if needed and saves and broadcasts it func (a *App) UpdateDNDStatusOfUsers() {