<commit_before>% Copyright (c) 2009, NorthScale, Inc.
% Copyright (c) 2008, <NAME>
% Copyright (c) 2008, Powerset, Inc
%
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions
% are met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in the
% documentation and/or other materials provided with the distribution.
% * Neither the name of Powerset, Inc nor the names of its
% contributors may be used to endorse or promote products derived from
% this software without specific prior written permission.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
% COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
%
% Original Author: <NAME>

-module(ns_config).

-behaviour(gen_server).

-define(METADATA_VER, '_ver').

-export([start_link/2, start_link/1, stop/0,
         get/2, get/1, get/0, set/2, set/1,
         search/2, search/1,
         search_prop/3, search_prop/4,
         search_raw/2]).

% A static config file is often hand edited.
% potentially with in-line manual comments.
%
% A dynamic config file is system generated and modified,
% such as due to changes from UI/admin-screen operations, or
% nodes getting added/removed, and gossiping about config
% information.
%
-record(config, {init,         % Initialization parameters.
                 static = [],  % List of TupleList's; TupleList is {K, V}.
                 dynamic = [], % List of TupleList's; TupleList is {K, V}.
                 policy_mod
                }).

%% gen_server callbacks

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-include_lib("eunit/include/eunit.hrl").

-ifdef(TEST).
-include("test/ns_config_test.erl").
-endif.

%% API

start_link(Full) ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, Full, []).

start_link(ConfigPath, PolicyMod) -> start_link([ConfigPath, PolicyMod]).

stop()   -> gen_server:cast(?MODULE, stop).
reinit() -> gen_server:call(?MODULE, reinit).

set(Key, PropList) when is_list(PropList) ->
    PropList2 = [{?METADATA_VER, erlang:now()} |
                 strip_metadata(PropList, [])],
    gen_server:call(?MODULE, {set, [{Key, PropList2}]});

set(Key, Val) -> gen_server:call(?MODULE, {set, [{Key, Val}]}).
set(KVList)   -> gen_server:call(?MODULE, {set, KVList}).

get()              -> gen_server:call(?MODULE, get).
get(Node)          -> ?MODULE:get(Node, 500).
get(Node, Timeout) -> gen_server:call({?MODULE, Node}, get, Timeout).

search(Key) -> search(?MODULE:get(), Key).

search(Config, Key) ->
    case search_raw(Config, Key) of
        false      -> false;
        {value, X} -> {value, strip_metadata(X, [])}
    end.

search_prop(Config, Key, SubKey) ->
    search_prop(Config, Key, SubKey, undefined).

search_prop(Config, Key, SubKey, DefaultSubVal) ->
    case search(Config, Key) of
        {value, PropList} ->
            proplists:get_value(SubKey, PropList, DefaultSubVal);
        false ->
            DefaultSubVal
    end.

% The search_raw API does not strip out metadata from results.

search_raw(undefined, _Key) -> false;
search_raw([], _Key)        -> false;
search_raw([KVList | Rest], Key) ->
    case lists:keysearch(Key, 1, KVList) of
        {value, {Key, V}} -> {value, V};
        _                 -> search_raw(Rest, Key)
    end;
search_raw(#config{dynamic = DL, static = SL}, Key) ->
    case search_raw(DL, Key) of
        {value, _} = R -> R;
        false          -> search_raw(SL, Key)
    end.

%% Implementation

% Removes metadata like METADATA_VER from results.

strip_metadata([], Acc)                       -> lists:reverse(Acc);
strip_metadata([{?METADATA_VER, _} | T], Acc) -> strip_metadata(T, Acc);
strip_metadata([X | T], Acc)                  -> strip_metadata(T, [X | Acc]);
strip_metadata(X, _)                          -> X.

%% gen_server callbacks

init({full, ConfigPath, DirPath, PolicyMod} = Init) ->
    case load_config(ConfigPath, DirPath, PolicyMod) of
        {ok, Config} ->
            Config2 = Config#config{init = Init},
            % TODO: Should save the merged dynamic file config.
            % TODO: Should let node_disco do picking and merging?
            Mergable = PolicyMod:mergable(),
            {ok, pick_node_and_merge(Mergable, Config2,
                                     nodes([visible]))};
        Error ->
            {stop, Error}
    end;

init([ConfigPath, PolicyMod]) ->
    init({full, ConfigPath, undefined, PolicyMod}).

terminate(_Reason, _State)          -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
handle_cast(stop, State)            -> {stop, shutdown, State}.
handle_info(_Info, State)           -> {noreply, State}.

handle_call(reinit, _From, State) ->
    case init(State#config.init) of
        {ok, State2}  -> {reply, ok, State2};
        {stop, Error} -> {reply, {error, Error}, State}
    end;

handle_call(get, _From, State) -> {reply, State, State};

handle_call({set, KVList}, _From, State) ->
    PolicyMod = State#config.policy_mod,
    State2 = merge_configs(PolicyMod:mergable(),
                           #config{dynamic = [KVList]},
                           State),
    case State2 =/= State of<commit_msg>added reload/resave API to ns_config<commit_after>% Copyright (c) 2009, NorthScale, Inc.
% Copyright (c) 2008, <NAME>
% Copyright (c) 2008, Powerset, Inc
%
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions
% are met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in the
% documentation and/or other materials provided with the distribution.
% * Neither the name of Powerset, Inc nor the names of its
% contributors may be used to endorse or promote products derived from
% this software without specific prior written permission.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
% COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
%
% Original Author: <NAME>

-module(ns_config).

-behaviour(gen_server).

-define(METADATA_VER, '_ver').

-export([start_link/2, start_link/1, stop/0,
         get/2, get/1, get/0, set/2, set/1,
         search/2, search/1,
         search_prop/3, search_prop/4,
         search_raw/2]).

% A static config file is often hand edited.
% potentially with in-line manual comments.
%
% A dynamic config file is system generated and modified,
% such as due to changes from UI/admin-screen operations, or
% nodes getting added/removed, and gossiping about config
% information.
%
-record(config, {init,         % Initialization parameters.
                 static = [],  % List of TupleList's; TupleList is {K, V}.
                 dynamic = [], % List of TupleList's; TupleList is {K, V}.
                 policy_mod
                }).

%% gen_server callbacks

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-include_lib("eunit/include/eunit.hrl").

-ifdef(TEST).
-include("test/ns_config_test.erl").
-endif.

%% API

start_link(Full) ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, Full, []).

start_link(ConfigPath, PolicyMod) -> start_link([ConfigPath, PolicyMod]).

stop()   -> gen_server:cast(?MODULE, stop).
reload() -> gen_server:call(?MODULE, reload).
resave() -> gen_server:call(?MODULE, resave).

set(Key, PropList) when is_list(PropList) ->
    PropList2 = [{?METADATA_VER, erlang:now()} |
                 strip_metadata(PropList, [])],
    gen_server:call(?MODULE, {merge, [{Key, PropList2}]});

set(Key, Val)   -> gen_server:call(?MODULE, {merge, [{Key, Val}]}).
set(KVList)     -> gen_server:call(?MODULE, {merge,   KVList}).
replace(KVList) -> gen_server:call(?MODULE, {replace, KVList}).

get()              -> gen_server:call(?MODULE, get).
get(Node)          -> ?MODULE:get(Node, 500).
get(Node, Timeout) -> gen_server:call({?MODULE, Node}, get, Timeout).

search(Key) -> search(?MODULE:get(), Key).

search(Config, Key) ->
    case search_raw(Config, Key) of
        false      -> false;
        {value, X} -> {value, strip_metadata(X, [])}
    end.

search_prop(Config, Key, SubKey) ->
    search_prop(Config, Key, SubKey, undefined).

search_prop(Config, Key, SubKey, DefaultSubVal) ->
    case search(Config, Key) of
        {value, PropList} ->
            proplists:get_value(SubKey, PropList, DefaultSubVal);
        false ->
            DefaultSubVal
    end.

% The search_raw API does not strip out metadata from results.

search_raw(undefined, _Key) -> false;
search_raw([], _Key)        -> false;
search_raw([KVList | Rest], Key) ->
    case lists:keysearch(Key, 1, KVList) of
        {value, {Key, V}} -> {value, V};
        _                 -> search_raw(Rest, Key)
    end;
search_raw(#config{dynamic = DL, static = SL}, Key) ->
    case search_raw(DL, Key) of
        {value, _} = R -> R;
        false          -> search_raw(SL, Key)
    end.

%% Implementation

% Removes metadata like METADATA_VER from results.

strip_metadata([], Acc)                       -> lists:reverse(Acc);
strip_metadata([{?METADATA_VER, _} | T], Acc) -> strip_metadata(T, Acc);
strip_metadata([X | T], Acc)                  -> strip_metadata(T, [X | Acc]);
strip_metadata(X, _)                          -> X.

%% gen_server callbacks

init({full, ConfigPath, DirPath, PolicyMod} = Init) ->
    case load_config(ConfigPath, DirPath, PolicyMod) of
        {ok, Config} ->
            Config2 = Config#config{init = Init},
            % TODO: Should save the merged dynamic file config.
            % TODO: Should let node_disco do picking and merging?
            Mergable = PolicyMod:mergable(),
            {ok, pick_node_and_merge(Mergable, Config2,
                                     nodes([visible]))};
        Error ->
            {stop, Error}
    end;

init([ConfigPath, PolicyMod]) ->
    init({full, ConfigPath, undefined, PolicyMod}).

terminate(_Reason, _State)          -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
handle_cast(stop, State)            -> {stop, shutdown, State}.
handle_info(_Info, State)           -> {noreply, State}.

handle_call(reload, _From, State) ->
    case init(State#config.init) of
        {ok, State2}  -> {reply, ok, State2};
        {stop, Error} -> {reply, {error, Error}, State}
    end;

handle_call(resave, _From, State) ->
    save_config(State),
    {reply, ok, State};

handle_call(get, _From, State) -> {reply, State, State};

handle_call({replace, KVList}, _From, State) ->
    {reply, ok, State#config{dynamic = [KVList]}};

handle_call({merge, KVList}, _From, State) ->
    PolicyMod = State#config.policy_mod,
    State2 = merge_configs(PolicyMod:mergable(),
                           #config{dynamic = [KVList]},
                           State),
    case State2 =/= State of
{'fraction_non_alphanumeric': 0.12629908766362555, 'fraction_numerical': 0.006822689408964696, 'mean_word_length': 3.0313399424368406, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 97, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3346876', 'n_tokens_mistral': 3960, 'n_tokens_neox': 3777, 'n_words': 1199}