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