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