Skip to content

Instantly share code, notes, and snippets.

@bradberger
Last active March 28, 2016 09:47
Show Gist options
  • Save bradberger/74550e293fa2007464f0 to your computer and use it in GitHub Desktop.
Save bradberger/74550e293fa2007464f0 to your computer and use it in GitHub Desktop.
// This file is correct version, but is not built dev_appserver because of a
// bug in the SDK.
// +build !appengine
package broker
import (
"encoding/json"
"errors"
"fmt"
"time"
"bitbucket.org/lieron/screvle-sentilon/model"
"bitbucket.org/lieron/screvle-sentilon/model/application"
"bitbucket.org/lieron/screvle-sentilon/model/data"
"bitbucket.org/lieron/screvle-sentilon/model/device"
"bitbucket.org/lieron/screvle-sentilon/model/user"
"github.com/Shopify/go-lua"
"golang.org/x/net/context"
"google.golang.org/appengine"
"google.golang.org/appengine/log"
)
var (
// ErrNoApplicationCode is returned when a topic has a callback, but the main Lua code file/string is empty.
ErrNoApplicationCode = errors.New("No application code")
)
// Context returns a valid context for use in datastore/memcache/etc. It's
// split across environments until the SDK issues are worked out.
func (b *Broker) Context() context.Context {
// Use the built-in context if available. This will give more debug
// info related to the request.
if b.context.Err() == nil {
// Per the docs, ok is false if there's no deadline, which means
// we can still use the context.
if d, ok := b.context.Deadline(); !ok || time.Now().Before(d) {
return b.context
}
}
return appengine.BackgroundContext()
}
func execLuaCode(c context.Context, callback string, dev *device.Device, d *data.Data) (result *data.Data, err error) {
// l.Call panics if there's an error, so we need to catch that here so as
// to just return a normal error.
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("Error executing callback %s: %v", callback, r)
}
}
}()
ts, _ := d.Timestamp.MarshalText()
l := lua.NewState()
a := application.Find(dev.ApplicationID, dev.SupplierID)
am := model.Init(c, a)
if err = am.Load(); err != nil {
return
}
// Make sure there is some code defined for the application.
if len(a.Code) < 1 {
err = ErrNoApplicationCode
return
}
// Lookup the user
u := user.Find(dev.UserID)
um := model.Init(c, u)
err = um.Load()
if err != nil {
return
}
// Get a model for the device, as well.
dm := model.Init(c, dev)
lua.BaseOpen(l)
lua.LoadString(l, a.Code)
// Add the user-defined function.
l.Global(callback)
// Push the Application table onto the stack, adding callback for app table if properties are changed.
l.NewTable()
l.PushString(a.ID)
l.SetField(-2, "id")
l.PushString(a.SupplierID)
l.SetField(-2, "supplier")
l.PushString(a.Name)
l.SetField(-2, "name")
l.PushInteger(int(a.Registration))
l.SetField(-2, "registration")
lua.SetFunctions(l, []lua.RegistryFunction {
{"__newindex", func(l *lua.State) int {
// The paramaters here are: 1 -table, 2 - key, 3 - value
k := lua.CheckString(l, 2)
if k == "properties" {
a.Properties = lua.CheckString(l, 3)
if err := am.Save(); err != nil {
panic(err)
}
}
return 0
}},
{"__index", func(l *lua.State) int {
// The paramaters here are: 1 - table, 2 - key
switch lua.CheckString(l, 2) {
case "properties":
l.PushString(a.Properties)
default:
l.PushNil()
}
return 1
}},
}, 0)
// Push the Device table onto the stack, adding callback for device table if properties are changed.
l.NewTable()
l.PushString(dev.ID)
l.SetField(-2, "id")
l.PushString(dev.ApplicationID)
l.SetField(-2, "application")
l.PushString(dev.UserID)
l.SetField(-2, "user")
l.PushString(dev.SerialNumber)
l.SetField(-2, "serialnumber")
lua.SetFunctions(l, []lua.RegistryFunction {
{"__newindex", func(l *lua.State) int {
// The paramaters here are: 1 -table, 2 - key, 3 - value
k := lua.CheckString(l, 2)
if k == "properties" {
dev.Properties = lua.CheckString(l, 3)
if err := dm.Save(); err != nil {
panic(err)
}
}
return 0
}},
{"__index", func(l *lua.State) int {
// The paramaters here are: 1 - table, 2 - key
switch lua.CheckString(l, 2) {
case "properties":
l.PushString(dev.Properties)
default:
l.PushNil()
}
return 1
}},
}, 0)
// Push the user table onto the stack, adding callback for user table if properties are changed.
l.NewTable()
l.PushString(u.ID)
l.SetField(-2, "id")
l.PushString(u.Email)
l.SetField(-2, "email")
l.PushString(u.ApplicationID)
l.SetField(-2, "application")
l.PushString(u.SupplierID)
l.SetField(-2, "supplier")
lua.SetFunctions(l, []lua.RegistryFunction {
{"__newindex", func(l *lua.State) int {
// The paramaters here are: 1 - table, 2 - key, 3 - value
k := lua.CheckString(l, 2)
if k == "properties" {
u.Properties = lua.CheckString(l, 3)
if err := um.Save(); err != nil {
panic(err)
}
}
return 0
}},
{"__index", func(l *lua.State) int {
// The paramaters here are: 1 - table, 2 - key
switch lua.CheckString(l, 2) {
case "properties":
l.PushString(u.Properties)
default:
l.PushNil()
}
return 1
}},
}, 0)
// Call the user-defined function with 6 params so as to include the tables, as well.
l.PushString(d.Topic)
l.PushString(string(d.Value))
l.PushString(string(ts))
l.Call(6, 1)
// Get the result, which should be at the top of the stack?
// Check first for JSON string here, then fall back to saving the string as the data value.
if s := lua.OptString(l, -1, ""); s != "" {
log.Infof(c, "Got string result from Lua code: %s", s)
result = &data.Data{}
if err = json.Unmarshal([]byte(s), &result); err != nil {
result = d
d.Value = s
}
// These properties are immutable.
result.ID = d.ID
result.DeviceID = d.DeviceID
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment