<commit_before>require Logger

defmodule Serverboards.Plugin.Runner do
  @moduledoc ~S"""
  Server that stats plugins and connects the RPC channels.

  Every started plugin has an UUID as id, and that UUID is later used
  for succesive calls using the runner.

  An UUID is used as ID as it can be considered an opaque secret token to
  identify the clients that can be passed around.
  """
  use GenServer
  alias Serverboards.Plugin


  def start_link (options \\ []) do
    {:ok, pid} = GenServer.start_link __MODULE__, :ok, options

    method_caller = Serverboards.Plugin.Runner.method_caller()
    MOM.Channel.subscribe(:auth_authenticated, fn msg ->
      %{ user: _user, client: client} = msg.payload
      MOM.RPC.Client.add_method_caller client, method_caller
      :ok
    end)

    {:ok, pid}
  end

  @doc ~S"""
  Starts a component by name and adds it to the running registry.

  Example:

    iex> {:ok, cmd} = start("serverboards.test.auth/fake", "test")
    iex> call cmd, "ping"
    {:ok, "pong"}
    iex> stop(cmd)
    true

  Or if does not exist:

    iex> start("nonexistantplugin", "test")
    {:error, :not_found}

  It can also be called with a component struct:

    iex> component = hd (Serverboards.Plugin.Registry.filter_component trait: "auth")
    iex> {:ok, cmd} = start(component, "test")
    iex> call cmd, "ping"
    {:ok, "pong"}
    iex> stop(cmd)
    true

  """
  def start(%Serverboards.Plugin.Component{} = component, user) do
    plugin_id = cond do
      is_map(component.plugin) -> component.plugin.id
      true -> component.plugin
    end

    component = %Serverboards.Plugin.Component{component |
      plugin: plugin_id
    }
    # Logger.info("Start component #{inspect component.id}")
    GenServer.call(Serverboards.Plugin.Runner, {:start, component, user})
  end

  def start(plugin_component_id, user)
  when is_binary(plugin_component_id) do
      case Serverboards.Plugin.Registry.find(plugin_component_id) do
        nil ->
          Logger.error("Plugin component #{plugin_component_id} not found")
          {:error, :not_found}
        c ->
          start(c, user)
      end
  end

  def stop(uuid) do
    case GenServer.call(Serverboards.Plugin.Runner, {:stop, uuid}) do
      {:error, :cant_stop} -> # just do not log it, as it is quite normal
        #Logger.debug("Non stoppable plugin to stop #{inspect uuid}")
        {:error, :cant_stop}
      {:error, e} ->
        Logger.error("Error stopping component #{inspect e}")
        {:error, e}
      {:ok, cmd} ->
        #Logger.debug("Stop plugin #{inspect uuid}")
        Serverboards.IO.Cmd.stop cmd
        true
    end
  end

  @doc ~S"""
  Forces stop of the command, even for init and singleton commands

  This is required in some situations, as reloading of plugin code.
  """
  def kill(uuid) do
    case get(uuid) do
      %{ pid: pid } = data when is_pid(pid) ->
        Logger.info("Kill process #{uuid} / #{ inspect data.component.id }")
        GenServer.call(Serverboards.Plugin.Runner,  {:exit, uuid})
        if Process.alive?(pid) do # should be running, but have found the bug in the wild.
          Serverboards.IO.Cmd.stop pid
        end
        :ok
      other ->
        {:error, other}
    end
  end

  def get_by_component_id(component_id) do
    GenServer.call(Serverboards.Plugin.Runner, {:get_by_component_id, component_id})
  end<commit_msg>Improved logging for cant stop processes<commit_after>require Logger

defmodule Serverboards.Plugin.Runner do
  @moduledoc ~S"""
  Server that stats plugins and connects the RPC channels.

  Every started plugin has an UUID as id, and that UUID is later used
  for succesive calls using the runner.

  An UUID is used as ID as it can be considered an opaque secret token to
  identify the clients that can be passed around.
  """
  use GenServer
  alias Serverboards.Plugin


  def start_link (options \\ []) do
    {:ok, pid} = GenServer.start_link __MODULE__, :ok, options

    method_caller = Serverboards.Plugin.Runner.method_caller()
    MOM.Channel.subscribe(:auth_authenticated, fn msg ->
      %{ user: _user, client: client} = msg.payload
      MOM.RPC.Client.add_method_caller client, method_caller
      :ok
    end)

    {:ok, pid}
  end

  @doc ~S"""
  Starts a component by name and adds it to the running registry.

  Example:

    iex> {:ok, cmd} = start("serverboards.test.auth/fake", "test")
    iex> call cmd, "ping"
    {:ok, "pong"}
    iex> stop(cmd)
    true

  Or if does not exist:

    iex> start("nonexistantplugin", "test")
    {:error, :not_found}

  It can also be called with a component struct:

    iex> component = hd (Serverboards.Plugin.Registry.filter_component trait: "auth")
    iex> {:ok, cmd} = start(component, "test")
    iex> call cmd, "ping"
    {:ok, "pong"}
    iex> stop(cmd)
    true

  """
  def start(%Serverboards.Plugin.Component{} = component, user) do
    plugin_id = cond do
      is_map(component.plugin) -> component.plugin.id
      true -> component.plugin
    end

    component = %Serverboards.Plugin.Component{component |
      plugin: plugin_id
    }
    # Logger.info("Start component #{inspect component.id}")
    GenServer.call(Serverboards.Plugin.Runner, {:start, component, user})
  end

  def start(plugin_component_id, user)
  when is_binary(plugin_component_id) do
      case Serverboards.Plugin.Registry.find(plugin_component_id) do
        nil ->
          Logger.error("Plugin component #{plugin_component_id} not found")
          {:error, :not_found}
        c ->
          start(c, user)
      end
  end

  def stop(uuid) do
    case GenServer.call(Serverboards.Plugin.Runner, {:stop, uuid}) do
      {:error, :cant_stop} -> # just do not log it, as it is quite normal
        #Logger.debug("Non stoppable plugin to stop #{inspect uuid}")
        {:error, :cant_stop}
      {:error, e} ->
        Logger.error("Error stopping component #{inspect uuid}: #{inspect e}")
        {:error, e}
      {:ok, cmd} ->
        #Logger.debug("Stop plugin #{inspect uuid}")
        Serverboards.IO.Cmd.stop cmd
        true
    end
  end

  @doc ~S"""
  Forces stop of the command, even for init and singleton commands

  This is required in some situations, as reloading of plugin code.
  """
  def kill(uuid) do
    case get(uuid) do
      %{ pid: pid } = data when is_pid(pid) ->
        Logger.info("Kill process #{uuid} / #{ inspect data.component.id }")
        GenServer.call(Serverboards.Plugin.Runner,  {:exit, uuid})
        if Process.alive?(pid) do # should be running, but have found the bug in the wild.
          Serverboards.IO.Cmd.stop pid
        end
        :ok
      other ->
        {:error, other}
    end
  end

  def get_by_component_id(component_id) do
    GenServer.call(Serverboards.Plugin.Runner, {:get_by_component_id, component_id})
  end
{'fraction_non_alphanumeric': 0.11173020527859237, 'fraction_numerical': 0.0, 'mean_word_length': 2.8493227990970653, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1085355', 'n_tokens_mistral': 2175, 'n_tokens_neox': 2134, 'n_words': 666}