github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+incompatible/app/status.go (about)

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