add dat base

This commit is contained in:
2022-12-31 17:17:10 +01:00
parent 6fb82df153
commit 4ff22d7d63
27 changed files with 1337 additions and 36 deletions
+45 -2
View File
@@ -5,6 +5,8 @@ import (
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/device"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"github.com/google/uuid"
"sync"
"sync/atomic"
"time"
@@ -42,7 +44,7 @@ func (s *effectEnforcer) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event)
// If the device is managed by the effect enforcer, cause the effect to be
// re-ran.
s.mu.Lock()
if run, ok := s.index[event.ID]; ok && run.due.IsZero() {
if run, ok := s.index[event.ID]; ok {
run.due = time.Now()
}
s.mu.Unlock()
@@ -59,10 +61,35 @@ func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.
allowedIDs = append(allowedIDs, ptr.ID)
}
}
if len(pointers) == 0 {
if command.ID != nil {
bus.RunEvent(events.AssignmentRemoved{ID: *command.ID})
} else {
bus.RunEvent(events.Log{
Level: "info",
Code: "assignment_matched_none",
Message: "Assignment to \"" + command.Match + "\" matched no devices.",
})
}
return
}
id := uuid.New()
if command.ID != nil {
id = *command.ID
}
bus.RunEvent(events.AssignmentCreated{
ID: id,
Match: command.Match,
Effect: command.Effect,
})
s.mu.Lock()
// Create a new run
newRun := &effectEnforcerRun{
id: id,
due: time.Now(),
ids: allowedIDs,
effect: command.Effect,
@@ -81,17 +108,28 @@ func (s *effectEnforcer) HandleCommand(bus *lucifer3.EventBus, command lucifer3.
if atomic.CompareAndSwapUint32(&s.started, 0, 1) {
go s.runLoop(bus)
}
for _, deviceID := range allowedIDs {
bus.RunEvent(events.DeviceAssigned{
DeviceID: deviceID,
AssignmentID: gentools.Ptr(id),
})
}
}
}
func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
deleteList := make([]int, 0, 8)
batch := make(commands.SetStateBatch, 64)
deleteIDs := make([]uuid.UUID, 0)
for now := range time.NewTicker(time.Millisecond * 50).C {
deleteIDs = deleteIDs[:0]
s.mu.Lock()
for i, run := range s.list {
if run.dead {
deleteIDs = append(deleteIDs, run.id)
deleteList = append(deleteList, i-len(deleteList))
continue
}
@@ -123,13 +161,17 @@ func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
if len(deleteList) > 0 {
for _, i := range deleteList {
deleteList = append(deleteList[:i], deleteList[i+1:]...)
s.list = append(s.list[:i], s.list[i+1:]...)
}
deleteList = deleteList[:0]
}
s.mu.Unlock()
for _, id := range deleteIDs {
bus.RunEvent(events.AssignmentRemoved{ID: id})
}
if len(batch) > 0 {
bus.RunCommand(batch)
batch = make(commands.SetStateBatch, 64)
@@ -138,6 +180,7 @@ func (s *effectEnforcer) runLoop(bus *lucifer3.EventBus) {
}
type effectEnforcerRun struct {
id uuid.UUID
due time.Time
ids []string
effect lucifer3.Effect
+12 -9
View File
@@ -196,10 +196,12 @@ func (b *Bridge) ApplyPatches(resources []ResourceData) (events []lucifer3.Event
func (b *Bridge) SetStates(patch map[string]device.State) {
b.mu.Lock()
newStates := gentools.CopyMap(b.desiredStates)
desiredStates := b.desiredStates
resources := b.resources
b.mu.Unlock()
desiredStates = gentools.CopyMap(desiredStates)
prefix := "hue:" + b.host + ":"
for id, state := range patch {
if !strings.HasPrefix(id, prefix) {
@@ -213,14 +215,14 @@ func (b *Bridge) SetStates(patch map[string]device.State) {
}
if !state.Empty() {
newStates[id] = resource.FixState(state, resources)
desiredStates[id] = resource.FixState(state, resources)
} else {
delete(newStates, id)
delete(desiredStates, id)
}
}
b.mu.Lock()
b.desiredStates = newStates
b.desiredStates = desiredStates
b.mu.Unlock()
b.triggerCongruenceCheck()
@@ -289,8 +291,8 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
break
}
// Make sure this loop takes half a second
rateLimit := time.After(time.Second / 2)
// Make sure this loop doesn't spam too hard
rateLimit := time.After(time.Second / 10)
// Take states
b.mu.Lock()
@@ -305,6 +307,7 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
updates := make(map[string]ResourceUpdate)
for id, desired := range desiredStates {
active, activeOK := activeStates[id]
lightID := resources[id].ServiceID("light")
if !reachable[id] || !activeOK || lightID == nil {
@@ -423,10 +426,10 @@ func (b *Bridge) makeCongruentLoop(ctx context.Context) {
b.mu.Unlock()
cancel()
}
// Wait the remaining time for the rate limit
<-rateLimit
// Wait the remaining time for the rate limit
<-rateLimit
}
}
}
@@ -0,0 +1,16 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE device_info
(
id VARCHAR(255) NOT NULL,
kind VARCHAR(255) NOT NULL,
data JSON NOT NULL,
PRIMARY KEY (id, kind)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE device_info;
-- +goose StatementEnd
@@ -0,0 +1,15 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE device_assignment
(
id CHAR(36) NOT NULL PRIMARY KEY,
created_date TIMESTAMP NOT NULL,
`match` TEXT NOT NULL,
effect JSON NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE device_assignment;
-- +goose StatementEnd
@@ -0,0 +1,14 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE device_auth
(
id VARCHAR(255) NOT NULL PRIMARY KEY,
api_key TEXT NOT NULL,
extras JSON NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE device_auth;
-- +goose StatementEnd
@@ -0,0 +1,15 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE device_alias
(
id VARCHAR(255) NOT NULL,
alias VARCHAR(1023) NOT NULL,
PRIMARY KEY (id, alias)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE device_alias;
-- +goose StatementEnd
+238
View File
@@ -0,0 +1,238 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
package mysqlgen
import (
"context"
"database/sql"
"fmt"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
q := Queries{db: db}
var err error
if q.deleteDeviceAliasStmt, err = db.PrepareContext(ctx, deleteDeviceAlias); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceAlias: %w", err)
}
if q.deleteDeviceAliasByIDStmt, err = db.PrepareContext(ctx, deleteDeviceAliasByID); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceAliasByID: %w", err)
}
if q.deleteDeviceAliasByIDLikeStmt, err = db.PrepareContext(ctx, deleteDeviceAliasByIDLike); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceAliasByIDLike: %w", err)
}
if q.deleteDeviceAssignmentStmt, err = db.PrepareContext(ctx, deleteDeviceAssignment); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceAssignment: %w", err)
}
if q.deleteDeviceAuthStmt, err = db.PrepareContext(ctx, deleteDeviceAuth); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceAuth: %w", err)
}
if q.deleteDeviceInfoStmt, err = db.PrepareContext(ctx, deleteDeviceInfo); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceInfo: %w", err)
}
if q.deleteDeviceInfoByIDStmt, err = db.PrepareContext(ctx, deleteDeviceInfoByID); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceInfoByID: %w", err)
}
if q.deleteDeviceInfoLikeStmt, err = db.PrepareContext(ctx, deleteDeviceInfoLike); err != nil {
return nil, fmt.Errorf("error preparing query DeleteDeviceInfoLike: %w", err)
}
if q.insertDeviceAliasStmt, err = db.PrepareContext(ctx, insertDeviceAlias); err != nil {
return nil, fmt.Errorf("error preparing query InsertDeviceAlias: %w", err)
}
if q.listDeviceAliasesStmt, err = db.PrepareContext(ctx, listDeviceAliases); err != nil {
return nil, fmt.Errorf("error preparing query ListDeviceAliases: %w", err)
}
if q.listDeviceAssignmentsStmt, err = db.PrepareContext(ctx, listDeviceAssignments); err != nil {
return nil, fmt.Errorf("error preparing query ListDeviceAssignments: %w", err)
}
if q.listDeviceAuthStmt, err = db.PrepareContext(ctx, listDeviceAuth); err != nil {
return nil, fmt.Errorf("error preparing query ListDeviceAuth: %w", err)
}
if q.listDeviceInfosStmt, err = db.PrepareContext(ctx, listDeviceInfos); err != nil {
return nil, fmt.Errorf("error preparing query ListDeviceInfos: %w", err)
}
if q.replaceDeviceAssignmentStmt, err = db.PrepareContext(ctx, replaceDeviceAssignment); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceDeviceAssignment: %w", err)
}
if q.replaceDeviceAuthStmt, err = db.PrepareContext(ctx, replaceDeviceAuth); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceDeviceAuth: %w", err)
}
if q.replaceDeviceInfoStmt, err = db.PrepareContext(ctx, replaceDeviceInfo); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceDeviceInfo: %w", err)
}
return &q, nil
}
func (q *Queries) Close() error {
var err error
if q.deleteDeviceAliasStmt != nil {
if cerr := q.deleteDeviceAliasStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceAliasStmt: %w", cerr)
}
}
if q.deleteDeviceAliasByIDStmt != nil {
if cerr := q.deleteDeviceAliasByIDStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceAliasByIDStmt: %w", cerr)
}
}
if q.deleteDeviceAliasByIDLikeStmt != nil {
if cerr := q.deleteDeviceAliasByIDLikeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceAliasByIDLikeStmt: %w", cerr)
}
}
if q.deleteDeviceAssignmentStmt != nil {
if cerr := q.deleteDeviceAssignmentStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceAssignmentStmt: %w", cerr)
}
}
if q.deleteDeviceAuthStmt != nil {
if cerr := q.deleteDeviceAuthStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceAuthStmt: %w", cerr)
}
}
if q.deleteDeviceInfoStmt != nil {
if cerr := q.deleteDeviceInfoStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceInfoStmt: %w", cerr)
}
}
if q.deleteDeviceInfoByIDStmt != nil {
if cerr := q.deleteDeviceInfoByIDStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceInfoByIDStmt: %w", cerr)
}
}
if q.deleteDeviceInfoLikeStmt != nil {
if cerr := q.deleteDeviceInfoLikeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteDeviceInfoLikeStmt: %w", cerr)
}
}
if q.insertDeviceAliasStmt != nil {
if cerr := q.insertDeviceAliasStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing insertDeviceAliasStmt: %w", cerr)
}
}
if q.listDeviceAliasesStmt != nil {
if cerr := q.listDeviceAliasesStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listDeviceAliasesStmt: %w", cerr)
}
}
if q.listDeviceAssignmentsStmt != nil {
if cerr := q.listDeviceAssignmentsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listDeviceAssignmentsStmt: %w", cerr)
}
}
if q.listDeviceAuthStmt != nil {
if cerr := q.listDeviceAuthStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listDeviceAuthStmt: %w", cerr)
}
}
if q.listDeviceInfosStmt != nil {
if cerr := q.listDeviceInfosStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listDeviceInfosStmt: %w", cerr)
}
}
if q.replaceDeviceAssignmentStmt != nil {
if cerr := q.replaceDeviceAssignmentStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceDeviceAssignmentStmt: %w", cerr)
}
}
if q.replaceDeviceAuthStmt != nil {
if cerr := q.replaceDeviceAuthStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceDeviceAuthStmt: %w", cerr)
}
}
if q.replaceDeviceInfoStmt != nil {
if cerr := q.replaceDeviceInfoStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceDeviceInfoStmt: %w", cerr)
}
}
return err
}
func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (sql.Result, error) {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...)
case stmt != nil:
return stmt.ExecContext(ctx, args...)
default:
return q.db.ExecContext(ctx, query, args...)
}
}
func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...)
case stmt != nil:
return stmt.QueryContext(ctx, args...)
default:
return q.db.QueryContext(ctx, query, args...)
}
}
func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) *sql.Row {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...)
case stmt != nil:
return stmt.QueryRowContext(ctx, args...)
default:
return q.db.QueryRowContext(ctx, query, args...)
}
}
type Queries struct {
db DBTX
tx *sql.Tx
deleteDeviceAliasStmt *sql.Stmt
deleteDeviceAliasByIDStmt *sql.Stmt
deleteDeviceAliasByIDLikeStmt *sql.Stmt
deleteDeviceAssignmentStmt *sql.Stmt
deleteDeviceAuthStmt *sql.Stmt
deleteDeviceInfoStmt *sql.Stmt
deleteDeviceInfoByIDStmt *sql.Stmt
deleteDeviceInfoLikeStmt *sql.Stmt
insertDeviceAliasStmt *sql.Stmt
listDeviceAliasesStmt *sql.Stmt
listDeviceAssignmentsStmt *sql.Stmt
listDeviceAuthStmt *sql.Stmt
listDeviceInfosStmt *sql.Stmt
replaceDeviceAssignmentStmt *sql.Stmt
replaceDeviceAuthStmt *sql.Stmt
replaceDeviceInfoStmt *sql.Stmt
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
tx: tx,
deleteDeviceAliasStmt: q.deleteDeviceAliasStmt,
deleteDeviceAliasByIDStmt: q.deleteDeviceAliasByIDStmt,
deleteDeviceAliasByIDLikeStmt: q.deleteDeviceAliasByIDLikeStmt,
deleteDeviceAssignmentStmt: q.deleteDeviceAssignmentStmt,
deleteDeviceAuthStmt: q.deleteDeviceAuthStmt,
deleteDeviceInfoStmt: q.deleteDeviceInfoStmt,
deleteDeviceInfoByIDStmt: q.deleteDeviceInfoByIDStmt,
deleteDeviceInfoLikeStmt: q.deleteDeviceInfoLikeStmt,
insertDeviceAliasStmt: q.insertDeviceAliasStmt,
listDeviceAliasesStmt: q.listDeviceAliasesStmt,
listDeviceAssignmentsStmt: q.listDeviceAssignmentsStmt,
listDeviceAuthStmt: q.listDeviceAuthStmt,
listDeviceInfosStmt: q.listDeviceInfosStmt,
replaceDeviceAssignmentStmt: q.replaceDeviceAssignmentStmt,
replaceDeviceAuthStmt: q.replaceDeviceAuthStmt,
replaceDeviceInfoStmt: q.replaceDeviceInfoStmt,
}
}
+301
View File
@@ -0,0 +1,301 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: device.sql
package mysqlgen
import (
"context"
"encoding/json"
"time"
"github.com/google/uuid"
)
const deleteDeviceAlias = `-- name: DeleteDeviceAlias :exec
DELETE
FROM device_alias
WHERE id = ?
AND alias = ?
`
type DeleteDeviceAliasParams struct {
ID string
Alias string
}
func (q *Queries) DeleteDeviceAlias(ctx context.Context, arg DeleteDeviceAliasParams) error {
_, err := q.exec(ctx, q.deleteDeviceAliasStmt, deleteDeviceAlias, arg.ID, arg.Alias)
return err
}
const deleteDeviceAliasByID = `-- name: DeleteDeviceAliasByID :exec
DELETE
FROM device_alias
WHERE id = ?
`
func (q *Queries) DeleteDeviceAliasByID(ctx context.Context, id string) error {
_, err := q.exec(ctx, q.deleteDeviceAliasByIDStmt, deleteDeviceAliasByID, id)
return err
}
const deleteDeviceAliasByIDLike = `-- name: DeleteDeviceAliasByIDLike :exec
DELETE
FROM device_alias
WHERE id LIKE ?
`
func (q *Queries) DeleteDeviceAliasByIDLike(ctx context.Context, id string) error {
_, err := q.exec(ctx, q.deleteDeviceAliasByIDLikeStmt, deleteDeviceAliasByIDLike, id)
return err
}
const deleteDeviceAssignment = `-- name: DeleteDeviceAssignment :exec
DELETE
FROM device_assignment
WHERE id = ?
`
func (q *Queries) DeleteDeviceAssignment(ctx context.Context, id uuid.UUID) error {
_, err := q.exec(ctx, q.deleteDeviceAssignmentStmt, deleteDeviceAssignment, id)
return err
}
const deleteDeviceAuth = `-- name: DeleteDeviceAuth :exec
DELETE
FROM device_auth
WHERE id = ?
`
func (q *Queries) DeleteDeviceAuth(ctx context.Context, id string) error {
_, err := q.exec(ctx, q.deleteDeviceAuthStmt, deleteDeviceAuth, id)
return err
}
const deleteDeviceInfo = `-- name: DeleteDeviceInfo :exec
DELETE
FROM device_info
WHERE id = ?
AND kind = ?
`
type DeleteDeviceInfoParams struct {
ID string
Kind string
}
func (q *Queries) DeleteDeviceInfo(ctx context.Context, arg DeleteDeviceInfoParams) error {
_, err := q.exec(ctx, q.deleteDeviceInfoStmt, deleteDeviceInfo, arg.ID, arg.Kind)
return err
}
const deleteDeviceInfoByID = `-- name: DeleteDeviceInfoByID :exec
DELETE
FROM device_info
WHERE id = ?
`
func (q *Queries) DeleteDeviceInfoByID(ctx context.Context, id string) error {
_, err := q.exec(ctx, q.deleteDeviceInfoByIDStmt, deleteDeviceInfoByID, id)
return err
}
const deleteDeviceInfoLike = `-- name: DeleteDeviceInfoLike :exec
DELETE
FROM device_info
WHERE id LIKE ?
`
func (q *Queries) DeleteDeviceInfoLike(ctx context.Context, id string) error {
_, err := q.exec(ctx, q.deleteDeviceInfoLikeStmt, deleteDeviceInfoLike, id)
return err
}
const insertDeviceAlias = `-- name: InsertDeviceAlias :exec
INSERT INTO device_alias (id, alias)
VALUES (?, ?)
`
type InsertDeviceAliasParams struct {
ID string
Alias string
}
func (q *Queries) InsertDeviceAlias(ctx context.Context, arg InsertDeviceAliasParams) error {
_, err := q.exec(ctx, q.insertDeviceAliasStmt, insertDeviceAlias, arg.ID, arg.Alias)
return err
}
const listDeviceAliases = `-- name: ListDeviceAliases :many
SELECT id, alias
FROM device_alias
`
func (q *Queries) ListDeviceAliases(ctx context.Context) ([]DeviceAlias, error) {
rows, err := q.query(ctx, q.listDeviceAliasesStmt, listDeviceAliases)
if err != nil {
return nil, err
}
defer rows.Close()
items := []DeviceAlias{}
for rows.Next() {
var i DeviceAlias
if err := rows.Scan(&i.ID, &i.Alias); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDeviceAssignments = `-- name: ListDeviceAssignments :many
SELECT id, created_date, ` + "`" + `match` + "`" + `, effect
FROM device_assignment
ORDER BY created_date
`
func (q *Queries) ListDeviceAssignments(ctx context.Context) ([]DeviceAssignment, error) {
rows, err := q.query(ctx, q.listDeviceAssignmentsStmt, listDeviceAssignments)
if err != nil {
return nil, err
}
defer rows.Close()
items := []DeviceAssignment{}
for rows.Next() {
var i DeviceAssignment
if err := rows.Scan(
&i.ID,
&i.CreatedDate,
&i.Match,
&i.Effect,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDeviceAuth = `-- name: ListDeviceAuth :many
SELECT id, api_key, extras
FROM device_auth
`
func (q *Queries) ListDeviceAuth(ctx context.Context) ([]DeviceAuth, error) {
rows, err := q.query(ctx, q.listDeviceAuthStmt, listDeviceAuth)
if err != nil {
return nil, err
}
defer rows.Close()
items := []DeviceAuth{}
for rows.Next() {
var i DeviceAuth
if err := rows.Scan(&i.ID, &i.ApiKey, &i.Extras); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDeviceInfos = `-- name: ListDeviceInfos :many
SELECT id, kind, data
FROM device_info
`
func (q *Queries) ListDeviceInfos(ctx context.Context) ([]DeviceInfo, error) {
rows, err := q.query(ctx, q.listDeviceInfosStmt, listDeviceInfos)
if err != nil {
return nil, err
}
defer rows.Close()
items := []DeviceInfo{}
for rows.Next() {
var i DeviceInfo
if err := rows.Scan(&i.ID, &i.Kind, &i.Data); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const replaceDeviceAssignment = `-- name: ReplaceDeviceAssignment :exec
REPLACE INTO device_assignment (id, created_date, ` + "`" + `match` + "`" + `, effect)
VALUES (?, ?, ?, ?)
`
type ReplaceDeviceAssignmentParams struct {
ID uuid.UUID
CreatedDate time.Time
Match string
Effect json.RawMessage
}
func (q *Queries) ReplaceDeviceAssignment(ctx context.Context, arg ReplaceDeviceAssignmentParams) error {
_, err := q.exec(ctx, q.replaceDeviceAssignmentStmt, replaceDeviceAssignment,
arg.ID,
arg.CreatedDate,
arg.Match,
arg.Effect,
)
return err
}
const replaceDeviceAuth = `-- name: ReplaceDeviceAuth :exec
REPLACE INTO device_auth (id, api_key, extras)
VALUES (?, ?, ?)
`
type ReplaceDeviceAuthParams struct {
ID string
ApiKey string
Extras json.RawMessage
}
func (q *Queries) ReplaceDeviceAuth(ctx context.Context, arg ReplaceDeviceAuthParams) error {
_, err := q.exec(ctx, q.replaceDeviceAuthStmt, replaceDeviceAuth, arg.ID, arg.ApiKey, arg.Extras)
return err
}
const replaceDeviceInfo = `-- name: ReplaceDeviceInfo :exec
REPLACE INTO device_info (id, kind, data)
VALUES (?, ?, ?)
`
type ReplaceDeviceInfoParams struct {
ID string
Kind string
Data json.RawMessage
}
func (q *Queries) ReplaceDeviceInfo(ctx context.Context, arg ReplaceDeviceInfoParams) error {
_, err := q.exec(ctx, q.replaceDeviceInfoStmt, replaceDeviceInfo, arg.ID, arg.Kind, arg.Data)
return err
}
+36
View File
@@ -0,0 +1,36 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
package mysqlgen
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
type DeviceAlias struct {
ID string
Alias string
}
type DeviceAssignment struct {
ID uuid.UUID
CreatedDate time.Time
Match string
Effect json.RawMessage
}
type DeviceAuth struct {
ID string
ApiKey string
Extras json.RawMessage
}
type DeviceInfo struct {
ID string
Kind string
Data json.RawMessage
}
+74
View File
@@ -0,0 +1,74 @@
-- name: ListDeviceInfos :many
SELECT *
FROM device_info;
-- name: ListDeviceAssignments :many
SELECT *
FROM device_assignment
ORDER BY created_date;
-- name: ListDeviceAuth :many
SELECT *
FROM device_auth;
-- name: ListDeviceAliases :many
SELECT *
FROM device_alias;
-- name: InsertDeviceAlias :exec
INSERT INTO device_alias (id, alias)
VALUES (?, ?);
-- name: ReplaceDeviceAuth :exec
REPLACE INTO device_auth (id, api_key, extras)
VALUES (?, ?, ?);
-- name: ReplaceDeviceInfo :exec
REPLACE INTO device_info (id, kind, data)
VALUES (?, ?, ?);
-- name: ReplaceDeviceAssignment :exec
REPLACE INTO device_assignment (id, created_date, `match`, effect)
VALUES (?, ?, ?, ?);
-- name: DeleteDeviceInfo :exec
DELETE
FROM device_info
WHERE id = ?
AND kind = ?;
-- name: DeleteDeviceInfoByID :exec
DELETE
FROM device_info
WHERE id = ?;
-- name: DeleteDeviceInfoLike :exec
DELETE
FROM device_info
WHERE id LIKE ?;
-- name: DeleteDeviceAssignment :exec
DELETE
FROM device_assignment
WHERE id = ?;
-- name: DeleteDeviceAuth :exec
DELETE
FROM device_auth
WHERE id = ?;
-- name: DeleteDeviceAlias :exec
DELETE
FROM device_alias
WHERE id = ?
AND alias = ?;
-- name: DeleteDeviceAliasByID :exec
DELETE
FROM device_alias
WHERE id = ?;
-- name: DeleteDeviceAliasByIDLike :exec
DELETE
FROM device_alias
WHERE id LIKE ?;
+321
View File
@@ -0,0 +1,321 @@
package mysqldb
import (
"context"
"database/sql"
"encoding/json"
"fmt"
lucifer3 "git.aiterp.net/lucifer3/server"
"git.aiterp.net/lucifer3/server/commands"
"git.aiterp.net/lucifer3/server/effects"
"git.aiterp.net/lucifer3/server/events"
"git.aiterp.net/lucifer3/server/internal/gentools"
"git.aiterp.net/lucifer3/server/services/mysqldb/mysqlgen"
"time"
_ "github.com/go-sql-driver/mysql"
)
type database struct {
db *sql.DB
}
func (d *database) Active() bool {
return true
}
func (d *database) HandleEvent(bus *lucifer3.EventBus, event lucifer3.Event) {
q := mysqlgen.New(d.db)
timeout, cancel := context.WithTimeout(context.Background(), time.Second/2)
defer cancel()
switch event := event.(type) {
case events.Started:
timeout, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Fetch all aliases first
aliases, err := q.ListDeviceAliases(timeout)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_list_aliases",
Message: "Database could not list aliases: " + err.Error(),
})
}
auths, err := q.ListDeviceAuth(timeout)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_list_device_infos",
Message: "Hardware State serialization failed: " + err.Error(),
})
}
for _, auth := range auths {
bus.RunCommand(commands.ConnectDevice{
ID: auth.ID,
APIKey: auth.ApiKey,
})
}
infos, err := q.ListDeviceInfos(timeout)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_list_device_infos",
Message: "Hardware State serialization failed: " + err.Error(),
})
}
for _, info := range infos {
switch info.Kind {
case "hardware_state":
staleEvent := events.HardwareState{}
err := json.Unmarshal(info.Data, &staleEvent)
if err == nil {
bus.RunEvent(staleEvent)
}
case "hardware_metadata":
staleEvent := events.HardwareMetadata{}
err := json.Unmarshal(info.Data, &staleEvent)
if err == nil {
bus.RunEvent(staleEvent)
}
}
}
// Run the aliases now
for _, alias := range aliases {
bus.RunCommand(commands.AddAlias{
Match: alias.ID,
Alias: alias.Alias,
})
}
assignments, err := q.ListDeviceAssignments(timeout)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_list_assignments",
Message: "Database could not list assignments: " + err.Error(),
})
}
for _, ass := range assignments {
effect := effects.Serializable{}
err := json.Unmarshal(ass.Effect, &effect)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_deserialize_effect",
Message: err.Error(),
})
continue
}
bus.RunCommand(commands.Assign{
ID: gentools.ShallowCopy(&ass.ID),
Match: ass.Match,
Effect: effect.Effect,
})
}
case events.DeviceAccepted:
if event.Extras == nil {
event.Extras = map[string]string{}
}
extras, err := json.Marshal(event.Extras)
if err != nil {
extras = []byte("{}")
}
err = q.ReplaceDeviceAuth(timeout, mysqlgen.ReplaceDeviceAuthParams{
ID: event.ID,
ApiKey: event.APIKey,
Extras: extras,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_save_device_auth",
Message: "Device auth save failed: " + err.Error(),
})
}
case events.HardwareState:
if event.Stale {
return
}
event.Stale = true
serialized, err := json.Marshal(event)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_serialize_hardware_state",
Message: "Hardware State serialization failed: " + err.Error(),
})
return
}
err = q.ReplaceDeviceInfo(timeout, mysqlgen.ReplaceDeviceInfoParams{
ID: event.ID,
Kind: "hardware_state",
Data: serialized,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_save_hardware_state",
Message: err.Error(),
})
return
}
case events.HardwareMetadata:
if event.Stale {
return
}
event.Stale = true
serialized, err := json.Marshal(event)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_serialize_hardware_state",
Message: "Hardware State serialization failed: " + err.Error(),
})
return
}
err = q.ReplaceDeviceInfo(timeout, mysqlgen.ReplaceDeviceInfoParams{
ID: event.ID,
Kind: "hardware_metadata",
Data: serialized,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_save_hardware_state",
Message: err.Error(),
})
return
}
case events.AssignmentCreated:
serialized, err := json.Marshal(&effects.Serializable{Effect: event.Effect})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_serialize_effect",
Message: err.Error(),
})
return
}
err = q.ReplaceDeviceAssignment(timeout, mysqlgen.ReplaceDeviceAssignmentParams{
ID: event.ID,
CreatedDate: time.Now().UTC(),
Match: event.Match,
Effect: serialized,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_save_assignment",
Message: err.Error(),
})
return
}
case events.AssignmentRemoved:
err := q.DeleteDeviceAssignment(timeout, event.ID)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_remove_assignment",
Message: err.Error(),
})
return
}
case events.AliasAdded:
err := q.InsertDeviceAlias(timeout, mysqlgen.InsertDeviceAliasParams{
ID: event.ID,
Alias: event.Alias,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_remove_assignment",
Message: err.Error(),
})
return
}
case events.AliasRemoved:
err := q.DeleteDeviceAlias(timeout, mysqlgen.DeleteDeviceAliasParams{
ID: event.ID,
Alias: event.Alias,
})
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_remove_assignment",
Message: err.Error(),
})
return
}
case events.DeviceForgotten:
err := q.DeleteDeviceInfoByID(timeout, event.ID)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_remove_info",
Message: "Failed to remove device info: " + err.Error(),
})
}
_ = q.DeleteDeviceAuth(timeout, event.ID)
err = q.DeleteDeviceAliasByID(timeout, event.ID)
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_remove_aliases",
Message: "Failed to remove device aliases: " + err.Error(),
})
}
err = q.DeleteDeviceAliasByIDLike(timeout, event.ID+":%")
if err != nil {
bus.RunEvent(events.Log{
Level: "error",
Code: "database_could_not_remove_sub_aliases",
Message: "Failed to remove sub-device aliases: " + err.Error(),
})
}
}
}
func (d *database) HandleCommand(*lucifer3.EventBus, lucifer3.Command) {}
func Connect(host string, port int, username, password, dbname string) (lucifer3.ActiveService, error) {
db, err := sql.Open("mysql", fmt.Sprintf(
"%s:%s@(%s:%d)/%s?parseTime=true", username, password, host, port, dbname,
))
if err != nil {
return nil, err
}
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(time.Minute)
err = db.Ping()
if err != nil {
return nil, err
}
return &database{db: db}, nil
}
@@ -0,0 +1,36 @@
package sqltypes
import (
"database/sql/driver"
"encoding/json"
"errors"
)
type NullRawMessage struct {
RawMessage json.RawMessage
Valid bool
}
func (n *NullRawMessage) Scan(value interface{}) error {
if value == nil {
n.RawMessage, n.Valid = json.RawMessage{}, false
return nil
}
buf, ok := value.([]byte)
if !ok {
return errors.New("cannot parse to bytes")
}
n.RawMessage, n.Valid = buf, true
return nil
}
func (n NullRawMessage) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return []byte(n.RawMessage), nil
}
+27 -3
View File
@@ -79,12 +79,27 @@ func (r *Resolver) HandleEvent(_ *lucifer3.EventBus, event lucifer3.Event) {
}
}
func (r *Resolver) HandleCommand(_ *lucifer3.EventBus, command lucifer3.Command) {
func (r *Resolver) HandleCommand(bus *lucifer3.EventBus, command lucifer3.Command) {
var aliasEvents []lucifer3.Event
switch command := command.(type) {
case commands.AddAlias:
r.mu.Lock()
for _, ptr := range r.resolve(command.Match) {
ptr.AddAlias(command.Alias)
added, removed := ptr.AddAlias(command.Alias)
if added != nil {
aliasEvents = append(aliasEvents, events.AliasAdded{
ID: ptr.ID,
Alias: *added,
})
}
if removed != nil {
aliasEvents = append(aliasEvents, events.AliasRemoved{
ID: ptr.ID,
Alias: *removed,
})
}
}
if strings.HasPrefix(command.Alias, "lucifer:name:") {
r.sortPointers()
@@ -94,13 +109,22 @@ func (r *Resolver) HandleCommand(_ *lucifer3.EventBus, command lucifer3.Command)
case commands.RemoveAlias:
r.mu.Lock()
for _, ptr := range r.resolve(command.Match) {
ptr.RemoveAlias(command.Alias)
if ptr.RemoveAlias(command.Alias) {
aliasEvents = append(aliasEvents, events.AliasRemoved{
ID: ptr.ID,
Alias: command.Alias,
})
}
}
if strings.HasPrefix(command.Alias, "lucifer:name:") {
r.sortPointers()
}
r.mu.Unlock()
}
if len(aliasEvents) > 0 {
go bus.RunEvents(aliasEvents)
}
}
func (r *Resolver) ensure(id string) *device.Pointer {