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 }