blob: 565ed9d3d5bf87d940d7f5e28c1bc5701399aa29 [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couch_replicator_ids).
-export([replication_id/1, replication_id/2, convert/1]).
-include_lib("couch/include/couch_db.hrl").
-include("couch_replicator_api_wrap.hrl").
-include("couch_replicator.hrl").
-import(couch_util, [
get_value/2,
get_value/3
]).
% replication_id/1 and replication_id/2 will attempt to fetch
% filter code for filtered replications. If fetching or parsing
% of the remotely fetched filter code fails they throw:
% {filter_fetch_error, Error} exception.
%
replication_id(#rep{options = Options} = Rep) ->
BaseId = replication_id(Rep, ?REP_ID_VERSION),
{BaseId, maybe_append_options([continuous, create_target], Options)}.
% Versioned clauses for generating replication IDs.
% If a change is made to how replications are identified,
% please add a new clause and increase ?REP_ID_VERSION.
replication_id(#rep{user_ctx = UserCtx} = Rep, 3) ->
UUID = couch_server:get_uuid(),
Src = get_rep_endpoint(UserCtx, Rep#rep.source),
Tgt = get_rep_endpoint(UserCtx, Rep#rep.target),
maybe_append_filters([UUID, Src, Tgt], Rep);
replication_id(#rep{user_ctx = UserCtx} = Rep, 2) ->
{ok, HostName} = inet:gethostname(),
Port = case (catch mochiweb_socket_server:get(couch_httpd, port)) of
P when is_number(P) ->
P;
_ ->
% On restart we might be called before the couch_httpd process is
% started.
% TODO: we might be under an SSL socket server only, or both under
% SSL and a non-SSL socket.
% ... mochiweb_socket_server:get(https, port)
list_to_integer(config:get("httpd", "port", "5984"))
end,
Src = get_rep_endpoint(UserCtx, Rep#rep.source),
Tgt = get_rep_endpoint(UserCtx, Rep#rep.target),
maybe_append_filters([HostName, Port, Src, Tgt], Rep);
replication_id(#rep{user_ctx = UserCtx} = Rep, 1) ->
{ok, HostName} = inet:gethostname(),
Src = get_rep_endpoint(UserCtx, Rep#rep.source),
Tgt = get_rep_endpoint(UserCtx, Rep#rep.target),
maybe_append_filters([HostName, Src, Tgt], Rep).
-spec convert([_] | binary() | {string(), string()}) -> {string(), string()}.
convert(Id) when is_list(Id) ->
convert(?l2b(Id));
convert(Id) when is_binary(Id) ->
lists:splitwith(fun(Char) -> Char =/= $+ end, ?b2l(Id));
convert({BaseId, Ext} = Id) when is_list(BaseId), is_list(Ext) ->
Id.
% Private functions
maybe_append_filters(Base,
#rep{source = Source, user_ctx = UserCtx, options = Options}) ->
Base2 = Base ++
case couch_replicator_filters:parse(Options) of
{ok, nil} ->
[];
{ok, {view, Filter, QueryParams}} ->
[Filter, QueryParams];
{ok, {user, {Doc, Filter}, QueryParams}} ->
case couch_replicator_filters:fetch(Doc, Filter, Source, UserCtx) of
{ok, Code} ->
[Code, QueryParams];
{error, Error} ->
throw({filter_fetch_error, Error})
end;
{ok, {docids, DocIds}} ->
[DocIds];
{ok, {mango, Selector}} ->
[Selector];
{error, FilterParseError} ->
throw({error, FilterParseError})
end,
couch_util:to_hex(couch_crypto:hash(md5, term_to_binary(Base2))).
maybe_append_options(Options, RepOptions) ->
lists:foldl(fun(Option, Acc) ->
Acc ++
case get_value(Option, RepOptions, false) of
true ->
"+" ++ atom_to_list(Option);
false ->
""
end
end, [], Options).
get_rep_endpoint(_UserCtx, #httpdb{url=Url, headers=Headers, oauth=OAuth}) ->
DefaultHeaders = (#httpdb{})#httpdb.headers,
case OAuth of
nil ->
{remote, Url, Headers -- DefaultHeaders};
#oauth{} ->
{remote, Url, Headers -- DefaultHeaders, OAuth}
end;
get_rep_endpoint(UserCtx, <<DbName/binary>>) ->
{local, DbName, UserCtx}.