Created
January 10, 2012 14:29
-
-
Save evanmiller/1589346 to your computer and use it in GitHub Desktop.
Scattered remains of Chicagosaurus Rex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(rr_db, [Server, PhotoRoot]). | |
-define(MIME_TYPE, "application/json"). | |
-compile(export_all). | |
init() -> | |
inets:start(), | |
inets:start(httpc, [{profile, database}]). | |
reset() -> | |
case http:request('delete', {Server, []}, [], []) of | |
{ok, _} -> | |
case http:request('put', {Server, [], [], []}, [], []) of | |
{ok, _} -> | |
create_view(user), | |
create_view(association), | |
ok; | |
_ -> | |
error | |
end; | |
_ -> | |
error | |
end. | |
dump() -> | |
case http:request(Server ++ "_all_docs") of | |
{ok, {Status, _Headers, Body}} -> | |
{ok, Body}; | |
Error -> | |
Error | |
end. | |
fetch(DocId) -> | |
case http:request(Server ++ rr_util:quote_plus(DocId)) of | |
{ok, {Status, _Headers, Body}} -> | |
case Status of | |
{_, 200, _} -> | |
{struct, PropList} = mochijson2:decode(Body), | |
{ok, rr_util:atomize_proplist(PropList)}; | |
{_, 404, _} -> | |
{error, not_found} | |
end; | |
Error -> | |
Error | |
end. | |
create_view(user) -> | |
Functions = [{<<"by_url">>, <<"function(doc) { if (doc.TYPE == 'user') { map(doc.url, doc); } }">>}, | |
{<<"by_email">>, <<"function(doc) { if (doc.TYPE == 'user') { map(doc.email, doc); } }">>}], | |
create_view("user", Functions); | |
create_view(association) -> | |
Functions = [{<<"full">>, <<"function(doc) { for (var i in doc) { if (doc.TYPE && i.indexOf('_id', 1) != -1) { map([doc.TYPE, i, doc[i]], doc); } } }">>}], | |
create_view("association", Functions). | |
create_view(ViewName, Functions) -> | |
JsonObject = list_to_binary(mochijson2:encode( | |
{struct, [ {<<"views">>, {struct, Functions}}]})), | |
case http:request('put', {Server ++ rr_util:quote_plus("_design/" ++ ViewName), | |
[], ?MIME_TYPE, JsonObject}, [], []) of | |
{ok, {Status, _Headers, Body}} -> | |
case Status of | |
{_, 201, _} -> | |
{struct, ResultProps} = mochijson2:decode(Body), | |
{ok, proplists:get_value(<<"id">>, ResultProps), | |
proplists:get_value(<<"rev">>, ResultProps)}; | |
Other -> | |
Other | |
end; | |
Other -> | |
Other | |
end. | |
lookup(Type, {Key, Value}) -> | |
case http:request(rr_util:concat([Server, "_view/", Type, "/by_", Key, "?key=", | |
rr_util:quote_plus(rr_util:concat(["\"", Value, "\""]))])) of | |
{ok, {Status, _Headers, Body}} -> | |
{ok, Rows} = process_rows(Status, Body), | |
case length(Rows) of | |
0 -> | |
undefined; | |
_N -> | |
lists:nth(1, Rows) | |
end; | |
Other -> | |
Other | |
end. | |
find(Type, {Key, Value}, [cache]) -> | |
case rr_cache:fetch({Type, Key, Value}) of | |
{ok, Result} -> | |
Result; | |
not_found -> | |
Result = find(Type, {Key, Value}), | |
rr_cache:store({Type, Key, Value}, Result), | |
Result | |
end. | |
find(Type, {Key, Value}) -> | |
case http:request(lists:concat([Server, "_view/association/full?key=", | |
rr_util:quote_plus(rr_util:concat(["[\"", Type, "\", \"", Key, "\", \"", Value, "\"]"]))])) of | |
{ok, {Status, _Headers, Body}} -> | |
process_rows(Status, Body); | |
Other -> | |
Other | |
end. | |
process_rows(Status, Body) -> | |
case Status of | |
{_, 200, _} -> | |
{struct, ResultSet} = mochijson2:decode(Body), | |
Rows = proplists:get_value(<<"rows">>, ResultSet), | |
{ok, lists:map( | |
fun({struct, Row}) -> | |
{struct, PropList} = proplists:get_value(<<"value">>, Row), | |
rr_util:atomize_proplist(PropList) | |
end, Rows)}; | |
Other -> | |
Other | |
end. | |
create(PropList) -> | |
PropList1 = lists:map(fun ({Key, Val}) -> {rr_util:to_binary(Key), rr_util:to_binary(Val)} end, PropList), | |
JsonObject = list_to_binary(mochijson2:encode({struct, PropList1})), | |
case http:request('post', {Server, [], ?MIME_TYPE, JsonObject}, [], []) of | |
{ok, {Status, _Headers, Body}} -> | |
case Status of | |
{_, 201, _} -> | |
{struct, ResultProps} = mochijson2:decode(Body), | |
{ok, proplists:get_value(<<"id">>, ResultProps), | |
proplists:get_value(<<"rev">>, ResultProps)}; | |
{_, 404, _} -> | |
{error, "Database does not exist!"}; | |
{_, 500, _} -> | |
{error, "Internal server error; check server logs", JsonObject}; | |
{_, Number, _} -> | |
{error, "Unhandled HTTP code " ++ integer_to_list(Number)} | |
end; | |
Error -> | |
Error | |
end. | |
update(DocId, PropList) -> | |
case current_revision(DocId) of | |
{ok, Revision} -> | |
update(DocId, Revision, PropList); | |
Error -> | |
Error | |
end. | |
update(DocId, Revision, PropList) -> | |
PropList1 = rr_util:binarize_proplist(PropList) ++ | |
[{<<"_id">>, rr_util:to_binary(DocId)}, | |
{<<"_rev">>, rr_util:to_binary(Revision)}], | |
JsonObject = list_to_binary(mochijson2:encode({struct, PropList1})), | |
case http:request('put', {Server ++ rr_util:quote_plus(DocId), [], ?MIME_TYPE, JsonObject}, [], []) of | |
{ok, {Status, _Headers, Body}} -> | |
case Status of | |
{_, 201, _} -> | |
{struct, ResultProps} = mochijson2:decode(Body), | |
{ok, proplists:get_value(<<"rev">>, ResultProps)}; | |
{_, 409, _} -> | |
{error, "Edit conflict"}; | |
{_, Number, _} -> | |
{error, "Unhandled HTTP code " ++ integer_to_list(Number)} | |
end; | |
Error -> | |
Error | |
end. | |
delete(DocId) when is_binary(DocId) -> | |
delete(binary_to_list(DocId)); | |
delete(DocId) -> | |
case current_revision(DocId) of | |
{ok, Revision} -> | |
delete(DocId, Revision); | |
Error -> | |
Error | |
end. | |
delete(DocId, Revision) when is_integer(Revision) -> | |
delete(DocId, integer_to_list(Revision)); | |
delete(DocId, Revision) -> | |
case http:request('delete', | |
{Server ++ rr_util:quote_plus(DocId) | |
++ "?rev=" ++ rr_util:quote_plus(Revision), []}, [], []) of | |
{ok, {Status, _Headers, Body}} -> | |
case Status of | |
{_, 202, _} -> | |
{struct, ResultProps} = mochijson2:decode(Body), | |
{ok, proplists:get_value(<<"rev">>, ResultProps)}; | |
{_, 409, _} -> | |
{error, edit_conflict}; | |
{_, Number, _} -> | |
{error, "Unhandled HTTP code " ++ integer_to_list(Number) ++ " " ++ Body} | |
end; | |
Error -> | |
Error | |
end. | |
parse_upload(Req, Type, Doc) -> | |
mochiweb_multipart:parse_multipart_request(Req, | |
fun(Part) -> | |
handle_upload(Part, {{Type, Doc, 0}, false}) | |
end). | |
parse_upload_in_memory(Req) -> | |
mochiweb_multipart:parse_multipart_request(Req, | |
fun(Part) -> | |
handle_upload_in_memory(Part, []) | |
end). | |
delete_file(Id) -> | |
file:delete(rr_util:concat([PhotoRoot, Id])). | |
handle_upload({headers, Headers}, {{Type, Props, UploadCount}, _}) -> | |
case Headers of | |
[{"content-disposition", {"form-data", [{"name", _}, {"filename", [_NonEmpty|_Filename]}]}}|_] -> | |
{ok, DocId, _Rev} = create([ | |
{"TYPE", Type}, | |
{"upload_number", integer_to_list(UploadCount)}] ++ Props), | |
{ok, FileDev} = file:open(rr_util:concat( | |
[PhotoRoot, binary_to_list(DocId)]), [raw,write]), | |
fun(Part) -> handle_upload(Part, {{Type, Props, UploadCount + 1}, FileDev}) end; | |
_ -> | |
fun(Part) -> handle_upload(Part, {{Type, Props, UploadCount}, false}) end | |
end; | |
handle_upload({body, _Data}, {Info, false}) -> | |
fun(Part) -> handle_upload(Part, {Info, false}) end; | |
handle_upload({body, Data}, {Info, FileDev}) -> | |
file:write(FileDev, Data), | |
fun(Part) -> handle_upload(Part, {Info, FileDev}) end; | |
handle_upload(body_end, {Info, false}) -> | |
fun(Part) -> handle_upload(Part, {Info, false}) end; | |
handle_upload(body_end, {Info, FileDev}) -> | |
file:close(FileDev), | |
fun(Part) -> handle_upload(Part, {Info, false}) end; | |
handle_upload(eof, _) -> | |
ok. | |
handle_upload_in_memory({headers, _Header}, RawData) -> | |
fun(N) -> handle_upload_in_memory(N, RawData) end; | |
handle_upload_in_memory({body, Data}, RawData) -> | |
fun(N) -> handle_upload_in_memory(N, RawData ++ binary_to_list(Data)) end; | |
handle_upload_in_memory(body_end, RawData) -> | |
fun(N) -> handle_upload_in_memory(N, RawData) end; | |
handle_upload_in_memory(eof, RawData) -> | |
RawData. | |
current_revision(DocId) -> | |
case fetch(DocId) of | |
{ok, PropList} -> | |
{ok, proplists:get_value('_rev', PropList)}; | |
Error -> | |
Error | |
end. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(rr_util). | |
-compile(export_all). | |
% more tolerant versions of list_to_integer and list_to_float | |
integerize("-" ++ Input) -> | |
integerize(Input, 0, negative); | |
integerize(Input) -> | |
integerize(Input, 0, positive). | |
integerize([C|Rest], Number, Sign) when C >= $0 andalso C =< $9-> | |
integerize(Rest, Number * 10 + C - $0, Sign); | |
integerize(_, Number, negative) -> | |
-1 * Number; | |
integerize(_, Number, positive) -> | |
Number. | |
floatize(Input) -> | |
floatize(Input, 0.0). | |
floatize([C|Rest], Number) when C >= $0 andalso C =< $9 -> | |
floatize(Rest, Number * 10 + C - $0); | |
floatize([$.|Rest], Number) -> | |
floatize(Rest, Number, 0.1); | |
floatize(_, Number) -> | |
Number. | |
floatize([C|Rest], Number, DecimalPlace) when C >= $0 andalso C =< $9 -> | |
floatize(Rest, Number + (C - $0) * DecimalPlace, DecimalPlace / 10); | |
floatize(_, Number, _) -> | |
Number. | |
calendarize(Input) -> | |
{Month, Day, Year} = parse_date(Input), | |
list_to_integer(Year) * 100 * 100 + | |
list_to_integer(Month) * 100 + | |
list_to_integer(Day). | |
increment_property(undefined, Group) -> | |
Group; | |
increment_property(Property, Group) -> | |
case lists:keysearch(Property, 1, Group) of | |
false -> | |
[{Property, "1"} | Group]; | |
{value, {_, N}} -> | |
lists:keyreplace(Property, 1, Group, | |
{Property, integer_to_list(list_to_integer(N) + 1)}) | |
end. | |
mark_group(NarrowBy, NarrowTo, ThisGroup, Group) when is_list(NarrowTo) -> | |
mark_group(NarrowBy, list_to_binary(NarrowTo), ThisGroup, Group); | |
mark_group(NarrowBy, NarrowTo, ThisGroup, Group) -> | |
lists:map(fun({Name, Count}) -> | |
{Name, Count, NarrowBy =:= ThisGroup andalso NarrowTo =:= Name} | |
end, Group). | |
rows_of(Number, List) -> | |
lists:reverse(rows_of(Number, List, [])). | |
rows_of(Number, List, Acc) -> | |
case length(List) > Number of | |
true -> | |
rows_of(Number, lists:nthtail(Number, List), [lists:sublist(List, Number) | Acc]); | |
false -> | |
[lists:sublist(List, Number) | Acc] | |
end. | |
set_return_cookies(Url, Text) when is_binary(Text) -> | |
set_return_cookies(Url, binary_to_list(Text)); | |
set_return_cookies(Url, Text) -> | |
[mochiweb_cookies:cookie("return", | |
mochiweb_util:quote_plus(Url), [{path, "/"}]), | |
mochiweb_cookies:cookie("return_text", | |
mochiweb_util:quote_plus(Text), [{path, "/"}])]. | |
get_return_cookies(Req) -> | |
[{return, case Req:get_cookie_value("return") of | |
undefined -> "/train/collection"; | |
Return -> mochiweb_util:unquote(Return) | |
end}, | |
{return_text, case Req:get_cookie_value("return_text") of | |
undefined -> "collection"; | |
Text -> mochiweb_util:unquote(Text) end}]. | |
first(_, _, []) -> | |
undefined; | |
first(Attribute, Value, [H|T]) -> | |
case proplists:get_value(Attribute, H) of | |
Value -> | |
H; | |
_ -> | |
first(Attribute, Value, T) | |
end. | |
concat([H|T]) when is_atom(H) -> | |
atom_to_list(H) ++ concat(T); | |
concat([H|T]) when is_binary(H) -> | |
binary_to_list(H) ++ concat(T); | |
concat([H|T]) when is_list(H) -> | |
H ++ concat(T); | |
concat(Other) -> | |
Other. | |
parse_date(Input) when is_binary(Input) -> | |
parse_date(binary_to_list(Input)); | |
parse_date(Input) when is_list(Input) -> | |
Tokens = string:tokens(Input, "/"), | |
case Tokens of | |
[Year] -> | |
{"0", "0", Year}; | |
[Month,Year] -> | |
{Month, "0", Year}; | |
[Month,Day,Year] -> | |
{Month, Day, Year}; | |
_ -> | |
{"0", "0", "0"} | |
end; | |
parse_date(_) -> | |
{"0", "0", "0"}. | |
atomize_proplist(PropList) -> | |
lists:map(fun | |
({K, {struct, Props}}) -> | |
{to_atom(K), {struct, atomize_proplist(Props)}}; | |
({K, V}) -> | |
{to_atom(K), to_binary(V)} | |
end, PropList). | |
sort_proplist(PropList) -> | |
lists:sort(fun({KeyA, _}, {KeyB, _}) -> KeyA =< KeyB end, PropList). | |
binarize_proplist(PropList) -> | |
lists:map(fun({K, V}) -> {to_binary(K), to_binary(V)} end, PropList). | |
to_atom(Atom) when is_atom(Atom) -> | |
Atom; | |
to_atom(List) when is_list(List) -> | |
list_to_atom(List); | |
to_atom(Bin) when is_binary(Bin) -> | |
to_atom(binary_to_list(Bin)). | |
to_binary(Atom) when is_atom(Atom) -> | |
to_binary(atom_to_list(Atom)); | |
to_binary(List) when is_list(List) -> | |
list_to_binary(List); | |
to_binary(Bin) -> | |
Bin. | |
to_integer(List) when is_list(List) -> | |
list_to_integer(List); | |
to_integer(Bin) when is_binary(Bin) -> | |
to_integer(binary_to_list(Bin)). | |
quote_plus(Bin) when is_binary(Bin) -> | |
quote_plus(binary_to_list(Bin)); | |
quote_plus(List) -> | |
mochiweb_util:quote_plus(List). | |
format_now(Time) -> | |
{{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_local_time(Time), | |
integer_to_list(Year) ++ "." ++ integer_to_list(Month) ++ "." ++ integer_to_list(Day) ++ | |
" "++integer_to_list(Hour)++":"++integer_to_list(Minute)++":"++integer_to_list(Second). | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment