func ServiceSchedulerTest(tr *TestRunner) { Convey("should receive simple task", func() { tr.When(sarif.CreateMessage("schedule/duration", map[string]interface{}{ "duration": "300ms", })) reply := tr.Expect() So(reply, ShouldBeAction, "schedule/created") }) Convey("should receive complex task", func() { tr.When(sarif.CreateMessage("schedule/duration", map[string]interface{}{ "duration": "100ms", "reply": sarif.Message{ Action: "push/text", Destination: tr.Id, Text: "reminder finished", }, })) reply := tr.Expect() So(reply, ShouldBeAction, "schedule/created") }) Convey("should emit both tasks", func() { reply := tr.Expect() So(reply, ShouldBeAction, "push/text") So(reply.Text, ShouldEqual, "reminder finished") reply = tr.Expect() So(reply, ShouldBeAction, "schedule/finished") So(reply.Text, ShouldStartWith, "Reminder from") }) }
func (s *Service) handlePut(msg sarif.Message) { collection, key := parseAction("store/put/", msg.Action) if collection == "" { s.ReplyBadRequest(msg, errors.New("No collection specified.")) return } if len(msg.Payload.Raw) == 0 && msg.Text != "" { v, _ := json.Marshal(msg.Text) msg.Payload.Raw = json.RawMessage(v) } var p interface{} if err := msg.DecodePayload(&p); err != nil { s.ReplyBadRequest(msg, err) return } // TODO: maybe a JSON payload consistency check doc, err := s.Store.Put(&Document{ Collection: collection, Key: key, Value: msg.Payload.Raw, }) if err != nil { s.ReplyInternalError(msg, err) return } doc.Value = nil reply := sarif.CreateMessage("store/updated/"+doc.Collection+"/"+doc.Key, doc) s.Reply(msg, reply) pub := sarif.CreateMessage("store/updated/"+doc.Collection+"/"+doc.Key, doc) s.Publish(pub) }
func (s *Service) handleQuery(msg sarif.Message) { query := msg.Text // Query and wait for first answer answers, errs := know.Ask(query) ans, ok := <-answers if !ok { // No answer found? Check for errors. err, ok := <-errs if !ok { // No errors found? Send negative answer pl := MessageAnswer{ Query: query, } s.Reply(msg, sarif.CreateMessage("knowledge/noanswer", pl)) return } // Error received, forward. s.ReplyInternalError(msg, err) return } // Send answer. pl := MessageAnswer{ ans.Question, ans.Answer, ans.Provider, } s.Reply(msg, sarif.CreateMessage("knowledge/answer", pl)) return }
func ServiceEventsTest(tr *TestRunner) { Convey("should store a new event", func() { tr.When(sarif.CreateMessage("event/new", map[string]interface{}{ "action": "user/drink/coffee", "text": "User drinks coffee.", })) reply := tr.Expect() So(reply, ShouldBeAction, "event/created") }) Convey("should store return last event", func() { tr.When(sarif.CreateMessage("event/last", map[string]interface{}{ "action": "user/drink/coffee", })) reply := tr.Expect() So(reply, ShouldBeAction, "event/found") payload := Event{} reply.DecodePayload(&payload) So(payload.Text, ShouldEqual, "User drinks coffee.") }) Convey("should record other messages", func() { // Create test events tr.When(sarif.CreateMessage("event/record", map[string]interface{}{ "action": "some/value/changed", "time": time.Now().Add(-100 * time.Minute), })) So(tr.Expect(), ShouldBeAction, "event/recording") tr.When(sarif.Message{ Action: "some/value/changed", Text: "some value has changed", }) tr.Wait() tr.When(sarif.CreateMessage("event/last", map[string]interface{}{ "action": "some/value/changed", })) reply := tr.Expect() So(reply, ShouldBeAction, "event/found") payload := Event{} reply.DecodePayload(&payload) So(payload.Text, ShouldEqual, "some value has changed") }) }
func (s *Service) handleDel(msg sarif.Message) { collection, key := parseAction("store/del/", msg.Action) if collection == "" || key == "" { s.ReplyBadRequest(msg, errors.New("No collection or key specified.")) return } if err := s.Store.Del(collection, key); err != nil { s.ReplyInternalError(msg, err) return } s.Reply(msg, sarif.CreateMessage("store/deleted/"+collection+"/"+key, nil)) s.Publish(sarif.CreateMessage("store/deleted/"+collection+"/"+key, nil)) }
func (s *Service) handleScan(msg sarif.Message) { collection, prefix := parseAction("store/scan/", msg.Action) if collection == "" { s.ReplyBadRequest(msg, errors.New("No collection specified.")) return } var p scanMessage if err := msg.DecodePayload(&p); err != nil { s.ReplyBadRequest(msg, err) return } if p.Start == "" && p.End == "" { if p.Prefix == "" { p.Prefix = prefix } } got, err := s.doScan(collection, p) if err != nil { s.ReplyInternalError(msg, err) return } s.Reply(msg, sarif.CreateMessage("store/scanned/"+collection, got)) }
func (cv *Conversation) handleUnknownUserMessage(msg sarif.Message) { pl := &MsgErrNatural{ Original: msg.Text, } cv.SendToClient(msg.Reply(sarif.CreateMessage("err/natural", pl))) }
func (t *TestRunner) UseConn(conn sarif.Conn) { t.conn = conn t.Publish(sarif.CreateMessage("proto/sub", map[string]string{ "device": t.Id, })) go t.listen() }
func (s *Service) handleEventList(msg sarif.Message) { var filter map[string]interface{} if err := msg.DecodePayload(&filter); err != nil { s.ReplyBadRequest(msg, err) return } if filter == nil { filter = make(map[string]interface{}) } reverse := false if len(filter) == 0 { filter["time >="] = time.Now().Add(-24 * time.Hour) reverse = true } s.Log("debug", "list by filter:", filter) var events []Event err := s.Store.Scan("events", store.Scan{ Only: "values", Filter: filter, Reverse: reverse, }, &events) if err != nil { s.ReplyInternalError(msg, err) } s.Log("debug", "list - found", len(events)) s.Reply(msg, sarif.CreateMessage("events/listed", &aggPayload{ Type: "list", Filter: filter, Events: events, Value: float64(len(events)), })) }
func (s *Service) HandleQuery(msg sarif.Message) { var f Fact if err := msg.DecodePayload(&f); err != nil { s.ReplyBadRequest(msg, err) return } f, err := s.InterpretLiterals(f) if err != nil { s.ReplyInternalError(msg, err) return } var facts []*Fact if err := s.DB.Where(f).Limit(100).Find(&facts).Error; err != nil { s.ReplyInternalError(msg, err) return } if facts, err = s.AddLabelFacts(facts); err != nil { s.ReplyInternalError(msg, err) return } s.Reply(msg, sarif.CreateMessage("concepts/result", &resultPayload{ ToJsonLd(facts), facts, })) }
func (s *Service) HandleQueryExternal(msg sarif.Message) { var f Fact if err := msg.DecodePayload(&f); err != nil { s.ReplyBadRequest(msg, err) return } facts := []*Fact{&f} FillVariables(facts) var r sparql.ResourceResponse q := sparql.DBPedia.Query() q = BuildQuery(q, facts) if err := q.Exec(&r); err != nil { s.ReplyInternalError(msg, err) return } result := ApplyBindings(facts, r.Results.Bindings, sparql.CommonPrefixes) s.Reply(msg, sarif.CreateMessage("concepts/result", &resultPayload{ ToJsonLd(result), result, })) for _, f := range result { if err := s.DB.FirstOrCreate(&f, &f).Error; err != nil { s.Log("err", "[reasoner] error updating external fact: "+err.Error()) } } }
func (s *Service) handleCounter(msg sarif.Message) { if msg.Text == "" { s.ReplyBadRequest(msg, errors.New("Please specify a counter name!")) return } name := msg.Text cnt, err := s.counterGet(name) if err != nil { s.ReplyInternalError(msg, err) return } newCnt := cnt if msg.IsAction("cmd/decrement") { newCnt-- } else if msg.IsAction("cmd/increment") { newCnt++ } if newCnt != cnt { if err := s.counterSet(name, newCnt); err != nil { s.ReplyInternalError(msg, err) return } } s.Reply(msg, sarif.CreateMessage("counter/changed/"+name, &counterMessage{name, newCnt})) }
func (s *Service) handleEventRecord(msg sarif.Message) { var p recordPayload if err := msg.DecodePayload(&p); err != nil { s.ReplyBadRequest(msg, err) return } if p.Action == "" { s.ReplyBadRequest(msg, errors.New("No action specified")) return } var cfg Config s.cfg.Get(&cfg) if cfg.RecordedActions == nil { cfg.RecordedActions = make(map[string]bool) } if enabled := cfg.RecordedActions[p.Action]; !enabled { cfg.RecordedActions[p.Action] = true s.cfg.Set(cfg) s.Subscribe(p.Action, "", s.handleEventNew) } s.Log("debug", "recording action:", p.Action) s.Reply(msg, sarif.CreateMessage("event/recording", p)) }
func (s *Service) handleAuthOtp(msg sarif.Message) { tok := "otp/std:" + GenerateDigits() s.SessionTokens[tok] = time.Now().Add(time.Minute) s.Reply(msg, sarif.CreateMessage("auth/generated", sarif.ClientInfo{ Auth: tok, })) }
func (s *Service) handleLuaLoad(msg sarif.Message) { gen := false name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "lua/load"), "/") if name == "" { name, gen = sarif.GenerateId(), true } if _, ok := s.Machines[name]; ok { s.destroyMachine(name) } m, err := s.createMachine(name) if err != nil { s.ReplyInternalError(msg, err) return } var ctp ContentPayload if err := msg.DecodePayload(&ctp); err != nil { s.ReplyBadRequest(msg, err) return } text := msg.Text if ctp.Content.Url != "" { ct, err := content.Get(ctp.Content) if err != nil { s.ReplyBadRequest(msg, err) } text = string(ct.Data) } var gp interface{} msg.DecodePayload(&gp) out, err, _ := m.Do(text, gp) if err != nil { s.ReplyBadRequest(msg, err) s.destroyMachine(name) return } if !gen { f, err := os.Create(s.cfg.ScriptDir + "/" + name + ".lua") if err == nil { _, err = f.Write([]byte(text)) defer f.Close() } if err != nil { s.ReplyInternalError(msg, err) s.destroyMachine(name) return } } s.Reply(msg, sarif.CreateMessage("lua/loaded", &MsgMachineStatus{ name, "up", out, })) }
func (s *Store) Get(key string, result interface{}) error { req := sarif.CreateMessage("store/get/"+key, nil) req.Destination = s.StoreName reply, ok := <-s.client.Request(req) if err := checkErr(reply, ok); err != nil { return err } return reply.DecodePayload(result) }
func (s *Scheduler) simpleCron() { for { now := time.Now() nextHour := now.Add(30 * time.Minute).Round(time.Hour) time.Sleep(nextHour.Sub(now)) action := strings.ToLower(time.Now().Add(5 * time.Minute).Format("cron/15h/Mon/2d/1m")) s.Publish(sarif.CreateMessage(action, nil)) } }
func (s *Service) handleAuthToken(msg sarif.Message) { tok := "token/std:" + sarif.GenerateId() + sarif.GenerateId() + sarif.GenerateId() s.Config.Tokens[tok] = true s.cfg.Set(s.Config) s.Reply(msg, sarif.CreateMessage("auth/generated", sarif.ClientInfo{ Auth: tok, })) }
func (s *Service) handleSimple(f func() error) func(sarif.Message) { return func(msg sarif.Message) { if err := f(); err != nil { s.ReplyInternalError(msg, err) return } s.Reply(msg, sarif.CreateMessage("ack/"+msg.Action, nil)) } }
func (s *Service) checkGeofences(last, curr Location) { var lastFences, currFences []Geofence err := s.Store.Scan("location_geofences", store.Scan{ Only: "values", Filter: map[string]interface{}{ "lat_min <=": last.Latitude, "lat_max >=": last.Latitude, "lng_min <=": last.Longitude, "lng_max >=": last.Longitude, }, }, &lastFences) if err != nil { s.Log("err/internal", "retrieve last fences: "+err.Error()) } err = s.Store.Scan("location_geofences", store.Scan{ Only: "values", Filter: map[string]interface{}{ "lat_min <=": curr.Latitude, "lat_max >=": curr.Latitude, "lng_min <=": curr.Longitude, "lng_max >=": curr.Longitude, }, }, &currFences) if err != nil { s.Log("err/internal", "retrieve curr fences: "+err.Error()) } for _, g := range lastFences { if !fenceInSlice(g, currFences) { s.Log("debug", "geofence leave", g) pl := GeofenceEventPayload{curr, g, "leave"} msg := sarif.CreateMessage("location/fence/leave/"+g.Name, pl) s.Publish(msg) } } for _, g := range currFences { if !fenceInSlice(g, lastFences) { s.Log("debug", "geofence enter", g) pl := GeofenceEventPayload{curr, g, "enter"} msg := sarif.CreateMessage("location/fence/enter/"+g.Name, pl) s.Publish(msg) } } }
func (s *Service) HandleCard(msg sarif.Message) { uid := strings.TrimPrefix(msg.Action, "vdir/card/") c, ok := s.cards[uid] if !ok { s.ReplyBadRequest(msg, errors.New("No card with with UID "+uid+" found!")) return } s.Reply(msg, sarif.CreateMessage("vdir/card", c.Card)) }
func (s *Service) counterSet(name string, cnt int) error { ack, ok := <-s.Request(sarif.CreateMessage("store/put/counter/"+name, cnt)) if !ok { return errors.New("Timeout while setting new value") } if ack.IsAction("err") { return errors.New(ack.Text) } return nil }
func (s *Store) Batch(p []Command, result interface{}) error { req := sarif.CreateMessage("store/batch", &p) req.Destination = s.StoreName reply, ok := <-s.client.Request(req) if err := checkErr(reply, ok); err != nil { return err } return reply.DecodePayload(result) }
func (s *Store) Put(key string, doc interface{}) (*Document, error) { res := &Document{} req := sarif.CreateMessage("store/put/"+key, doc) req.Destination = s.StoreName reply, ok := <-s.client.Request(req) if err := checkErr(reply, ok); err != nil { return nil, err } return res, reply.DecodePayload(res) }
func (s *Scheduler) handle(msg sarif.Message) { var t ScheduleMessage if err := msg.DecodePayload(&t); err != nil { s.ReplyBadRequest(msg, err) return } now := time.Now() t.Task.Time = now if t.Time != "" { t.Task.Time = futureTime(util.ParseTime(t.Time, now)) } if t.RandomAfter != "" && t.RandomBefore != "" { after := futureTime(util.ParseTime(t.RandomAfter, t.Task.Time)) before := futureTime(util.ParseTime(t.RandomBefore, t.Task.Time)) if before.Before(after) { after, before = before, after } maxDur := int64(before.Sub(after)) ranDur := time.Duration(rand.Int63n(maxDur)) t.Task.Time = after.Add(ranDur) } if t.Duration != "" { dur, err := util.ParseDuration(t.Duration) if err != nil { s.ReplyBadRequest(msg, err) return } t.Task.Time = t.Task.Time.Add(dur) } if t.Task.Reply.Action == "" { text := msg.Text if text == "" { text = "Reminder from " + time.Now().Format(time.RFC3339) + " finished." } t.Task.Reply = sarif.Message{ Action: "schedule/finished", Destination: msg.Source, Text: text, } } if t.Task.Reply.CorrId == "" { t.Reply.CorrId = msg.Id } s.Log("info", "new task:", t) if _, err := s.Store.Put(t.Task.Key(), &t.Task); err != nil { s.ReplyInternalError(msg, err) return } go s.recalculateTimer() s.Reply(msg, sarif.CreateMessage("schedule/created", t.Task)) }
func (s *Service) handleServingRecord(msg sarif.Message) { var sv Serving if err := msg.DecodePayload(&sv); err != nil { s.ReplyBadRequest(msg, err) return } size, name := splitSizeName(msg.Text) if sv.Size == 0 { sv.Size = size } if sv.Name == "" { sv.Name = name } if sv.Product == nil { ps, err := s.findProduct(sv.Name) if err != nil { s.ReplyBadRequest(msg, err) return } if len(ps) > 1 { pList := "" for _, p := range ps { pList += "\n- " + p.Name } s.ReplyBadRequest(msg, fmt.Errorf("%d products named %s found.%s", len(ps), sv.Name, pList)) return } if len(ps) == 1 { sv.Product = &ps[0] } } if sv.AmountWeight <= 0 { sv.AmountWeight = Weight(sv.Size) * sv.Product.ServingWeight } if sv.AmountVolume <= 0 { sv.AmountVolume = Volume(sv.Size) * sv.Product.ServingVolume } if sv.AmountWeight <= 0 && sv.AmountVolume <= 0 { s.ReplyBadRequest(msg, errors.New("No serving amount specified.")) return } if sv.Time.IsZero() { sv.Time = time.Now() } if err := s.DB.Save(&sv).Error; err != nil { s.ReplyInternalError(msg, err) return } s.Reply(msg, sarif.CreateMessage("meal/serving/recorded", &sv)) }
func (s *Service) handleMockAttachments(msg sarif.Message) { s.Log("debug", "received", msg) pl := AttachmentPayload{ Attachments: []schema.Attachment{ { Fallback: "Image of a cat.", Title: "Cat", ImageUrl: "data:image/jpeg;base64,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", }, }, } s.Reply(msg, sarif.CreateMessage("mock/attached", pl)) }
func (s *Service) getConversation(device string) *Conversation { cv, ok := s.conversations[device] if !ok { cv = &Conversation{ service: s, Device: device, } s.conversations[device] = cv s.Subscribe("", s.DeviceId+"/"+device, s.handleNetworkMessage) cv.PublishForClient(sarif.CreateMessage("natural/client/new", nil)) } return cv }
func (s *Service) handleLocationUpdate(msg sarif.Message) { loc := Location{} if err := msg.DecodePayload(&loc); err != nil { s.ReplyBadRequest(msg, err) return } if loc.Time.IsZero() { loc.Time = time.Now() } loc.Geohash = EncodeGeohash(loc.Latitude, loc.Longitude, 12) s.Log("debug", "store update", loc) var last []Location err := s.Store.Scan("locations", store.Scan{ Reverse: true, Only: "values", Limit: 1, }, &last) if err != nil { s.Log("err/internal", "retrieve last err: "+err.Error()) } if len(last) > 0 { loc.Distance = HaversineDistance(last[0], loc) loc.Speed = loc.Distance / loc.Time.Sub(last[0].Time).Seconds() } if _, err := s.Store.Put(loc.Key(), &loc); err != nil { s.ReplyInternalError(msg, err) } if changed := s.Clusters.Advance(loc); changed { c := s.Clusters.Current() status := "enter" if c.Status != ConfirmedCluster { status = "leave" c = s.Clusters.LastCompleted() s.Clusters.ClearCompleted() } // TODO: make optional if place, err := ReverseGeocode(c.Location); err == nil { c.Address = place.Pretty() } s.Publish(sarif.CreateMessage("location/cluster/"+status, c)) } if len(last) > 0 { s.checkGeofences(last[0], loc) } }
func (s *Service) handleBatch(msg sarif.Message) { cmds := make([]BatchCommand, 0) if err := msg.DecodePayload(&cmds); err != nil { s.ReplyInternalError(msg, err) return } results := make([]interface{}, len(cmds)) for i, cmd := range cmds { collection, key := parseAction("", cmd.Key) switch cmd.Type { case "put": doc, err := s.Store.Put(&Document{ Collection: collection, Key: key, Value: []byte(cmd.Value), }) if err != nil { s.ReplyInternalError(msg, err) return } results[i] = doc case "get": doc, err := s.Store.Get(collection, key) if err != nil { s.ReplyInternalError(msg, err) return } results[i] = doc case "del": if err := s.Store.Del(collection, key); err != nil { s.ReplyInternalError(msg, err) return } results[i] = true case "scan": var p scanMessage if err := json.Unmarshal(cmd.Value, &p); err != nil { s.ReplyInternalError(msg, err) return } got, err := s.doScan(collection, p) if err != nil { s.ReplyInternalError(msg, err) return } results[i] = got } } s.Reply(msg, sarif.CreateMessage("store/batched/", results)) }