From a9535054b9786fe545c974b00e7bfdc051b2caad Mon Sep 17 00:00:00 2001 From: James Aimonetti Date: Mon, 19 Dec 2011 10:57:55 -0800 Subject: [PATCH 1/4] WHISTLE-250: fix for to_float conversion and updated tests --- lib/whistle-1.0.0/src/wh_util.erl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/whistle-1.0.0/src/wh_util.erl b/lib/whistle-1.0.0/src/wh_util.erl index e4fd60ae8a2..cc4a79b418e 100644 --- a/lib/whistle-1.0.0/src/wh_util.erl +++ b/lib/whistle-1.0.0/src/wh_util.erl @@ -194,7 +194,7 @@ to_float(X, S) when is_list(X) -> end; to_float(X, strict) when is_integer(X) -> error(badarg); -to_float(X, nonstrict) when is_integer(X) -> +to_float(X, notstrict) when is_integer(X) -> X * 1.0; to_float(X, _) when is_float(X) -> X. @@ -447,22 +447,22 @@ is_ipv6(Address) when is_list(Address) -> prop_to_integer() -> ?FORALL({F, I}, {float(), integer()}, begin - Is = [ Fun(N) || Fun <- [ fun to_list/1, fun to_binary/1], N <- [F, I] ], - lists:all(fun(N) -> erlang:is_integer(to_integer(N)) end, Is) + Is = [ [Fun(N), N] || Fun <- [ fun to_list/1, fun to_binary/1], N <- [F, I] ], + lists:all(fun([FN, N]) -> erlang:is_integer(to_integer(N)) andalso erlang:is_integer(to_integer(FN)) end, Is) end). prop_to_number() -> ?FORALL({F, I}, {float(), integer()}, begin - Is = [ Fun(N) || Fun <- [ fun to_list/1, fun to_binary/1], N <- [F, I] ], - lists:all(fun(N) -> erlang:is_number(to_number(N)) end, Is) + Is = [ [Fun(N), N] || Fun <- [ fun to_list/1, fun to_binary/1], N <- [F, I] ], + lists:all(fun([FN, N]) -> erlang:is_number(to_number(N)) andalso erlang:is_number(to_number(FN)) end, Is) end). prop_to_float() -> ?FORALL({F, I}, {float(), integer()}, begin - Fs = [ Fun(N) || Fun <- [ fun to_list/1, fun to_binary/1], N <- [F, I] ], - lists:all(fun(N) -> erlang:is_float(to_float(N)) end, Fs) + Fs = [ [Fun(N), N] || Fun <- [ fun to_list/1, fun to_binary/1], N <- [F, I] ], + lists:all(fun([FN, N]) -> erlang:is_float(to_float(N)) andalso erlang:is_float(to_float(FN)) end, Fs) end). prop_to_list() -> @@ -475,7 +475,7 @@ prop_to_binary() -> lists:all(fun(X) -> is_binary(to_binary(X)) end, [A, L, B, I, F, IO])). prop_iolist_t() -> - ?FORALL(IO, iolist(), is_binary(iolist_to_binary(IO))). + ?FORALL(IO, iolist(), is_binary(to_binary(IO))). %% (AAABBBCCCC, 1AAABBBCCCC) -> AAABBBCCCCCC. prop_to_npan() -> From 43dd9e5503f090b0b5440900b8789a24de8a83be Mon Sep 17 00:00:00 2001 From: James Aimonetti Date: Tue, 20 Dec 2011 16:26:23 -0800 Subject: [PATCH 2/4] WHISTLE-630: update cb_media to pull the first attachment (rather than a contrived name) --- .../apps/crossbar/src/modules/cb_media.erl | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/whistle_apps/apps/crossbar/src/modules/cb_media.erl b/whistle_apps/apps/crossbar/src/modules/cb_media.erl index 66b9fcf797b..672231c7e3d 100644 --- a/whistle_apps/apps/crossbar/src/modules/cb_media.erl +++ b/whistle_apps/apps/crossbar/src/modules/cb_media.erl @@ -63,7 +63,8 @@ start_link() -> %% @end %%-------------------------------------------------------------------- init([]) -> - {ok, ok, 0}. + _ = bind_to_crossbar(), + {ok, ok}. %%-------------------------------------------------------------------- %% @private @@ -107,6 +108,7 @@ handle_cast(_, S) -> %%-------------------------------------------------------------------- handle_info({binding_fired, Pid, <<"v1_resource.content_types_provided.media">>, {RD, Context, Params}}, State) -> spawn(fun() -> + _ = crossbar_util:put_reqid(Context), Context1 = content_types_provided(Params, Context), Pid ! {binding_result, true, {RD, Context1, Params}} end), @@ -114,6 +116,7 @@ handle_info({binding_fired, Pid, <<"v1_resource.content_types_provided.media">>, handle_info({binding_fired, Pid, <<"v1_resource.content_types_accepted.media">>, {RD, Context, Params}}, State) -> spawn(fun() -> + _ = crossbar_util:put_reqid(Context), Context1 = content_types_accepted(Params, Context), Pid ! {binding_result, true, {RD, Context1, Params}} end), @@ -156,6 +159,7 @@ handle_info({binding_fired, Pid, <<"v1_resource.execute.get.media">>, [RD, Conte end); _ -> spawn(fun() -> + _ = crossbar_util:put_reqid(Context), Pid ! {binding_result, true, [RD, Context, Params]} end) end, @@ -230,11 +234,8 @@ handle_info({binding_fired, Pid, _, Payload}, State) -> Pid ! {binding_result, false, Payload}, {noreply, State}; -handle_info(timeout, State) -> - bind_to_crossbar(), - {noreply, State}; - handle_info(_Info, State) -> + ?LOG("Unhandled message: ~p", [_Info]), {noreply, State}. %%-------------------------------------------------------------------- @@ -272,7 +273,7 @@ code_change(_OldVsn, State, _Extra) -> %% for the keys we need to consume. %% @end %%-------------------------------------------------------------------- --spec(bind_to_crossbar/0 :: () -> no_return()). +-spec bind_to_crossbar/0 :: () -> 'ok' | {'error', 'exists'}. bind_to_crossbar() -> _ = crossbar_bindings:bind(<<"v1_resource.content_types_provided.media">>), _ = crossbar_bindings:bind(<<"v1_resource.content_types_accepted.media">>), @@ -288,11 +289,13 @@ bind_to_crossbar() -> %% %% @end %%-------------------------------------------------------------------- +-spec content_types_provided/2 :: (path_tokens(), #cb_context{}) -> #cb_context{}. content_types_provided([_MediaID, ?BIN_DATA], #cb_context{req_verb = <<"get">>}=Context) -> CTP = [{to_binary, ?MEDIA_MIME_TYPES}], Context#cb_context{content_types_provided=CTP}; content_types_provided(_, Context) -> Context. +-spec content_types_accepted/2 :: (path_tokens(), #cb_context{}) -> #cb_context{}. content_types_accepted([_MediaID, ?BIN_DATA], #cb_context{req_verb = <<"post">>}=Context) -> CTA = [{from_binary, ?MEDIA_MIME_TYPES}], Context#cb_context{content_types_accepted=CTA}; @@ -309,7 +312,7 @@ content_types_accepted(_, Context) -> Context. %% Failure here returns 405 %% @end %%-------------------------------------------------------------------- --spec(allowed_methods/1 :: (Paths :: list()) -> tuple(boolean(), http_methods())). +-spec allowed_methods/1 :: (path_tokens()) -> {boolean(), http_methods()}. allowed_methods([]) -> {true, ['GET', 'PUT']}; allowed_methods([_MediaID]) -> @@ -330,7 +333,7 @@ allowed_methods(_) -> %% Failure here returns 404 %% @end %%-------------------------------------------------------------------- --spec(resource_exists/1 :: (Paths :: list()) -> tuple(boolean(), [])). +-spec resource_exists/1 :: (path_tokens()) -> {boolean(), []}. resource_exists([]) -> {true, []}; resource_exists([_MediaID]) -> @@ -349,7 +352,7 @@ resource_exists(_) -> %% Failure here returns 400 %% @end %%-------------------------------------------------------------------- --spec(validate/2 :: (Params :: list(), Context :: #cb_context{}) -> #cb_context{}). +-spec validate/2 :: (path_tokens(), #cb_context{}) -> #cb_context{}. validate([], #cb_context{req_verb = <<"get">>}=Context) -> lookup_media(Context); @@ -402,6 +405,7 @@ validate(Params, #cb_context{req_verb=Verb, req_nouns=Nouns, req_data=D}=Context ?LOG_SYS("Data: ~p", [D]), crossbar_util:response_faulty_request(Context). +-spec create_media_meta/2 :: (json_object(), #cb_context{}) -> #cb_context{}. create_media_meta(Data, Context) -> Doc1 = lists:foldr(fun(Meta, DocAcc) -> case wh_json:get_value(Meta, Data) of @@ -409,15 +413,16 @@ create_media_meta(Data, Context) -> V -> [{Meta, wh_util:to_binary(V)} | DocAcc] end end, [], ?METADATA_FIELDS), - crossbar_doc:save(Context#cb_context{doc={struct, [{<<"pvt_type">>, <<"media">>} | Doc1]}}). + crossbar_doc:save(Context#cb_context{doc=wh_json:from_list([{<<"pvt_type">>, <<"media">>} | Doc1])}). +-spec update_media_binary/4 :: (ne_binary(), ne_binary(), #cb_context{}, json_object()) -> #cb_context{}. update_media_binary(MediaID, Contents, Context, HeadersJObj) -> CT = wh_json:get_value(<<"content_type">>, HeadersJObj, <<"application/octet-stream">>), Opts = [{headers, [{content_type, wh_util:to_list(CT)}]}], ?LOG("Setting Content-Type to ~s", [CT]), - case crossbar_doc:save_attachment(MediaID, attachment_name(MediaID), Contents, Context, Opts) of + case crossbar_doc:save_attachment(MediaID, attachment_name(), Contents, Context, Opts) of #cb_context{resp_status=success}=Context1 -> ?LOG("Saved attachement successfully"), #cb_context{doc=Doc} = crossbar_doc:load(MediaID, Context), @@ -429,7 +434,7 @@ update_media_binary(MediaID, Contents, Context, HeadersJObj) -> end. %% GET /media --spec(lookup_media/1 :: (Context :: #cb_context{}) -> #cb_context{}). +-spec lookup_media/1 :: (Context :: #cb_context{}) -> #cb_context{}. lookup_media(Context) -> case crossbar_doc:load_view(<<"media/crossbar_listing">>, [], Context) of #cb_context{resp_status=success, doc=Doc}=Context1 -> @@ -439,16 +444,25 @@ lookup_media(Context) -> end. %% GET/POST/DELETE /media/MediaID --spec(get_media_doc/2 :: (MediaID :: binary(), Context :: #cb_context{}) -> #cb_context{}). +-spec get_media_doc/2 :: (ne_binary(), #cb_context{}) -> #cb_context{}. get_media_doc(MediaID, Context) -> crossbar_doc:load(MediaID, Context). %% GET/DELETE /media/MediaID/raw get_media_binary(MediaID, Context) -> - crossbar_doc:load_attachment(MediaID, attachment_name(MediaID), Context). + case crossbar_doc:load(MediaID, Context) of + #cb_context{resp_status=success, doc=MediaMeta} -> + case wh_json:get_value([<<"_attachments">>, 1], MediaMeta) of + undefined -> crossbar_util:response_bad_identifier(MediaID, Context); + AttachMeta -> + [AttachmentID] = wh_json:get_keys(AttachMeta), + crossbar_doc:load_attachment(MediaID, AttachmentID, Context) + end; + Context1 -> Context1 + end. %% check for existence of media by name --spec(lookup_media_by_name/2 :: (MediaID :: binary(), Context :: #cb_context{}) -> #cb_context{}). +-spec lookup_media_by_name/2 :: (ne_binary(), #cb_context{}) -> #cb_context{}. lookup_media_by_name(MediaName, Context) -> crossbar_doc:load_view(<<"media/listing_by_name">>, [{<<"key">>, MediaName}], Context). @@ -461,7 +475,17 @@ delete_media(Context) -> crossbar_doc:delete(Context). delete_media_binary(MediaID, Context) -> - crossbar_doc:delete_attachment(MediaID, attachment_name(MediaID), Context). + case crossbar_doc:load(MediaID, Context) of + #cb_context{resp_status=success, doc=MediaMeta} -> + case wh_json:get_value([<<"_attachments">>, 1], MediaMeta) of + undefined -> crossbar_util:response_bad_identifier(MediaID, Context); + AttachMeta -> + [AttachmentID] = wh_json:get_keys(AttachMeta), + crossbar_doc:delete_attachment(MediaID, AttachmentID, Context) + end; + Context1 -> Context1 + end. -attachment_name(MediaID) -> - <>. +-spec attachment_name/0 :: () -> ne_binary(). +attachment_name() -> + list_to_binary([wh_util:to_hex(crypto:rand_bytes(16)), ".mp3"]). From 23ffe3862090eafebf24f540dc687ff7b8843bfa Mon Sep 17 00:00:00 2001 From: James Aimonetti Date: Tue, 20 Dec 2011 16:26:50 -0800 Subject: [PATCH 3/4] WHISTLE-630: update spec for loading attachment --- whistle_apps/apps/crossbar/src/crossbar_doc.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whistle_apps/apps/crossbar/src/crossbar_doc.erl b/whistle_apps/apps/crossbar/src/crossbar_doc.erl index 7b6f9254181..9a1bc37a58d 100644 --- a/whistle_apps/apps/crossbar/src/crossbar_doc.erl +++ b/whistle_apps/apps/crossbar/src/crossbar_doc.erl @@ -218,7 +218,7 @@ load_docs(#cb_context{db_name=Db}=Context, Filter) when is_function(Filter, 2) - %% Failure here returns 500 or 503 %% @end %%-------------------------------------------------------------------- --spec(load_attachment/3 :: (DocId :: binary(), AName :: binary(), Context :: #cb_context{}) -> #cb_context{}). +-spec load_attachment/3 :: (ne_binary(), ne_binary(), #cb_context{}) -> #cb_context{}. load_attachment(_DocId, _AName, #cb_context{db_name = <<>>}=Context) -> ?LOG("loading attachment ~s from doc ~s failed: no db", [_DocId, _AName]), crossbar_util:response_db_missing(Context); From 46573e9b10eb72fd109913c5cfe006fe81071420 Mon Sep 17 00:00:00 2001 From: karl anderson Date: Thu, 22 Dec 2011 03:05:43 +0000 Subject: [PATCH 4/4] WHISTLE-811: created a proplist function to filter for unique entries while maintaining the order --- ecallmgr/src/ecallmgr_call_command.erl | 2 +- lib/whistle-1.0.0/src/props.erl | 33 +++++++++++++++++--------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/ecallmgr/src/ecallmgr_call_command.erl b/ecallmgr/src/ecallmgr_call_command.erl index 27b963b11d2..6ef33ff93dd 100644 --- a/ecallmgr/src/ecallmgr_call_command.erl +++ b/ecallmgr/src/ecallmgr_call_command.erl @@ -257,7 +257,7 @@ get_fs_app(Node, UUID, JObj, <<"bridge">>) -> put(callid, UUID), S ! {self(), (catch get_bridge_endpoint(EP))} end) - || {_, EP} <- orddict:to_list(orddict:from_list(KeyedEPs))] + || {_, EP} <- props:unique(KeyedEPs)] ], D =/= ""], Generators = [fun(DP) -> case wh_json:get_integer_value(<<"Timeout">>, JObj) of diff --git a/lib/whistle-1.0.0/src/props.erl b/lib/whistle-1.0.0/src/props.erl index f6ffef45ba0..494f66315eb 100644 --- a/lib/whistle-1.0.0/src/props.erl +++ b/lib/whistle-1.0.0/src/props.erl @@ -11,6 +11,7 @@ -export([get_value/2, get_value/3, delete/2, is_defined/2]). -export([get_keys/1]). +-export([unique/1]). -include_lib("whistle/include/wh_types.hrl"). @@ -29,15 +30,15 @@ get_value(Key, {struct, Prop}, Def) -> get_value(Key, Prop, Def); get_value(Key, Prop, Default) -> case lists:keyfind(Key, 1, Prop) of - false -> - case lists:member(Key, Prop) of - true -> true; - false -> Default - end; - {Key, V} -> % only return V if a two-tuple is found - V; - Other when is_tuple(Other) -> % otherwise return the default - Default + false -> + case lists:member(Key, Prop) of + true -> true; + false -> Default + end; + {Key, V} -> % only return V if a two-tuple is found + V; + Other when is_tuple(Other) -> % otherwise return the default + Default end. -spec get_keys/1 :: (Prop) -> [term(),...] | [] when @@ -56,6 +57,16 @@ delete(K, Prop) -> Prop :: proplist(). is_defined(Key, Prop) -> case lists:keyfind(Key, 1, Prop) of - {Key,_} -> true; - _ -> false + {Key,_} -> true; + _ -> false end. + +-spec unique/1 :: (proplist()) -> proplist(). +unique(List) -> + unique(List, []). + +-spec unique/2 :: (proplist(), proplist()) -> proplist(). +unique([], Uniques) -> + lists:reverse(Uniques); +unique([{Key, _}=H|T], Uniques) -> + unique(lists:keydelete(Key, 1, T), [H|Uniques]).