github.com/hahmadia/mattermost-server@v5.11.1+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 status := GetStatusFromCache(id) 49 if status != nil { 50 statusMap[id] = status 51 } 52 } 53 } 54 55 return statusMap 56 } 57 58 func (a *App) GetStatusesByIds(userIds []string) (map[string]interface{}, *model.AppError) { 59 if !*a.Config().ServiceSettings.EnableUserStatuses { 60 return map[string]interface{}{}, nil 61 } 62 63 statusMap := map[string]interface{}{} 64 metrics := a.Metrics 65 66 missingUserIds := []string{} 67 for _, userId := range userIds { 68 if result, ok := statusCache.Get(userId); ok { 69 statusMap[userId] = result.(*model.Status).Status 70 if metrics != nil { 71 metrics.IncrementMemCacheHitCounter("Status") 72 } 73 } else { 74 missingUserIds = append(missingUserIds, userId) 75 if metrics != nil { 76 metrics.IncrementMemCacheMissCounter("Status") 77 } 78 } 79 } 80 81 if len(missingUserIds) > 0 { 82 result := <-a.Srv.Store.Status().GetByIds(missingUserIds) 83 if result.Err != nil { 84 return nil, result.Err 85 } 86 statuses := result.Data.([]*model.Status) 87 88 for _, s := range statuses { 89 a.AddStatusCacheSkipClusterSend(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 result := <-a.Srv.Store.Status().GetByIds(missingUserIds) 131 if result.Err != nil { 132 return nil, result.Err 133 } 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 189 var oldManual bool 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 result := <-a.Srv.Store.Status().Get(userId) 357 if result.Err != nil { 358 return nil, result.Err 359 } 360 return result.Data.(*model.Status), nil 361 } 362 363 func (a *App) IsUserAway(lastActivityAt int64) bool { 364 return model.GetMillis()-lastActivityAt >= *a.Config().TeamSettings.UserStatusAwayTimeout*1000 365 }