github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/status.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"github.com/mattermost/mattermost-server/v5/mlog"
     8  	"github.com/mattermost/mattermost-server/v5/model"
     9  )
    10  
    11  func (a *App) AddStatusCacheSkipClusterSend(status *model.Status) {
    12  	a.Srv().statusCache.Set(status.UserId, status)
    13  }
    14  
    15  func (a *App) AddStatusCache(status *model.Status) {
    16  	a.AddStatusCacheSkipClusterSend(status)
    17  
    18  	if a.Cluster() != nil {
    19  		msg := &model.ClusterMessage{
    20  			Event:    model.CLUSTER_EVENT_UPDATE_STATUS,
    21  			SendType: model.CLUSTER_SEND_BEST_EFFORT,
    22  			Data:     status.ToClusterJson(),
    23  		}
    24  		a.Cluster().SendClusterMessage(msg)
    25  	}
    26  }
    27  
    28  func (a *App) GetAllStatuses() map[string]*model.Status {
    29  	if !*a.Config().ServiceSettings.EnableUserStatuses {
    30  		return map[string]*model.Status{}
    31  	}
    32  
    33  	statusMap := map[string]*model.Status{}
    34  	if userIds, err := a.Srv().statusCache.Keys(); err == nil {
    35  		for _, userId := range userIds {
    36  			status := a.GetStatusFromCache(userId)
    37  			if status != nil {
    38  				statusMap[userId] = status
    39  			}
    40  		}
    41  	}
    42  	return statusMap
    43  }
    44  
    45  func (a *App) GetStatusesByIds(userIds []string) (map[string]interface{}, *model.AppError) {
    46  	if !*a.Config().ServiceSettings.EnableUserStatuses {
    47  		return map[string]interface{}{}, nil
    48  	}
    49  
    50  	statusMap := map[string]interface{}{}
    51  	metrics := a.Metrics()
    52  
    53  	missingUserIds := []string{}
    54  	for _, userId := range userIds {
    55  		var status *model.Status
    56  		if err := a.Srv().statusCache.Get(userId, &status); err == nil {
    57  			statusMap[userId] = status.Status
    58  			if metrics != nil {
    59  				metrics.IncrementMemCacheHitCounter("Status")
    60  			}
    61  		} else {
    62  			missingUserIds = append(missingUserIds, userId)
    63  			if metrics != nil {
    64  				metrics.IncrementMemCacheMissCounter("Status")
    65  			}
    66  		}
    67  	}
    68  
    69  	if len(missingUserIds) > 0 {
    70  		statuses, err := a.Srv().Store.Status().GetByIds(missingUserIds)
    71  		if err != nil {
    72  			return nil, err
    73  		}
    74  
    75  		for _, s := range statuses {
    76  			a.AddStatusCacheSkipClusterSend(s)
    77  			statusMap[s.UserId] = s.Status
    78  		}
    79  
    80  	}
    81  
    82  	// For the case where the user does not have a row in the Status table and cache
    83  	for _, userId := range missingUserIds {
    84  		if _, ok := statusMap[userId]; !ok {
    85  			statusMap[userId] = model.STATUS_OFFLINE
    86  		}
    87  	}
    88  
    89  	return statusMap, nil
    90  }
    91  
    92  //GetUserStatusesByIds used by apiV4
    93  func (a *App) GetUserStatusesByIds(userIds []string) ([]*model.Status, *model.AppError) {
    94  	if !*a.Config().ServiceSettings.EnableUserStatuses {
    95  		return []*model.Status{}, nil
    96  	}
    97  
    98  	var statusMap []*model.Status
    99  	metrics := a.Metrics()
   100  
   101  	missingUserIds := []string{}
   102  	for _, userId := range userIds {
   103  		var status *model.Status
   104  		if err := a.Srv().statusCache.Get(userId, &status); err == nil {
   105  			statusMap = append(statusMap, status)
   106  			if metrics != nil {
   107  				metrics.IncrementMemCacheHitCounter("Status")
   108  			}
   109  		} else {
   110  			missingUserIds = append(missingUserIds, userId)
   111  			if metrics != nil {
   112  				metrics.IncrementMemCacheMissCounter("Status")
   113  			}
   114  		}
   115  	}
   116  
   117  	if len(missingUserIds) > 0 {
   118  		statuses, err := a.Srv().Store.Status().GetByIds(missingUserIds)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  
   123  		for _, s := range statuses {
   124  			a.AddStatusCacheSkipClusterSend(s)
   125  		}
   126  
   127  		statusMap = append(statusMap, statuses...)
   128  
   129  	}
   130  
   131  	// For the case where the user does not have a row in the Status table and cache
   132  	// remove the existing ids from missingUserIds and then create a offline state for the missing ones
   133  	// This also return the status offline for the non-existing Ids in the system
   134  	for i := 0; i < len(missingUserIds); i++ {
   135  		missingUserId := missingUserIds[i]
   136  		for _, userMap := range statusMap {
   137  			if missingUserId == userMap.UserId {
   138  				missingUserIds = append(missingUserIds[:i], missingUserIds[i+1:]...)
   139  				i--
   140  				break
   141  			}
   142  		}
   143  	}
   144  	for _, userId := range missingUserIds {
   145  		statusMap = append(statusMap, &model.Status{UserId: userId, Status: "offline"})
   146  	}
   147  
   148  	return statusMap, nil
   149  }
   150  
   151  // SetStatusLastActivityAt sets the last activity at for a user on the local app server and updates
   152  // status to away if needed. Used by the WS to set status to away if an 'online' device disconnects
   153  // while an 'away' device is still connected
   154  func (a *App) SetStatusLastActivityAt(userId string, activityAt int64) {
   155  	var status *model.Status
   156  	var err *model.AppError
   157  	if status, err = a.GetStatus(userId); err != nil {
   158  		return
   159  	}
   160  
   161  	status.LastActivityAt = activityAt
   162  
   163  	a.AddStatusCacheSkipClusterSend(status)
   164  	a.SetStatusAwayIfNeeded(userId, false)
   165  }
   166  
   167  func (a *App) SetStatusOnline(userId string, manual bool) {
   168  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   169  		return
   170  	}
   171  
   172  	broadcast := false
   173  
   174  	var oldStatus string = model.STATUS_OFFLINE
   175  	var oldTime int64
   176  	var oldManual bool
   177  	var status *model.Status
   178  	var err *model.AppError
   179  
   180  	if status, err = a.GetStatus(userId); err != nil {
   181  		status = &model.Status{UserId: userId, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
   182  		broadcast = true
   183  	} else {
   184  		if status.Manual && !manual {
   185  			return // manually set status always overrides non-manual one
   186  		}
   187  
   188  		if status.Status != model.STATUS_ONLINE {
   189  			broadcast = true
   190  		}
   191  
   192  		oldStatus = status.Status
   193  		oldTime = status.LastActivityAt
   194  		oldManual = status.Manual
   195  
   196  		status.Status = model.STATUS_ONLINE
   197  		status.Manual = false // for "online" there's no manual setting
   198  		status.LastActivityAt = model.GetMillis()
   199  	}
   200  
   201  	a.AddStatusCache(status)
   202  
   203  	// Only update the database if the status has changed, the status has been manually set,
   204  	// or enough time has passed since the previous action
   205  	if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.STATUS_MIN_UPDATE_TIME {
   206  		if broadcast {
   207  			if err := a.Srv().Store.Status().SaveOrUpdate(status); err != nil {
   208  				mlog.Error("Failed to save status", mlog.String("user_id", userId), mlog.Err(err), mlog.String("user_id", userId))
   209  			}
   210  		} else {
   211  			if err := a.Srv().Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt); err != nil {
   212  				mlog.Error("Failed to save status", mlog.String("user_id", userId), mlog.Err(err), mlog.String("user_id", userId))
   213  			}
   214  		}
   215  	}
   216  
   217  	if broadcast {
   218  		a.BroadcastStatus(status)
   219  	}
   220  }
   221  
   222  func (a *App) BroadcastStatus(status *model.Status) {
   223  	if a.Srv().Busy.IsBusy() {
   224  		// this is considered a non-critical service and will be disabled when server busy.
   225  		return
   226  	}
   227  	event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
   228  	event.Add("status", status.Status)
   229  	event.Add("user_id", status.UserId)
   230  	a.Publish(event)
   231  }
   232  
   233  func (a *App) SetStatusOffline(userId string, manual bool) {
   234  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   235  		return
   236  	}
   237  
   238  	status, err := a.GetStatus(userId)
   239  	if err == nil && status.Manual && !manual {
   240  		return // manually set status always overrides non-manual one
   241  	}
   242  
   243  	status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
   244  
   245  	a.SaveAndBroadcastStatus(status)
   246  }
   247  
   248  func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
   249  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   250  		return
   251  	}
   252  
   253  	status, err := a.GetStatus(userId)
   254  
   255  	if err != nil {
   256  		status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: 0, ActiveChannel: ""}
   257  	}
   258  
   259  	if !manual && status.Manual {
   260  		return // manually set status always overrides non-manual one
   261  	}
   262  
   263  	if !manual {
   264  		if status.Status == model.STATUS_AWAY {
   265  			return
   266  		}
   267  
   268  		if !a.IsUserAway(status.LastActivityAt) {
   269  			return
   270  		}
   271  	}
   272  
   273  	status.Status = model.STATUS_AWAY
   274  	status.Manual = manual
   275  	status.ActiveChannel = ""
   276  
   277  	a.SaveAndBroadcastStatus(status)
   278  }
   279  
   280  func (a *App) SetStatusDoNotDisturb(userId string) {
   281  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   282  		return
   283  	}
   284  
   285  	status, err := a.GetStatus(userId)
   286  
   287  	if err != nil {
   288  		status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
   289  	}
   290  
   291  	status.Status = model.STATUS_DND
   292  	status.Manual = true
   293  
   294  	a.SaveAndBroadcastStatus(status)
   295  }
   296  
   297  func (a *App) SaveAndBroadcastStatus(status *model.Status) {
   298  	a.AddStatusCache(status)
   299  
   300  	if err := a.Srv().Store.Status().SaveOrUpdate(status); err != nil {
   301  		mlog.Error("Failed to save status", mlog.String("user_id", status.UserId), mlog.Err(err))
   302  	}
   303  
   304  	a.BroadcastStatus(status)
   305  }
   306  
   307  func (a *App) SetStatusOutOfOffice(userId string) {
   308  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   309  		return
   310  	}
   311  
   312  	status, err := a.GetStatus(userId)
   313  
   314  	if err != nil {
   315  		status = &model.Status{UserId: userId, Status: model.STATUS_OUT_OF_OFFICE, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
   316  	}
   317  
   318  	status.Status = model.STATUS_OUT_OF_OFFICE
   319  	status.Manual = true
   320  
   321  	a.SaveAndBroadcastStatus(status)
   322  }
   323  
   324  func (a *App) GetStatusFromCache(userId string) *model.Status {
   325  	var status *model.Status
   326  	if err := a.Srv().statusCache.Get(userId, &status); err == nil {
   327  		statusCopy := &model.Status{}
   328  		*statusCopy = *status
   329  		return statusCopy
   330  	}
   331  
   332  	return nil
   333  }
   334  
   335  func (a *App) GetStatus(userId string) (*model.Status, *model.AppError) {
   336  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   337  		return &model.Status{}, nil
   338  	}
   339  
   340  	status := a.GetStatusFromCache(userId)
   341  	if status != nil {
   342  		return status, nil
   343  	}
   344  
   345  	return a.Srv().Store.Status().Get(userId)
   346  }
   347  
   348  func (a *App) IsUserAway(lastActivityAt int64) bool {
   349  	return model.GetMillis()-lastActivityAt >= *a.Config().TeamSettings.UserStatusAwayTimeout*1000
   350  }