Minimize app dependency on platform service (#21548)

Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2022-12-07 20:31:03 +03:00
коммит произвёл GitHub
родитель 91ee9ed2eb
Коммит d8e2859b0b
17 изменённых файлов: 228 добавлений и 281 удалений

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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")

Просмотреть файл

@@ -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)
}

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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()

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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++ {

Просмотреть файл

@@ -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)

Просмотреть файл

@@ -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.

Просмотреть файл

@@ -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.

Просмотреть файл

@@ -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() {