You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
8.3 KiB

  1. package mysql
  2. import (
  3. "context"
  4. "encoding/json"
  5. "git.aiterp.net/lucifer/new-server/models"
  6. sq "github.com/Masterminds/squirrel"
  7. "github.com/jmoiron/sqlx"
  8. "log"
  9. "strings"
  10. )
  11. type deviceRecord struct {
  12. ID int `db:"id"`
  13. BridgeID int `db:"bridge_id"`
  14. InternalID string `db:"internal_id"`
  15. Icon string `db:"icon"`
  16. Name string `db:"name"`
  17. Capabilities string `db:"capabilities"`
  18. ButtonNames string `db:"button_names"`
  19. }
  20. type deviceStateRecord struct {
  21. DeviceID int `db:"device_id"`
  22. Hue float64 `db:"hue"`
  23. Saturation float64 `db:"saturation"`
  24. Kelvin int `db:"kelvin"`
  25. Power bool `db:"power"`
  26. Intensity float64 `db:"intensity"`
  27. }
  28. type devicePropertyRecord struct {
  29. DeviceID int `db:"device_id"`
  30. Key string `db:"prop_key"`
  31. Value string `db:"prop_value"`
  32. IsUser bool `db:"is_user"`
  33. }
  34. type deviceTagRecord struct {
  35. DeviceID int `db:"device_id"`
  36. TagName string `db:"tag_name"`
  37. }
  38. type DeviceRepo struct {
  39. DBX *sqlx.DB
  40. }
  41. func (r *DeviceRepo) Find(ctx context.Context, id int) (*models.Device, error) {
  42. var device deviceRecord
  43. err := r.DBX.GetContext(ctx, &device, "SELECT * FROM device WHERE id = ?", id)
  44. if err != nil {
  45. return nil, dbErr(err)
  46. }
  47. return r.populateOne(ctx, device)
  48. }
  49. func (r *DeviceRepo) FetchByReference(ctx context.Context, kind models.ReferenceKind, value string) ([]models.Device, error) {
  50. q := sq.Select("device.*").From("device")
  51. switch kind {
  52. case models.RKDeviceID:
  53. q = q.Where(sq.Eq{"id": strings.Split(value, ",")})
  54. case models.RKBridgeID:
  55. q = q.Where(sq.Eq{"bridge_id": strings.Split(value, ",")})
  56. case models.RKTag:
  57. q = q.Join("device_tag dt ON device.id=dt.device_id").Where(sq.Eq{"dt.tag_name": strings.Split(value, ",")})
  58. case models.RKAll:
  59. default:
  60. log.Println("Unknown reference kind used for device fetch:", kind)
  61. return []models.Device{}, nil
  62. }
  63. query, args, err := q.ToSql()
  64. if err != nil {
  65. return nil, dbErr(err)
  66. }
  67. records := make([]deviceRecord, 0, 8)
  68. err = r.DBX.SelectContext(ctx, &records, query, args...)
  69. if err != nil {
  70. return nil, dbErr(err)
  71. }
  72. return r.populate(ctx, records)
  73. }
  74. func (r *DeviceRepo) Save(ctx context.Context, device *models.Device) error {
  75. tx, err := r.DBX.Beginx()
  76. if err != nil {
  77. return dbErr(err)
  78. }
  79. defer tx.Rollback()
  80. record := deviceRecord{
  81. ID: device.ID,
  82. BridgeID: device.BridgeID,
  83. InternalID: device.InternalID,
  84. Icon: device.Icon,
  85. Name: device.Name,
  86. Capabilities: strings.Join(models.DeviceCapabilitiesToStrings(device.Capabilities), ","),
  87. ButtonNames: strings.Join(device.ButtonNames, ","),
  88. }
  89. if device.ID > 0 {
  90. _, err := tx.NamedExecContext(ctx, `
  91. UPDATE device SET
  92. internal_id = :internal_id,
  93. icon = :icon,
  94. name = :name,
  95. capabilities = :capabilities,
  96. button_names = :button_names
  97. WHERE id=:id
  98. `, record)
  99. if err != nil {
  100. return dbErr(err)
  101. }
  102. // Let's just be lazy for now, optimize later if need be.
  103. _, err = tx.ExecContext(ctx, "DELETE FROM device_tag WHERE device_id=?", record.ID)
  104. if err != nil {
  105. return dbErr(err)
  106. }
  107. _, err = tx.ExecContext(ctx, "DELETE FROM device_property WHERE device_id=?", record.ID)
  108. if err != nil {
  109. return dbErr(err)
  110. }
  111. } else {
  112. res, err := tx.NamedExecContext(ctx, `
  113. INSERT INTO device (bridge_id, internal_id, icon, name, capabilities, button_names)
  114. VALUES (:bridge_id, :internal_id, :icon, :name, :capabilities, :button_names)
  115. `, record)
  116. if err != nil {
  117. return dbErr(err)
  118. }
  119. lastID, err := res.LastInsertId()
  120. if err != nil {
  121. return dbErr(err)
  122. }
  123. record.ID = int(lastID)
  124. device.ID = int(lastID)
  125. }
  126. for _, tag := range device.Tags {
  127. _, err := tx.ExecContext(ctx, "INSERT INTO device_tag (device_id, tag_name) VALUES (?, ?)", record.ID, tag)
  128. if err != nil {
  129. return dbErr(err)
  130. }
  131. }
  132. for key, value := range device.UserProperties {
  133. _, err := tx.ExecContext(ctx, "INSERT INTO device_property (device_id, prop_key, prop_value, is_user) VALUES (?, ?, ?, 1)",
  134. record.ID, key, value,
  135. )
  136. if err != nil {
  137. return dbErr(err)
  138. }
  139. }
  140. for key, value := range device.DriverProperties {
  141. j, err := json.Marshal(value)
  142. if err != nil {
  143. // Eh, it'll get filled by the driver anyway
  144. continue
  145. }
  146. _, err = tx.ExecContext(ctx, "INSERT INTO device_property (device_id, prop_key, prop_value, is_user) VALUES (?, ?, ?, 0)",
  147. record.ID, key, string(j),
  148. )
  149. if err != nil {
  150. // Return err here anyway, it might put the tx in a bad state to ignore it.
  151. return dbErr(err)
  152. }
  153. }
  154. _, err = tx.NamedExecContext(ctx, `
  155. REPLACE INTO device_state(device_id, hue, saturation, kelvin, power, intensity)
  156. VALUES (:device_id, :hue, :saturation, :kelvin, :power, :intensity)
  157. `, deviceStateRecord{
  158. DeviceID: record.ID,
  159. Hue: device.State.Color.Hue,
  160. Saturation: device.State.Color.Saturation,
  161. Kelvin: device.State.Color.Kelvin,
  162. Power: device.State.Power,
  163. Intensity: device.State.Intensity,
  164. })
  165. if err != nil {
  166. return dbErr(err)
  167. }
  168. return tx.Commit()
  169. }
  170. func (r *DeviceRepo) Delete(ctx context.Context, device *models.Device) error {
  171. _, err := r.DBX.ExecContext(ctx, "DELETE FROM device WHERE Id=?", device.ID)
  172. if err != nil {
  173. return dbErr(err)
  174. }
  175. return nil
  176. }
  177. func (r *DeviceRepo) populateOne(ctx context.Context, record deviceRecord) (*models.Device, error) {
  178. records, err := r.populate(ctx, []deviceRecord{record})
  179. if err != nil {
  180. return nil, err
  181. }
  182. return &records[0], nil
  183. }
  184. func (r *DeviceRepo) populate(ctx context.Context, records []deviceRecord) ([]models.Device, error) {
  185. if len(records) == 0 {
  186. return []models.Device{}, nil
  187. }
  188. ids := make([]int, 0, len(records))
  189. for _, record := range records {
  190. ids = append(ids, record.ID)
  191. }
  192. tagsQuery, tagsArgs, err := sq.Select("*").From("device_tag").Where(sq.Eq{"device_id": ids}).ToSql()
  193. if err != nil {
  194. return nil, dbErr(err)
  195. }
  196. propsQuery, propsArgs, err := sq.Select("*").From("device_property").Where(sq.Eq{"device_id": ids}).ToSql()
  197. if err != nil {
  198. return nil, dbErr(err)
  199. }
  200. stateQuery, stateArgs, err := sq.Select("*").From("device_state").Where(sq.Eq{"device_id": ids}).ToSql()
  201. if err != nil {
  202. return nil, dbErr(err)
  203. }
  204. states := make([]deviceStateRecord, 0, len(records))
  205. props := make([]devicePropertyRecord, 0, len(records)*8)
  206. tags := make([]deviceTagRecord, 0, len(records)*4)
  207. err = r.DBX.SelectContext(ctx, &states, stateQuery, stateArgs...)
  208. if err != nil {
  209. return nil, dbErr(err)
  210. }
  211. err = r.DBX.SelectContext(ctx, &props, propsQuery, propsArgs...)
  212. if err != nil {
  213. return nil, dbErr(err)
  214. }
  215. err = r.DBX.SelectContext(ctx, &tags, tagsQuery, tagsArgs...)
  216. if err != nil {
  217. return nil, dbErr(err)
  218. }
  219. devices := make([]models.Device, 0, len(records))
  220. for _, record := range records {
  221. device := models.Device{
  222. ID: record.ID,
  223. BridgeID: record.BridgeID,
  224. InternalID: record.InternalID,
  225. Icon: record.Icon,
  226. Name: record.Name,
  227. ButtonNames: strings.Split(record.ButtonNames, ","),
  228. DriverProperties: make(map[string]interface{}, 8),
  229. UserProperties: make(map[string]string, 8),
  230. Tags: make([]string, 0, 8),
  231. }
  232. if device.ButtonNames[0] == "" {
  233. device.ButtonNames = device.ButtonNames[:0]
  234. }
  235. caps := make([]models.DeviceCapability, 0, 16)
  236. for _, capStr := range strings.Split(record.Capabilities, ",") {
  237. caps = append(caps, models.DeviceCapability(capStr))
  238. }
  239. device.Capabilities = caps
  240. for _, state := range states {
  241. if state.DeviceID == record.ID {
  242. device.State = models.DeviceState{
  243. Power: state.Power,
  244. Color: models.ColorValue{
  245. Hue: state.Hue,
  246. Saturation: state.Saturation,
  247. Kelvin: state.Kelvin,
  248. },
  249. Intensity: state.Intensity,
  250. }
  251. }
  252. }
  253. driverProps := make(map[string]json.RawMessage, 8)
  254. for _, prop := range props {
  255. if prop.DeviceID == record.ID {
  256. if prop.IsUser {
  257. device.UserProperties[prop.Key] = prop.Value
  258. } else {
  259. driverProps[prop.Key] = json.RawMessage(prop.Value)
  260. }
  261. }
  262. }
  263. if len(driverProps) > 0 {
  264. j, err := json.Marshal(driverProps)
  265. if err != nil {
  266. return nil, dbErr(err)
  267. }
  268. err = json.Unmarshal(j, &device.DriverProperties)
  269. if err != nil {
  270. return nil, dbErr(err)
  271. }
  272. }
  273. for _, tag := range tags {
  274. if tag.DeviceID == record.ID {
  275. device.Tags = append(device.Tags, tag.TagName)
  276. }
  277. }
  278. devices = append(devices, device)
  279. }
  280. return devices, nil
  281. }