github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+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 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.AddStatusCacheSkipClusterSend(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.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 = 0 189 var oldManual bool = false 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 if result := <-a.Srv.Store.Status().Get(userId); result.Err != nil { 357 return nil, result.Err 358 } else { 359 return result.Data.(*model.Status), nil 360 } 361 } 362 363 func (a *App) IsUserAway(lastActivityAt int64) bool { 364 return model.GetMillis()-lastActivityAt >= *a.Config().TeamSettings.UserStatusAwayTimeout*1000 365 }