github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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.ToJson(),
    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.AddStatusCache(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.AddStatusCache(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  func (a *App) SetStatusOnline(userId string, sessionId string, manual bool) {
   165  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   166  		return
   167  	}
   168  
   169  	broadcast := false
   170  
   171  	var oldStatus string = model.STATUS_OFFLINE
   172  	var oldTime int64 = 0
   173  	var oldManual bool = false
   174  	var status *model.Status
   175  	var err *model.AppError
   176  
   177  	if status, err = a.GetStatus(userId); err != nil {
   178  		status = &model.Status{UserId: userId, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
   179  		broadcast = true
   180  	} else {
   181  		if status.Manual && !manual {
   182  			return // manually set status always overrides non-manual one
   183  		}
   184  
   185  		if status.Status != model.STATUS_ONLINE {
   186  			broadcast = true
   187  		}
   188  
   189  		oldStatus = status.Status
   190  		oldTime = status.LastActivityAt
   191  		oldManual = status.Manual
   192  
   193  		status.Status = model.STATUS_ONLINE
   194  		status.Manual = false // for "online" there's no manual setting
   195  		status.LastActivityAt = model.GetMillis()
   196  	}
   197  
   198  	a.AddStatusCache(status)
   199  
   200  	// Only update the database if the status has changed, the status has been manually set,
   201  	// or enough time has passed since the previous action
   202  	if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.STATUS_MIN_UPDATE_TIME {
   203  
   204  		var schan store.StoreChannel
   205  		if broadcast {
   206  			schan = a.Srv.Store.Status().SaveOrUpdate(status)
   207  		} else {
   208  			schan = a.Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt)
   209  		}
   210  
   211  		if result := <-schan; result.Err != nil {
   212  			mlog.Error(fmt.Sprintf("Failed to save status for user_id=%v, err=%v", userId, result.Err), mlog.String("user_id", userId))
   213  		}
   214  	}
   215  
   216  	if broadcast {
   217  		a.BroadcastStatus(status)
   218  	}
   219  }
   220  
   221  func (a *App) BroadcastStatus(status *model.Status) {
   222  	event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
   223  	event.Add("status", status.Status)
   224  	event.Add("user_id", status.UserId)
   225  	a.Publish(event)
   226  }
   227  
   228  func (a *App) SetStatusOffline(userId string, manual bool) {
   229  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   230  		return
   231  	}
   232  
   233  	status, err := a.GetStatus(userId)
   234  	if err == nil && status.Manual && !manual {
   235  		return // manually set status always overrides non-manual one
   236  	}
   237  
   238  	status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
   239  
   240  	a.SaveAndBroadcastStatus(status)
   241  }
   242  
   243  func (a *App) SetStatusAwayIfNeeded(userId string, manual bool) {
   244  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   245  		return
   246  	}
   247  
   248  	status, err := a.GetStatus(userId)
   249  
   250  	if err != nil {
   251  		status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: manual, LastActivityAt: 0, ActiveChannel: ""}
   252  	}
   253  
   254  	if !manual && status.Manual {
   255  		return // manually set status always overrides non-manual one
   256  	}
   257  
   258  	if !manual {
   259  		if status.Status == model.STATUS_AWAY {
   260  			return
   261  		}
   262  
   263  		if !a.IsUserAway(status.LastActivityAt) {
   264  			return
   265  		}
   266  	}
   267  
   268  	status.Status = model.STATUS_AWAY
   269  	status.Manual = manual
   270  	status.ActiveChannel = ""
   271  
   272  	a.SaveAndBroadcastStatus(status)
   273  }
   274  
   275  func (a *App) SetStatusDoNotDisturb(userId string) {
   276  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   277  		return
   278  	}
   279  
   280  	status, err := a.GetStatus(userId)
   281  
   282  	if err != nil {
   283  		status = &model.Status{UserId: userId, Status: model.STATUS_OFFLINE, Manual: false, LastActivityAt: 0, ActiveChannel: ""}
   284  	}
   285  
   286  	status.Status = model.STATUS_DND
   287  	status.Manual = true
   288  
   289  	a.SaveAndBroadcastStatus(status)
   290  }
   291  
   292  func (a *App) SaveAndBroadcastStatus(status *model.Status) *model.AppError {
   293  	a.AddStatusCache(status)
   294  
   295  	if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
   296  		mlog.Error(fmt.Sprintf("Failed to save status for user_id=%v, err=%v", status.UserId, result.Err))
   297  	}
   298  
   299  	event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
   300  	event.Add("status", status.Status)
   301  	event.Add("user_id", status.UserId)
   302  	a.Publish(event)
   303  
   304  	return nil
   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.AddStatusCache(status)
   322  
   323  	if result := <-a.Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
   324  		mlog.Error(fmt.Sprintf("Failed to save status for user_id=%v, err=%v", userId, result.Err), mlog.String("user_id", userId))
   325  	}
   326  
   327  	event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil)
   328  	event.Add("status", model.STATUS_OUT_OF_OFFICE)
   329  	event.Add("user_id", status.UserId)
   330  	a.Publish(event)
   331  }
   332  
   333  func GetStatusFromCache(userId string) *model.Status {
   334  	if result, ok := statusCache.Get(userId); ok {
   335  		status := result.(*model.Status)
   336  		statusCopy := &model.Status{}
   337  		*statusCopy = *status
   338  		return statusCopy
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func (a *App) GetStatus(userId string) (*model.Status, *model.AppError) {
   345  	if !*a.Config().ServiceSettings.EnableUserStatuses {
   346  		return &model.Status{}, nil
   347  	}
   348  
   349  	status := GetStatusFromCache(userId)
   350  	if status != nil {
   351  		return status, nil
   352  	}
   353  
   354  	if result := <-a.Srv.Store.Status().Get(userId); result.Err != nil {
   355  		return nil, result.Err
   356  	} else {
   357  		return result.Data.(*model.Status), nil
   358  	}
   359  }
   360  
   361  func (a *App) IsUserAway(lastActivityAt int64) bool {
   362  	return model.GetMillis()-lastActivityAt >= *a.Config().TeamSettings.UserStatusAwayTimeout*1000
   363  }