From 19a48acfc60ffa3500360474362bb5c52c12f278 Mon Sep 17 00:00:00 2001 From: nickago Date: Mon, 17 Aug 2015 13:13:27 -0700 Subject: Created a /echo command that will post on the user's behalf after a specified amount of time --- api/command.go | 101 +++++++++++++++++++++---------------------------------- model/command.go | 2 +- 2 files changed, 39 insertions(+), 64 deletions(-) diff --git a/api/command.go b/api/command.go index f051bd42e..31f676a11 100644 --- a/api/command.go +++ b/api/command.go @@ -4,15 +4,15 @@ package api import ( + "net/http" + "strconv" + "strings" + "time" + l4g "code.google.com/p/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" - "net/http" - "reflect" - "runtime" - "strconv" - "strings" ) type commandHandler func(c *Context, command *model.Command) bool @@ -41,7 +41,6 @@ func command(c *Context, w http.ResponseWriter, r *http.Request) { } checkCommand(c, command) - if c.Err != nil { return } else { @@ -56,8 +55,6 @@ func checkCommand(c *Context, command *model.Command) bool { return false } - tchan := Srv.Store.Team().Get(c.Session.TeamId) - if len(command.ChannelId) > 0 { cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId) @@ -66,24 +63,9 @@ func checkCommand(c *Context, command *model.Command) bool { } } - allowValet := false - if tResult := <-tchan; tResult.Err != nil { - c.Err = model.NewAppError("checkCommand", "Could not find the team for this session, team_id="+c.Session.TeamId, "") - return false - } else { - allowValet = tResult.Data.(*model.Team).AllowValet - } - - ec := runtime.FuncForPC(reflect.ValueOf(echoCommand).Pointer()).Name() - for _, v := range commands { - if !allowValet && ec == runtime.FuncForPC(reflect.ValueOf(v).Pointer()).Name() { - continue - } - if v(c, command) { - return true - } else if c.Err != nil { + if v(c, command) || c.Err != nil { return true } } @@ -112,55 +94,48 @@ func logoutCommand(c *Context, command *model.Command) bool { } func echoCommand(c *Context, command *model.Command) bool { - cmd := "/echo" - if strings.Index(command.Command, cmd) == 0 { - parts := strings.SplitN(command.Command, " ", 3) - - channelName := "" - if len(parts) >= 2 { - channelName = parts[1] + if !command.Suggest && strings.Index(command.Command, cmd) == 0 { + parameters := strings.SplitN(command.Command, " ", 2) + if len(parameters) != 2 || len(parameters[1]) == 0 { + return false } - - message := "" - if len(parts) >= 3 { - message = parts[2] + message := strings.Trim(parameters[1], " ") + delay := 0 + if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 { + if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil { + delay = checkDelay + } + message = message[1:endMsg] + } else if strings.Index(message, " ") > -1 { + delayIdx := strings.LastIndex(message, " ") + delayStr := strings.Trim(message[delayIdx:], " ") + + if checkDelay, err := strconv.Atoi(delayStr); err == nil { + delay = checkDelay + message = message[:delayIdx] + } } - if result := <-Srv.Store.Channel().GetChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { - c.Err = result.Err - return false - } else { - channels := result.Data.(*model.ChannelList) - - for _, v := range channels.Channels { - if v.Type == model.CHANNEL_DIRECT { - continue - } - - if v.Name == channelName && !command.Suggest { - post := &model.Post{} - post.ChannelId = v.Id - post.Message = message + // Fire off asynchronous process + go func() { + post := &model.Post{} + post.ChannelId = command.ChannelId + post.Message = message - if _, err := CreateValetPost(c, post); err != nil { - c.Err = err - return false - } - - command.Response = model.RESP_EXECUTED - return true - } + time.Sleep(time.Duration(delay) * time.Second) - if len(channelName) == 0 || (strings.Index(v.Name, channelName) == 0 && len(parts) < 3) { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: "Echo a message using Valet in a channel"}) - } + if _, err := CreatePost(c, post, false); err != nil { + l4g.Error("Unable to create /echo post, err=%v", err) } - } + }() + + command.Response = model.RESP_EXECUTED + return true } else if strings.Index(cmd, command.Command) == 0 { - command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo a message using Valet in a channel"}) + command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo back text from your account, /echo \"message\" [delay in seoncds]"}) } return false diff --git a/model/command.go b/model/command.go index 23573205e..83243cc98 100644 --- a/model/command.go +++ b/model/command.go @@ -14,7 +14,7 @@ const ( type Command struct { Command string `json:"command"` - Response string `json:"reponse"` + Response string `json:"response"` GotoLocation string `json:"goto_location"` ChannelId string `json:"channel_id"` Suggest bool `json:"-"` -- cgit v1.2.3-1-g7c22 From 4a0eeba2372755de574f69ca97525974addec8a3 Mon Sep 17 00:00:00 2001 From: nickago Date: Tue, 25 Aug 2015 09:19:07 -0700 Subject: Added semaphore for resource allocation to echo command --- api/command.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/api/command.go b/api/command.go index 31f676a11..749cbf790 100644 --- a/api/command.go +++ b/api/command.go @@ -24,6 +24,8 @@ var commands = []commandHandler{ echoCommand, } +var echoSem chan bool + func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") r.Handle("/command", ApiUserRequired(command)).Methods("POST") @@ -95,6 +97,7 @@ func logoutCommand(c *Context, command *model.Command) bool { func echoCommand(c *Context, command *model.Command) bool { cmd := "/echo" + maxThreads := 100 if !command.Suggest && strings.Index(command.Command, cmd) == 0 { parameters := strings.SplitN(command.Command, " ", 2) @@ -118,8 +121,24 @@ func echoCommand(c *Context, command *model.Command) bool { } } - // Fire off asynchronous process + if delay > 10000 { + c.Err = model.NewAppError("echoCommand", "Delays must be under 10000 seconds", "") + return false + } + + if echoSem == nil { + // We want one additional thread allowed so we never reach channel lockup + echoSem = make(chan bool, maxThreads+1) + } + + if len(echoSem) >= maxThreads { + c.Err = model.NewAppError("echoCommand", "High volume of echo request, cannot process request", "") + return false + } + + echoSem <- true go func() { + defer func() { <-echoSem }() post := &model.Post{} post.ChannelId = command.ChannelId post.Message = message -- cgit v1.2.3-1-g7c22 From 757820435237b51f9e202a23274b46529acd41ec Mon Sep 17 00:00:00 2001 From: nickago Date: Wed, 26 Aug 2015 07:21:37 -0700 Subject: Added unit tests --- api/command_test.go | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/api/command_test.go b/api/command_test.go index a58ef9be5..fe52dd41b 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -4,9 +4,10 @@ package api import ( + "testing" + "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" - "testing" ) func TestSuggestRootCommands(t *testing.T) { @@ -50,6 +51,12 @@ func TestSuggestRootCommands(t *testing.T) { if rs3.Suggestions[0].Suggestion != "/join" { t.Fatal("should have join cmd") } + + rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command) + + if rs4.Suggestions[0].Suggestion != "/echo" { + t.Fatal("should have echo cmd") + } } func TestLogoutCommands(t *testing.T) { @@ -145,3 +152,31 @@ func TestJoinCommands(t *testing.T) { t.Fatal("didn't join channel") } } + +func TestEchoCommand(t *testing.T) { + Setup() + + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} + user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user1.Id)) + + Client.LoginByEmail(team.Name, user1.Email, "pwd") + + channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} + channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) + + echoTestString := "/echo test" + + r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command) + if r1.Response != model.RESP_EXECUTED { + t.Fatal("Echo command failed to execute") + } + + p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList) + if len(p1.Order) != 1 { + t.Fatal("Echo command failed to send") + } +} -- cgit v1.2.3-1-g7c22