CapabilityService.vala 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Invercargill.Expressions;
  4. using InvercargillSql.Orm;
  5. using Inversion;
  6. using ClankerSaddle.Data;
  7. namespace ClankerSaddle.Services {
  8. /**
  9. * Capability storage: CRUD over `capabilities` plus their saddle and
  10. * task attachments, and the tag-matching predicate the dispatcher
  11. * routes by — a saddle is eligible for a task when its capability
  12. * set is a superset of the task's required set (an empty task set
  13. * matches any saddle).
  14. */
  15. public class CapabilityService : Object {
  16. private OrmSession db = inject<OrmSession>();
  17. /**
  18. * Inserts a capability; names are unique. Returns the stored row
  19. * (with its id).
  20. */
  21. public async Capability create_capability(string name) throws Error {
  22. if (name.strip().length == 0) {
  23. throw new Error(Quark.from_string("clanker-saddle"), 40,
  24. "A capability name is required");
  25. }
  26. var existing = yield db.query<Capability>()
  27. .where(expr("name == $0", elem(name.strip())))
  28. .first_async();
  29. if (existing != null) {
  30. throw new Error(Quark.from_string("clanker-saddle"), 41,
  31. @"A capability named '$(name)' already exists");
  32. }
  33. var capability = new Capability() {
  34. name = name.strip(),
  35. created_at = new DateTime.now_local()
  36. };
  37. yield db.insert_async<Capability>(capability);
  38. return capability;
  39. }
  40. /** Loads the capability identified by `id`, or null. */
  41. public async Capability? get_capability(int64 id) throws Error {
  42. return yield db.query<Capability>()
  43. .where(expr("id == $0", elem<int64?>(id)))
  44. .first_async();
  45. }
  46. /**
  47. * The capability named exactly `name`, or null — the subtask
  48. * callback's tag validation lookup (names are unique).
  49. */
  50. public async Capability? find_by_name(string name) throws Error {
  51. return yield db.query<Capability>()
  52. .where(expr("name == $0", elem(name)))
  53. .first_async();
  54. }
  55. /** Every capability ordered by name. */
  56. public async ImmutableLot<Capability> list_capabilities() throws Error {
  57. return yield db.query<Capability>()
  58. .order_by(expr("name"))
  59. .materialise_async();
  60. }
  61. /**
  62. * Deletes a capability and every attachment referencing it (a
  63. * saddle stops carrying it, a task stops requiring it).
  64. */
  65. public async void delete_capability(int64 id) throws Error {
  66. foreach (var link in yield db.query<TaskCapability>()
  67. .where(expr("capability_id == $0", elem<int64?>(id)))
  68. .materialise_async()) {
  69. yield db.delete_async<TaskCapability>(link);
  70. }
  71. foreach (var link in yield db.query<SaddleCapability>()
  72. .where(expr("capability_id == $0", elem<int64?>(id)))
  73. .materialise_async()) {
  74. yield db.delete_async<SaddleCapability>(link);
  75. }
  76. var capability = yield get_capability(id);
  77. if (capability != null) {
  78. yield db.delete_async<Capability>((!)capability);
  79. }
  80. }
  81. /**
  82. * Attaches `capability_id` to `saddle_id`; duplicates are a no-op
  83. * (the pair is unique).
  84. */
  85. public async void attach_to_saddle(int64 capability_id, int64 saddle_id) throws Error {
  86. var capability = yield get_capability(capability_id);
  87. if (capability == null) {
  88. throw new Error(Quark.from_string("clanker-saddle"), 42,
  89. @"No capability exists with id $(capability_id)");
  90. }
  91. var existing = yield db.query<SaddleCapability>()
  92. .where(expr("capability_id == $0 && saddle_id == $1",
  93. elem<int64?>(capability_id), elem<int64?>(saddle_id)))
  94. .materialise_async();
  95. if (existing.length > 0) {
  96. return;
  97. }
  98. yield db.insert_async<SaddleCapability>(new SaddleCapability() {
  99. capability_id = capability_id,
  100. saddle_id = saddle_id
  101. });
  102. }
  103. /** Detaches the `saddle_capabilities` row identified by `id`. */
  104. public async void detach_from_saddle(int64 link_id) throws Error {
  105. var link = yield db.query<SaddleCapability>()
  106. .where(expr("id == $0", elem<int64?>(link_id)))
  107. .first_async();
  108. if (link != null) {
  109. yield db.delete_async<SaddleCapability>((!)link);
  110. }
  111. }
  112. /** The `saddle_capabilities` attachments of `saddle_id`, oldest first. */
  113. public async ImmutableLot<SaddleCapability> list_saddle_attachments(int64 saddle_id) throws Error {
  114. return yield db.query<SaddleCapability>()
  115. .where(expr("saddle_id == $0", elem<int64?>(saddle_id)))
  116. .order_by(expr("id"))
  117. .materialise_async();
  118. }
  119. /** The capabilities a saddle carries, attachment order. */
  120. public async ImmutableLot<Capability> list_for_saddle(int64 saddle_id) throws Error {
  121. var rows = new Vector<Capability>();
  122. foreach (var link in yield list_saddle_attachments(saddle_id)) {
  123. var capability = yield get_capability(link.capability_id);
  124. if (capability != null) {
  125. rows.add((!)capability);
  126. }
  127. }
  128. return rows.to_immutable_buffer();
  129. }
  130. /**
  131. * Attaches `capability_id` to `task_id` (a required capability);
  132. * duplicates are a no-op (the pair is unique).
  133. */
  134. public async void attach_to_task(int64 capability_id, int64 task_id) throws Error {
  135. var capability = yield get_capability(capability_id);
  136. if (capability == null) {
  137. throw new Error(Quark.from_string("clanker-saddle"), 42,
  138. @"No capability exists with id $(capability_id)");
  139. }
  140. var existing = yield db.query<TaskCapability>()
  141. .where(expr("capability_id == $0 && task_id == $1",
  142. elem<int64?>(capability_id), elem<int64?>(task_id)))
  143. .materialise_async();
  144. if (existing.length > 0) {
  145. return;
  146. }
  147. yield db.insert_async<TaskCapability>(new TaskCapability() {
  148. capability_id = capability_id,
  149. task_id = task_id
  150. });
  151. }
  152. /** Detaches the `task_capabilities` row identified by `id`. */
  153. public async void detach_from_task(int64 link_id) throws Error {
  154. var link = yield db.query<TaskCapability>()
  155. .where(expr("id == $0", elem<int64?>(link_id)))
  156. .first_async();
  157. if (link != null) {
  158. yield db.delete_async<TaskCapability>((!)link);
  159. }
  160. }
  161. /** The `task_capabilities` attachments of `task_id`, oldest first. */
  162. public async ImmutableLot<TaskCapability> list_task_attachments(int64 task_id) throws Error {
  163. return yield db.query<TaskCapability>()
  164. .where(expr("task_id == $0", elem<int64?>(task_id)))
  165. .order_by(expr("id"))
  166. .materialise_async();
  167. }
  168. /** The capabilities a task requires, attachment order. */
  169. public async ImmutableLot<Capability> list_for_task(int64 task_id) throws Error {
  170. var rows = new Vector<Capability>();
  171. foreach (var link in yield list_task_attachments(task_id)) {
  172. var capability = yield get_capability(link.capability_id);
  173. if (capability != null) {
  174. rows.add((!)capability);
  175. }
  176. }
  177. return rows.to_immutable_buffer();
  178. }
  179. /** Every `saddle_capabilities` row (for dispatch-time batching). */
  180. public async ImmutableLot<SaddleCapability> all_saddle_attachments() throws Error {
  181. return yield db.query<SaddleCapability>().materialise_async();
  182. }
  183. /** Every `task_capabilities` row (for usage counts and routing). */
  184. public async ImmutableLot<TaskCapability> all_task_attachments() throws Error {
  185. return yield db.query<TaskCapability>().materialise_async();
  186. }
  187. /**
  188. * True when `saddle_id`'s capability set is a superset of
  189. * `task_id`'s required set — the dispatcher's eligibility check.
  190. */
  191. public async bool saddle_covers_task(int64 saddle_id, int64 task_id) throws Error {
  192. var required = new HashSet<string>();
  193. foreach (var capability in yield list_for_task(task_id)) {
  194. required.add(capability.name);
  195. }
  196. if (required.length == 0) {
  197. return true;
  198. }
  199. foreach (var capability in yield list_for_saddle(saddle_id)) {
  200. required.remove(capability.name);
  201. }
  202. return required.length == 0;
  203. }
  204. }
  205. }