1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(geocouch_duplicates).
14
15% This module contains functions that are needed by GeoCouch but that
16% are not exported by CouchDB. Here's the place to put the code from
17% those functions.
18
19-include("couch_db.hrl").
20-export([start_list_resp/6, send_non_empty_chunk/2, sort_lib/1,
21    make_arity_3_fun/1, parse_int_param/1, parse_positive_int_param/1,
22    nuke_dir/2]).
23
24% From couch_httpd_show
25start_list_resp(QServer, LName, Req, Db, Head, Etag) ->
26    JsonReq = couch_httpd_external:json_req_obj(Req, Db),
27    [<<"start">>,Chunks,JsonResp] = couch_query_servers:ddoc_proc_prompt(QServer,
28        [<<"lists">>, LName], [Head, JsonReq]),
29    JsonResp2 = apply_etag(JsonResp, Etag),
30    #extern_resp_args{
31        code = Code,
32        ctype = CType,
33        headers = ExtHeaders
34    } = couch_httpd_external:parse_external_response(JsonResp2),
35    JsonHeaders = couch_httpd_external:default_or_content_type(CType, ExtHeaders),
36    {ok, Resp} = couch_httpd:start_chunked_response(Req, Code, JsonHeaders),
37    {ok, Resp, ?b2l(?l2b(Chunks))}.
38% Needed for start_list_resp/6
39apply_etag({ExternalResponse}, CurrentEtag) ->
40    % Here we embark on the delicate task of replacing or creating the
41    % headers on the JsonResponse object. We need to control the Etag and
42    % Vary headers. If the external function controls the Etag, we'd have to
43    % run it to check for a match, which sort of defeats the purpose.
44    case couch_util:get_value(<<"headers">>, ExternalResponse, nil) of
45    nil ->
46        % no JSON headers
47        % add our Etag and Vary headers to the response
48        {[{<<"headers">>, {[{<<"Etag">>, CurrentEtag}, {<<"Vary">>, <<"Accept">>}]}} | ExternalResponse]};
49    JsonHeaders ->
50        {[case Field of
51        {<<"headers">>, JsonHeaders} -> % add our headers
52            JsonHeadersEtagged = couch_util:json_apply_field({<<"Etag">>, CurrentEtag}, JsonHeaders),
53            JsonHeadersVaried = couch_util:json_apply_field({<<"Vary">>, <<"Accept">>}, JsonHeadersEtagged),
54            {<<"headers">>, JsonHeadersVaried};
55        _ -> % skip non-header fields
56            Field
57        end || Field <- ExternalResponse]}
58    end.
59
60
61% From couch_httpd_show
62send_non_empty_chunk(Resp, Chunk) ->
63    case Chunk of
64        [] -> ok;
65        _ -> couch_httpd:send_chunk(Resp, Chunk)
66    end.
67
68% From couch_view_group
69sort_lib({Lib}) ->
70    sort_lib(Lib, []).
71sort_lib([], LAcc) ->
72    lists:keysort(1, LAcc);
73sort_lib([{LName, {LObj}}|Rest], LAcc) ->
74    LSorted = sort_lib(LObj, []), % descend into nested object
75    sort_lib(Rest, [{LName, LSorted}|LAcc]);
76sort_lib([{LName, LCode}|Rest], LAcc) ->
77    sort_lib(Rest, [{LName, LCode}|LAcc]).
78
79% From couch_httpd (will be exported from 1.1.x on)
80make_arity_3_fun(SpecStr) ->
81    case couch_util:parse_term(SpecStr) of
82    {ok, {Mod, Fun, SpecArg}} ->
83	fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3, SpecArg) end;
84    {ok, {Mod, Fun}} ->
85	fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3) end
86    end.
87
88% From couch_httpd_view
89parse_int_param(Val) ->
90    case (catch list_to_integer(Val)) of
91    IntVal when is_integer(IntVal) ->
92        IntVal;
93    _ ->
94        Msg = io_lib:format("Invalid value for integer parameter: ~p", [Val]),
95        throw({query_parse_error, ?l2b(Msg)})
96    end.
97
98% From couch_httpd_view
99parse_positive_int_param(Val) ->
100    case parse_int_param(Val) of
101    IntVal when IntVal >= 0 ->
102        IntVal;
103    _ ->
104        Fmt = "Invalid value for positive integer parameter: ~p",
105        Msg = io_lib:format(Fmt, [Val]),
106        throw({query_parse_error, ?l2b(Msg)})
107    end.
108
109% From couch_view
110nuke_dir(RootDelDir, Dir) ->
111    case file:list_dir(Dir) of
112    {error, enoent} -> ok; % doesn't exist
113    {ok, Files} ->
114        lists:foreach(
115            fun(File)->
116                Full = Dir ++ "/" ++ File,
117                case couch_file:delete(RootDelDir, Full, false) of
118                ok -> ok;
119                % Directory doesn't exist
120                {error, enoent} -> ok;
121                {error, eperm} ->
122                    ok = nuke_dir(RootDelDir, Full)
123                end
124            end,
125            Files),
126        case file:del_dir(Dir) of
127        ok -> ok;
128        % Directory doesn't exist (might have been deleted by some other
129        % process already)
130        {error, enoent} -> ok
131        end
132    end.
133