From 97558f6a6ec4c53fa69035fb430ead209d9c222d Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 13 Jan 2017 13:53:37 -0500 Subject: PLT-4938 Add app package and move logic over from api package (#4931) * Add app package and move logic over from api package * Change app package functions to return errors * Move non-api tests into app package * Fix merge --- app/status.go | 255 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 app/status.go (limited to 'app/status.go') diff --git a/app/status.go b/app/status.go new file mode 100644 index 000000000..98cdb0dc0 --- /dev/null +++ b/app/status.go @@ -0,0 +1,255 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + l4g "github.com/alecthomas/log4go" + + "github.com/mattermost/platform/einterfaces" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" + "github.com/mattermost/platform/utils" +) + +var statusCache *utils.Cache = utils.NewLru(model.STATUS_CACHE_SIZE) + +func ClearStatusCache() { + statusCache.Purge() +} + +func AddStatusCacheSkipClusterSend(status *model.Status) { + statusCache.Add(status.UserId, status) +} + +func AddStatusCache(status *model.Status) { + AddStatusCacheSkipClusterSend(status) + + if einterfaces.GetClusterInterface() != nil { + einterfaces.GetClusterInterface().UpdateStatus(status) + } +} + +func GetAllStatuses() map[string]*model.Status { + userIds := statusCache.Keys() + statusMap := map[string]*model.Status{} + + for _, userId := range userIds { + if id, ok := userId.(string); !ok { + continue + } else { + status := GetStatusFromCache(id) + if status != nil { + statusMap[id] = status + } + } + } + + return statusMap +} + +func GetStatusesByIds(userIds []string) (map[string]interface{}, *model.AppError) { + statusMap := map[string]interface{}{} + metrics := einterfaces.GetMetricsInterface() + + missingUserIds := []string{} + for _, userId := range userIds { + if result, ok := statusCache.Get(userId); ok { + statusMap[userId] = result.(*model.Status).Status + if metrics != nil { + metrics.IncrementMemCacheHitCounter("Status") + } + } else { + missingUserIds = append(missingUserIds, userId) + if metrics != nil { + metrics.IncrementMemCacheMissCounter("Status") + } + } + } + + if len(missingUserIds) > 0 { + if result := <-Srv.Store.Status().GetByIds(missingUserIds); result.Err != nil { + return nil, result.Err + } else { + statuses := result.Data.([]*model.Status) + + for _, s := range statuses { + AddStatusCache(s) + statusMap[s.UserId] = s.Status + } + } + } + + // For the case where the user does not have a row in the Status table and cache + for _, userId := range missingUserIds { + if _, ok := statusMap[userId]; !ok { + statusMap[userId] = model.STATUS_OFFLINE + } + } + + return statusMap, nil +} + +func SetStatusOnline(userId string, sessionId string, manual bool) { + broadcast := false + + var oldStatus string = model.STATUS_OFFLINE + var oldTime int64 = 0 + var oldManual bool = false + var status *model.Status + var err *model.AppError + + if status, err = GetStatus(userId); err != nil { + status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), ""} + broadcast = true + } else { + if status.Manual && !manual { + return // manually set status always overrides non-manual one + } + + if status.Status != model.STATUS_ONLINE { + broadcast = true + } + + oldStatus = status.Status + oldTime = status.LastActivityAt + oldManual = status.Manual + + status.Status = model.STATUS_ONLINE + status.Manual = false // for "online" there's no manual setting + status.LastActivityAt = model.GetMillis() + } + + AddStatusCache(status) + + // Only update the database if the status has changed, the status has been manually set, + // or enough time has passed since the previous action + if status.Status != oldStatus || status.Manual != oldManual || status.LastActivityAt-oldTime > model.STATUS_MIN_UPDATE_TIME { + achan := Srv.Store.Session().UpdateLastActivityAt(sessionId, status.LastActivityAt) + + var schan store.StoreChannel + if broadcast { + schan = Srv.Store.Status().SaveOrUpdate(status) + } else { + schan = Srv.Store.Status().UpdateLastActivityAt(status.UserId, status.LastActivityAt) + } + + if result := <-achan; result.Err != nil { + l4g.Error(utils.T("api.status.last_activity.error"), userId, sessionId, result.Err) + } + + if result := <-schan; result.Err != nil { + l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err) + } + } + + if broadcast { + event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) + event.Add("status", model.STATUS_ONLINE) + event.Add("user_id", status.UserId) + go Publish(event) + } +} + +func SetStatusOffline(userId string, manual bool) { + status, err := GetStatus(userId) + if err == nil && status.Manual && !manual { + return // manually set status always overrides non-manual one + } + + status = &model.Status{userId, model.STATUS_OFFLINE, manual, model.GetMillis(), ""} + + AddStatusCache(status) + + if result := <-Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { + l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err) + } + + event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) + event.Add("status", model.STATUS_OFFLINE) + event.Add("user_id", status.UserId) + go Publish(event) +} + +func SetStatusAwayIfNeeded(userId string, manual bool) { + status, err := GetStatus(userId) + + if err != nil { + status = &model.Status{userId, model.STATUS_OFFLINE, manual, 0, ""} + } + + if !manual && status.Manual { + return // manually set status always overrides non-manual one + } + + if !manual { + if status.Status == model.STATUS_AWAY { + return + } + + if !IsUserAway(status.LastActivityAt) { + return + } + } + + status.Status = model.STATUS_AWAY + status.Manual = manual + status.ActiveChannel = "" + + AddStatusCache(status) + + if result := <-Srv.Store.Status().SaveOrUpdate(status); result.Err != nil { + l4g.Error(utils.T("api.status.save_status.error"), userId, result.Err) + } + + event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_STATUS_CHANGE, "", "", status.UserId, nil) + event.Add("status", model.STATUS_AWAY) + event.Add("user_id", status.UserId) + go Publish(event) +} + +func GetStatusFromCache(userId string) *model.Status { + if result, ok := statusCache.Get(userId); ok { + status := result.(*model.Status) + statusCopy := &model.Status{} + *statusCopy = *status + return statusCopy + } + + return nil +} + +func GetStatus(userId string) (*model.Status, *model.AppError) { + status := GetStatusFromCache(userId) + if status != nil { + return status, nil + } + + if result := <-Srv.Store.Status().Get(userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Status), nil + } +} + +func IsUserAway(lastActivityAt int64) bool { + return model.GetMillis()-lastActivityAt >= *utils.Cfg.TeamSettings.UserStatusAwayTimeout*1000 +} + +func DoesStatusAllowPushNotification(user *model.User, status *model.Status, channelId string) bool { + props := user.NotifyProps + + if props["push"] == "none" { + return false + } + + if pushStatus, ok := props["push_status"]; (pushStatus == model.STATUS_ONLINE || !ok) && (status.ActiveChannel != channelId || model.GetMillis()-status.LastActivityAt > model.STATUS_CHANNEL_TIMEOUT) { + return true + } else if pushStatus == model.STATUS_AWAY && (status.Status == model.STATUS_AWAY || status.Status == model.STATUS_OFFLINE) { + return true + } else if pushStatus == model.STATUS_OFFLINE && status.Status == model.STATUS_OFFLINE { + return true + } + + return false +} -- cgit v1.2.3-1-g7c22