Last active
March 28, 2016 09:47
-
-
Save bradberger/74550e293fa2007464f0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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