From eba79eb701f86511a11419860329cb9a47920ab4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20B=C3=BChler?= <david.buhler@cea.fr>
Date: Fri, 24 Sep 2021 10:56:39 +0200
Subject: [PATCH] [server] Adds a synchronized array of emitted messages.

---
 .../api/generated/kernel/services/index.ts    | 93 +++++++++++++++++++
 src/plugins/server/kernel_main.ml             | 43 +++++++++
 2 files changed, 136 insertions(+)

diff --git a/ivette/src/frama-c/api/generated/kernel/services/index.ts b/ivette/src/frama-c/api/generated/kernel/services/index.ts
index fa99770d45e..bda3ec1dbb7 100644
--- a/ivette/src/frama-c/api/generated/kernel/services/index.ts
+++ b/ivette/src/frama-c/api/generated/kernel/services/index.ts
@@ -150,6 +150,99 @@ const logkindTags_internal: Server.GetRequest<null,tag[]> = {
 /** Registered tags for the above type. */
 export const logkindTags: Server.GetRequest<null,tag[]>= logkindTags_internal;
 
+/** Data for array rows [`message`](#message)  */
+export interface messageData {
+  /** Entry identifier. */
+  key: Json.key<'#message'>;
+  /** Message kind */
+  kind: logkind;
+  /** Emitter plugin */
+  plugin: string;
+  /** Message text */
+  message: string;
+  /** Message category (only for debug or warning messages) */
+  category?: string;
+  /** Source file position */
+  source?: source;
+}
+
+/** Loose decoder for `messageData` */
+export const jMessageData: Json.Loose<messageData> =
+  Json.jObject({
+    key: Json.jFail(Json.jKey<'#message'>('#message'),'#message expected'),
+    kind: jLogkindSafe,
+    plugin: Json.jFail(Json.jString,'String expected'),
+    message: Json.jFail(Json.jString,'String expected'),
+    category: Json.jString,
+    source: jSource,
+  });
+
+/** Safe decoder for `messageData` */
+export const jMessageDataSafe: Json.Safe<messageData> =
+  Json.jFail(jMessageData,'MessageData expected');
+
+/** Natural order for `messageData` */
+export const byMessageData: Compare.Order<messageData> =
+  Compare.byFields
+    <{ key: Json.key<'#message'>, kind: logkind, plugin: string,
+       message: string, category?: string, source?: source }>({
+    key: Compare.string,
+    kind: byLogkind,
+    plugin: Compare.alpha,
+    message: Compare.string,
+    category: Compare.defined(Compare.string),
+    source: Compare.defined(bySource),
+  });
+
+/** Signal for array [`message`](#message)  */
+export const signalMessage: Server.Signal = {
+  name: 'kernel.services.signalMessage',
+};
+
+const reloadMessage_internal: Server.GetRequest<null,null> = {
+  kind: Server.RqKind.GET,
+  name:   'kernel.services.reloadMessage',
+  input:  Json.jNull,
+  output: Json.jNull,
+  signals: [],
+};
+/** Force full reload for array [`message`](#message)  */
+export const reloadMessage: Server.GetRequest<null,null>= reloadMessage_internal;
+
+const fetchMessage_internal: Server.GetRequest<
+  number,
+  { pending: number, updated: messageData[], removed: Json.key<'#message'>[],
+    reload: boolean }
+  > = {
+  kind: Server.RqKind.GET,
+  name:   'kernel.services.fetchMessage',
+  input:  Json.jNumber,
+  output: Json.jObject({
+            pending: Json.jFail(Json.jNumber,'Number expected'),
+            updated: Json.jList(jMessageData),
+            removed: Json.jList(Json.jKey<'#message'>('#message')),
+            reload: Json.jFail(Json.jBoolean,'Boolean expected'),
+          }),
+  signals: [],
+};
+/** Data fetcher for array [`message`](#message)  */
+export const fetchMessage: Server.GetRequest<
+  number,
+  { pending: number, updated: messageData[], removed: Json.key<'#message'>[],
+    reload: boolean }
+  >= fetchMessage_internal;
+
+const message_internal: State.Array<Json.key<'#message'>,messageData> = {
+  name: 'kernel.services.message',
+  getkey: ((d:messageData) => d.key),
+  signal: signalMessage,
+  fetch: fetchMessage,
+  reload: reloadMessage,
+  order: byMessageData,
+};
+/** Log messages */
+export const message: State.Array<Json.key<'#message'>,messageData> = message_internal;
+
 /** Message event record. */
 export interface log {
   /** Message kind */
diff --git a/src/plugins/server/kernel_main.ml b/src/plugins/server/kernel_main.ml
index 3c5fb34a461..1c0e569403a 100644
--- a/src/plugins/server/kernel_main.ml
+++ b/src/plugins/server/kernel_main.ml
@@ -166,6 +166,49 @@ struct
   include (val data : S with type t = Log.kind)
 end
 
+(* -------------------------------------------------------------------------- *)
+(* --- Synchronized array of log events                                   --- *)
+(* -------------------------------------------------------------------------- *)
+
+let model = States.model ()
+
+let () = States.column model ~name:"kind"
+    ~descr:(Md.plain "Message kind")
+    ~data:(module LogKind)
+    ~get:(fun (evt, _) -> evt.Log.evt_kind)
+
+let () = States.column model ~name:"plugin"
+    ~descr:(Md.plain "Emitter plugin")
+    ~data:(module Jalpha)
+    ~get:(fun (evt, _) -> evt.Log.evt_plugin)
+
+let () = States.column model ~name:"message"
+    ~descr:(Md.plain "Message text")
+    ~data:(module Jstring)
+    ~get:(fun (evt, _) -> evt.Log.evt_message)
+
+let () = States.option model ~name:"category"
+    ~descr:(Md.plain "Message category (only for debug or warning messages)")
+    ~data:(module Jstring)
+    ~get:(fun (evt, _) -> evt.Log.evt_category)
+
+let () = States.option model ~name:"source"
+    ~descr:(Md.plain "Source file position")
+    ~data:(module LogSource)
+    ~get:(fun (evt, _) -> evt.Log.evt_source)
+
+let iter f = ignore (Messages.fold (fun i evt -> f (evt, i); succ i) 0)
+
+let _array =
+  States.register_array
+    ~package
+    ~name:"message"
+    ~descr:(Md.plain "Log messages")
+    ~key:(fun (_evt, i) -> string_of_int i)
+    ~iter:iter
+    ~add_reload_hook:Messages.add_global_hook
+    model
+
 (* -------------------------------------------------------------------------- *)
 (* --- Log Events                                                         --- *)
 (* -------------------------------------------------------------------------- *)
-- 
GitLab