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.

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