%% CouchDB %% Copyright (C) 2006 Damien Katz %% %% This program is free software; you can redistribute it and/or %% modify it under the terms of the GNU General Public License %% as published by the Free Software Foundation; either version 2 %% of the License, or (at your option) any later version. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program; if not, write to the Free Software %% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -module(couch_doc). -export([get_view_functions/1, is_special_doc/1]). -export([bin_foldl/3,bin_size/1,bin_to_binary/1]). -export([from_json_obj/1,to_json_obj/2]). -include("couch_db.hrl"). to_json_obj(#doc{id=Id,deleted=Del,body=Body,revs=Revs,meta=Meta}=Doc,Options)-> {obj, [{"_id", Id}] ++ case Revs of [] -> []; _ -> [{"_rev", lists:nth(1, Revs)}] end ++ case Del of false -> {obj, BodyProps} = Body, BodyProps; true -> [{"_deleted", true}] end ++ case lists:member(revs, Options) of false -> []; true -> [{"_revs", list_to_tuple(Revs)}] end ++ lists:map( fun({revs_info, RevsInfo}) -> JsonRevsInfo = [{obj, [{rev, Rev}, {status, atom_to_list(Status)}]} || {Rev, Status} <- RevsInfo], {"_revs_info", list_to_tuple(JsonRevsInfo)}; ({conflicts, Conflicts}) -> {"_conflicts", list_to_tuple(Conflicts)}; ({deleted_conflicts, Conflicts}) -> {"_deleted_conflicts", list_to_tuple(Conflicts)} end, Meta) ++ case lists:member(attachments, Options) of true -> % return the full rev list and the binaries as strings. BinProps = lists:map( fun({Name, {Type, BinValue}}) -> {Name, {obj, [{"content-type", Type}, {"data", couch_util:encodeBase64(bin_to_binary(BinValue))}]}} end, Doc#doc.attachments), case BinProps of [] -> []; _ -> [{"_attachments", {obj, BinProps}}] end; false -> BinProps = lists:map( fun({Name, {Type, BinValue}}) -> {Name, {obj, [{"stub", true}, {"content-type", Type}, {"length", bin_size(BinValue)}]}} end, Doc#doc.attachments), case BinProps of [] -> []; _ -> [{"_attachments", {obj, BinProps}}] end end }. from_json_obj({obj, Props}) -> {obj,JsonBins} = proplists:get_value("_attachments", Props, {obj, []}), Bins = lists:flatmap(fun({Name, {obj, BinProps}}) -> case proplists:get_value("stub", BinProps) of true -> []; _ -> Value = proplists:get_value("data", BinProps), Type = proplists:get_value("content-type", BinProps, ?DEFAULT_ATTACHMENT_CONTENT_TYPE), [{Name, {Type, couch_util:decodeBase64(Value)}}] end end, JsonBins), AllowedSpecialMembers = ["id", "revs", "rev", "attachments", "revs_info", "conflicts", "deleted_conflicts", "deleted"], [case lists:member(Name, AllowedSpecialMembers) of true -> ok; false -> throw({doc_validation, io_lib:format("Bad special document member: _~s", [Name])}) end || {[$_|Name], _Value} <- Props], Revs = case tuple_to_list(proplists:get_value("_revs", Props, {})) of [] -> case proplists:get_value("_rev", Props) of undefined -> []; Rev -> [Rev] end; Revs0 -> Revs0 end, #doc{ id = proplists:get_value("_id", Props, ""), revs = Revs, deleted = proplists:get_value("_deleted", Props, false), body = {obj, [{Key, Value} || {[FirstChar|_]=Key, Value} <- Props, FirstChar /= $_]}, attachments = Bins }. is_special_doc(?DESIGN_DOC_PREFIX ++ _ ) -> true; is_special_doc(?STEW_DOC_PREFIX ++ _ ) -> true; is_special_doc(#doc{id=Id}) -> is_special_doc(Id); is_special_doc(_) -> false. bin_foldl(Bin, Fun, Acc) when is_binary(Bin) -> case Fun(Bin, Acc) of {ok, Acc2} -> {ok, Acc2}; {done, Acc2} -> {ok, Acc2} end; bin_foldl({Fd, Sp, Len}, Fun, Acc) -> {ok, Acc2, _Sp2} = couch_stream:foldl(Fd, Sp, Len, Fun, Acc), {ok, Acc2}. bin_size(Bin) when is_binary(Bin) -> size(Bin); bin_size({_Fd, _Sp, Len}) -> Len. bin_to_binary(Bin) when is_binary(Bin) -> Bin; bin_to_binary({Fd, Sp, Len}) -> {ok, Bin, _Sp2} = couch_stream:read(Fd, Sp, Len), Bin. get_view_functions(#doc{body={obj, Fields}}) -> Lang = proplists:get_value("language", Fields, "text/javascript"), {obj, Views} = proplists:get_value("views", Fields, {obj, []}), {Lang, [{ViewName, Value} || {ViewName, Value} <- Views, is_list(Value)]}; get_view_functions(_Doc) -> none.