├── Makefile ├── README.md ├── apps └── esli │ ├── ebin │ └── .gitignore │ ├── include │ └── esli.hrl │ └── src │ ├── esli.app.src │ ├── esli.erl │ ├── esli_admin.erl │ ├── esli_app.erl │ ├── esli_checker.erl │ ├── esli_conf.erl │ ├── esli_id_link.erl │ ├── esli_riakc_client.erl │ ├── esli_riakc_handler.erl │ ├── esli_riakc_sup.erl │ ├── esli_sup.erl │ └── esli_web.erl ├── deps └── .gitignore ├── html ├── css │ └── style.css ├── index.html ├── js │ └── helper.js ├── not_found.html └── server_error.html ├── rebar ├── rebar.config ├── rel ├── files │ ├── app.config │ ├── erl │ ├── esli │ ├── nodetool │ └── vm.args └── reltool.config └── riak_search_schema.txt /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Makefile for esli 3 | # Artem Golovinsky 4 | 5 | REBAR = ./rebar 6 | REL_ROOT = rel/esli 7 | HTDOCS = $(REL_ROOT)/var/htdocs 8 | 9 | all: get-deps compile generate 10 | 11 | 12 | get-deps: 13 | $(REBAR) get-deps 14 | 15 | compile: get-deps 16 | $(REBAR) compile 17 | 18 | generate: get-deps compile 19 | $(REBAR) generate 20 | chmod 755 $(REL_ROOT)/bin/esli 21 | cp -R html/* $(HTDOCS) 22 | 23 | clean: 24 | $(REBAR) clean 25 | 26 | clean-all: clean 27 | $(REBAR) delete-deps -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **ESLI** is web-url shortener is written on Erlang ## 2 | 3 | ### Dependencies ### 4 | 5 | Riak key-store 6 | 7 | * [http://wiki.basho.com](http://wiki.basho.com "Riak Homepage") 8 | 9 | * [https://github.com/basho/riak](https://github.com/basho/riak "Riak on github") 10 | 11 | ### Installation and using ### 12 | 13 | 1. Get it from github: 14 | 15 | `$ git clone https://github.com/2garryn/esli` 16 | 17 | 2. Go to _esli_ directory and make it: 18 | 19 | `$ make` 20 | 21 | 3. Before using _esli_ start your _Riak_ node. 22 | 4. When _Riak_ node is started you can start _esli_: 23 | 24 | `$ rel/esli/bin/esli start` 25 | 26 | `$ rel/esli/bin/esli ping` 27 | 28 | `pong` 29 | 30 | 5. To stop url-shortener: 31 | 32 | `$ rel/esli/bin/esli stop` 33 | 34 | By default _esli_ server is listening 8081 port of http://localhost, i.e. to make short link you should open http://localhost:8081/ in your web-browser. 35 | On real server you can set other port and host in etc/app.config in _esli_ directory. Anyway, look at this file :-). 36 | All data is written to _sl_ bucket in _Riak_. 37 | 38 | 39 | Also, you can look at esli/var/htdocs, there is html-pages, which user get. 40 | 41 | ### License ### 42 | 43 | Copyright (c) 2011 Artem Golovinsky . 44 | 45 | All rights reserved. 46 | 47 | Redistribution and use in source and binary forms are permitted 48 | provided that the above copyright notice and this paragraph are 49 | duplicated in all such forms and that any documentation, 50 | advertising materials, and other materials related to such 51 | distribution and use acknowledge that the software was developed 52 | by the . The name of the 53 | University may not be used to endorse or promote products derived 54 | from this software without specific prior written permission. 55 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 56 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 57 | WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 58 | 59 | -------------------------------------------------------------------------------- /apps/esli/ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2garryn/esli/6e48337b066bb230813317dcda613993c2929684/apps/esli/ebin/.gitignore -------------------------------------------------------------------------------- /apps/esli/include/esli.hrl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli.hrl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description: 5 | %%% --------------------------------------------------------------- 6 | 7 | %% riakc handler process identifier 8 | -define(RC_HANDLER, rc_handler). 9 | 10 | %% riakc supervisor identifier 11 | -define(SV_NAME, esli_riakc_sup). 12 | 13 | %% Name of Readers 14 | -define(WC_NAME, "riakc_client"). 15 | 16 | %% Name of writers 17 | 18 | -define(SL_BUCKET, <<"esli">>). 19 | 20 | -define(ID_LINK, id_link). 21 | 22 | -define(SUP_NAME, esli_sup). 23 | 24 | -define(CHARS, "abcdefghjiklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ_-1234567890"). 25 | 26 | -define(MAX_LENGTH, 6). 27 | 28 | 29 | -define(MAX_LONG_LINK_LENGTH, 32768). 30 | -------------------------------------------------------------------------------- /apps/esli/src/esli.app.src: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli.app.src 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : 5 | %%% --------------------------------------------------------------- 6 | {application, esli, 7 | [ 8 | {description, "esli - to short web-links"}, 9 | {vsn, "1"}, 10 | {registered, []}, 11 | {applications, [ 12 | kernel, 13 | stdlib, 14 | riakc, 15 | mochiweb 16 | ]}, 17 | {mod, { esli_app, []}}, 18 | {env, []} 19 | ]}. 20 | -------------------------------------------------------------------------------- /apps/esli/src/esli.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : Interface module for esli-core. 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli). 8 | 9 | -export([get_short_link/1, get_full_link/1]). 10 | 11 | 12 | %%% Get short link Id from full link 13 | 14 | get_short_link(LongLink) -> 15 | esli_riakc_handler:request_short_link(self(), LongLink), 16 | receive 17 | {short_link, SLId} -> 18 | {short_link, SLId}; 19 | _ -> 20 | {error, 501} 21 | after 22 | 10000 -> 23 | {error, 501} 24 | end. 25 | 26 | %%% Get full link from short link Id 27 | 28 | get_full_link(SLId) -> 29 | esli_riakc_handler:request_full_link(self(), SLId), 30 | receive 31 | {full_link, FullLink} -> 32 | {full_link,FullLink}; 33 | {error, 404} -> 34 | {error, 404}; 35 | _ -> 36 | {error, 501} 37 | after 38 | 10000 -> 39 | {error, 501} 40 | end. 41 | 42 | -------------------------------------------------------------------------------- /apps/esli/src/esli_admin.erl: -------------------------------------------------------------------------------- 1 | -module(esli_admin). 2 | 3 | -export([start_link/2,count_url_per_date/2,remove_last_month/1]). 4 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 5 | terminate/2, code_change/3]). 6 | -record(state, {pid}). 7 | -include("esli.hrl"). 8 | -define(SEC_IN_MONTH, 2592000). %% 30 days 9 | -define(REQUEST_TIMEOUT, 30000). 10 | %%% -------------------------------------------------------- 11 | %%% External API 12 | %%% -------------------------------------------------------- 13 | start_link(Host, Port) -> 14 | gen_server:start_link({local,?MODULE},?MODULE,[Host, Port],[]). 15 | 16 | count_url_per_date(Date1, Date2) -> 17 | gen_server:call(?MODULE, {count_records, Date1, Date2}, ?REQUEST_TIMEOUT). 18 | 19 | remove_last_month(Type) -> 20 | gen_server:cast(?MODULE, {remove_last_month, Type}). 21 | 22 | %%% -------------------------------------------------------- 23 | %%% Callbacks 24 | %%% -------------------------------------------------------- 25 | 26 | init([Host, Port]) -> 27 | {ok, Pid} = riakc_pb_socket:start_link(Host, Port), 28 | {ok,#state{pid=Pid}}. 29 | 30 | handle_call({count_records, Date1, Date2}, _From, State) -> 31 | SecDate1 = calendar:datetime_to_gregorian_seconds(Date1), 32 | SecDate2 = calendar:datetime_to_gregorian_seconds(Date2), 33 | Request = "created:[" ++ integer_to_list(SecDate1) ++ " TO " ++ integer_to_list(SecDate2) ++ "]", 34 | {ok, List} = riakc_pb_socket:search(State#state.pid, ?SL_BUCKET, Request), 35 | {reply, length(List), State}. 36 | 37 | handle_cast({remove_last_month, Type}, State) -> 38 | Rtype = 39 | case Type of 40 | created -> 41 | "created"; 42 | got -> 43 | "got" 44 | end, 45 | Now = calendar:datetime_to_gregorian_seconds({erlang:date(), 46 | erlang:time()}), 47 | LastMonth = Now - ?SEC_IN_MONTH, 48 | Request = Rtype ++ ":[0 TO " ++ integer_to_list(LastMonth) ++ "]", 49 | {ok, List} = riakc_pb_socket:search(State#state.pid, ?SL_BUCKET, Request), 50 | remove_all(State#state.pid, List), 51 | {noreply, State}. 52 | 53 | handle_info(_Info, State) -> 54 | {noreply, State}. 55 | 56 | 57 | terminate(Reason, _State) -> 58 | error_logger:error_msg("Crash riak client: ~p ~n", [Reason]), 59 | ok. 60 | 61 | code_change(_OldVsn, State, _Extra) -> 62 | {ok, State}. 63 | 64 | remove_all(Pid, [[Bucket, Key]|Other]) -> 65 | riakc_pb_socket:delete(Pid, Bucket, Key), 66 | remove_all(Pid, Other); 67 | remove_all(_, []) -> 68 | error_logger:info_msg("All removed"). 69 | -------------------------------------------------------------------------------- /apps/esli/src/esli_app.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_app.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli_app). 8 | 9 | -behaviour(application). 10 | 11 | %% Application callbacks 12 | -export([start/2, stop/1]). 13 | 14 | %% =================================================================== 15 | %% Application callbacks 16 | %% =================================================================== 17 | 18 | start(_StartType, _StartArgs) -> 19 | esli_sup:start_link(). 20 | 21 | stop(_State) -> 22 | ok. 23 | -------------------------------------------------------------------------------- /apps/esli/src/esli_checker.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_checker.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : 5 | %%% --------------------------------------------------------------- 6 | 7 | 8 | -module(esli_checker). 9 | 10 | -include("esli.hrl"). 11 | 12 | -export([check_short/1, check_and_update_full/1]). 13 | 14 | 15 | check_short(Sh) -> 16 | F = fun(El) -> 17 | lists:member(El, ?CHARS) 18 | end, 19 | L = lists:filter(F, Sh), 20 | if 21 | length(L) =:= ?MAX_LENGTH -> 22 | true; 23 | true -> 24 | false 25 | end. 26 | 27 | check_and_update_full(Full) when length(Full) < ?MAX_LONG_LINK_LENGTH -> 28 | case check_1(Full) of 29 | {true, NewStr} -> 30 | {true, add_protocol(NewStr, NewStr)}; 31 | false -> 32 | false 33 | end; 34 | 35 | check_and_update_full(_) -> 36 | false. 37 | 38 | 39 | %% check that full url is not space 40 | check_1(Full) -> 41 | case string:strip(Full, both, 32) of 42 | [] -> 43 | false; 44 | NewStr -> 45 | check_2(NewStr) 46 | end. 47 | 48 | 49 | %% url should not contain spaces 50 | check_2(Full) -> 51 | case length(string:tokens(Full, " ")) of 52 | 1 -> 53 | {true, Full}; 54 | _ -> 55 | false 56 | end. 57 | 58 | %% add protocol if it is not in URL 59 | add_protocol("http://" ++ _Other, Full) -> 60 | Full; 61 | 62 | add_protocol("https://" ++ _Other, Full) -> 63 | Full; 64 | 65 | add_protocol("ftp://" ++ _Other, Full) -> 66 | Full; 67 | 68 | add_protocol("gopher://" ++ _Other, Full) -> 69 | Full; 70 | 71 | add_protocol("mailto://" ++ _Other, Full) -> 72 | Full; 73 | 74 | add_protocol("news://" ++ _Other, Full) -> 75 | Full; 76 | 77 | add_protocol("irc://" ++ _Other, Full) -> 78 | Full; 79 | 80 | add_protocol("telnet://" ++ _Other, Full) -> 81 | Full; 82 | 83 | add_protocol("wais://" ++ _Other, Full) -> 84 | Full; 85 | 86 | add_protocol("xmpp://" ++ _Other, Full) -> 87 | Full; 88 | 89 | add_protocol("file://" ++ _Other, Full) -> 90 | Full; 91 | 92 | add_protocol("data://" ++ _Other, Full) -> 93 | Full; 94 | 95 | add_protocol(_, Full) -> 96 | "http://" ++ Full. 97 | -------------------------------------------------------------------------------- /apps/esli/src/esli_conf.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_conf.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : Helper for getting settings from app.config 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli_conf). 8 | 9 | -export([get_config/1]). 10 | 11 | get_config(Var) -> 12 | {ok, Value} = application:get_env(esli, Var), 13 | Value. 14 | 15 | 16 | -------------------------------------------------------------------------------- /apps/esli/src/esli_id_link.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_id_link.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : Generator of new web links id. 5 | %%% 6 | %%% --------------------------------------------------------------- 7 | -module(esli_id_link). 8 | 9 | -export([get_id/1, get_solted_id/1]). 10 | 11 | -include("esli.hrl"). 12 | 13 | get_id(Text) -> 14 | <> = erlang:md5(Text), 15 | additional_chars([lists:nth(Elem + 1, ?CHARS) || Elem <- transform(Bits)]). 16 | 17 | 18 | get_solted_id(Text1) -> 19 | F = float_to_list(random:uniform()), 20 | get_id(Text1 ++ F). 21 | 22 | %%% Convert 10 to 64 23 | transform(Bits) -> 24 | transform([], Bits). 25 | 26 | transform(Num, S) when S < 64 -> 27 | [S|Num]; 28 | 29 | transform(Ar,Some) -> 30 | New = Some rem 64, 31 | transform([New|Ar],Some div 64). 32 | 33 | additional_chars(ConvList) when length(ConvList) =:= ?MAX_LENGTH -> 34 | ConvList; 35 | 36 | additional_chars(ConvList) -> 37 | additional_chars([lists:nth(1, ?CHARS) | ConvList]). 38 | -------------------------------------------------------------------------------- /apps/esli/src/esli_riakc_client.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_riakc_client.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : It is wrapper for riakc_pb client 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli_riakc_client). 8 | 9 | -behaviour(gen_server). 10 | 11 | %% API 12 | -export([start_link/3, get_short_link/3, get_full_link/3]). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 16 | terminate/2, code_change/3]). 17 | 18 | -record(state, {pid, name, req_id}). 19 | 20 | -include("esli.hrl"). 21 | 22 | %% ------------------------------------------- 23 | %% Interface function 24 | %% ------------------------------------------- 25 | start_link(Name, Host, Port) -> 26 | gen_server:start_link({local, Name}, ?MODULE, [Name, Host, Port], []). 27 | 28 | get_short_link(FreeW, ReqPid, LongLink) -> 29 | gen_server:cast(FreeW, {get_short_link, ReqPid, LongLink}). 30 | 31 | get_full_link(Free, ReqPid, ShortLink) -> 32 | gen_server:cast(Free, {get_full_link, ReqPid, ShortLink}). 33 | 34 | %% ------------------------------------------ 35 | %% Callback 36 | %% ------------------------------------------ 37 | 38 | init([Name, Host, Port]) -> 39 | {ok, Pid} = riakc_pb_socket:start_link(Host, Port), 40 | ?RC_HANDLER ! {free, Name}, 41 | {ok, #state{pid=Pid, name=Name}}. 42 | 43 | handle_call(_Request, _From, State) -> 44 | Reply = ok, 45 | {reply, Reply, State}. 46 | 47 | handle_cast({get_short_link, ReqPid, LongLink}, State) -> 48 | EndedId = get_my_id(LongLink, State#state.pid), 49 | Obj = riakc_obj:new(?SL_BUCKET, 50 | list_to_binary(EndedId), 51 | json_view(list_to_binary(LongLink), list_to_binary(EndedId)), 52 | "application/json"), 53 | case riakc_pb_socket:put(State#state.pid, Obj) of 54 | ok -> 55 | ReqPid ! {short_link, EndedId}; 56 | SomeThing -> 57 | error_logger:error_msg("Put to riak error: ~p ~n", [SomeThing]), 58 | ReqPid ! error 59 | end, 60 | ?RC_HANDLER ! {free, State#state.name}, 61 | {noreply, State}; 62 | 63 | 64 | handle_cast({get_full_link, ReqPid, ShortLink}, State) -> 65 | case riakc_pb_socket:get(State#state.pid,?SL_BUCKET, list_to_binary(ShortLink)) of 66 | {ok, RiakData} -> 67 | Full = get_full_from_obj(RiakData), 68 | ReqPid ! {full_link, binary_to_list(Full)}, 69 | update_got_date(RiakData, State#state.pid); 70 | _ -> 71 | ReqPid ! {error, 404} 72 | end, 73 | ?RC_HANDLER ! {free, State#state.name}, 74 | {noreply, State}. 75 | 76 | handle_info(_Info, State) -> 77 | {noreply, State}. 78 | 79 | 80 | terminate(Reason, _State) -> 81 | error_logger:error_msg("Crash riak client: ~p ~n", [Reason]), 82 | ok. 83 | 84 | code_change(_OldVsn, State, _Extra) -> 85 | {ok, State}. 86 | 87 | get_my_id(Text, Pid) -> 88 | get_my_pid(Text, esli_id_link:get_id(Text), Pid). 89 | 90 | get_my_pid(Text, LinkId, Pid) -> 91 | case riakc_pb_socket:get(Pid, ?SL_BUCKET, list_to_binary(LinkId)) of 92 | {error, notfound} -> 93 | LinkId; 94 | _ -> 95 | NewLI = esli_id_link:get_solted_id(Text), 96 | get_my_pid(Text, NewLI, Pid) 97 | end. 98 | 99 | 100 | 101 | 102 | %%% --------------------------------------------- 103 | %%% JSON view of long link 104 | %%% 105 | %%% {link: "http://long_link", 106 | %%% short: "qWeD12", 107 | %%% created: 123123123, - unix epoch 108 | %%% updated: 123123123} 109 | 110 | json_view(Long, Short) -> 111 | UnixEpoch = unix_epoch_now(), 112 | Json = struct(Long, UnixEpoch, UnixEpoch, Short), 113 | iolist_to_binary(mochijson2:encode(Json)). 114 | 115 | 116 | struct(Long,Created, Updated, Short) -> 117 | {struct, 118 | [ 119 | {link, Long}, 120 | {short,Short}, 121 | {created, Created}, 122 | {got, Updated} 123 | ] 124 | }. 125 | 126 | unix_epoch_now() -> 127 | Date = erlang:date(), 128 | Time = erlang:time(), 129 | calendar:datetime_to_gregorian_seconds({Date,Time}). 130 | 131 | get_full_from_obj(RiakData) -> 132 | case riakc_obj:get_content_type(RiakData) of 133 | "application/json" -> 134 | {struct, Fields} = mochijson2:decode(riakc_obj:get_value(RiakData)), 135 | proplists:get_value(<<"link">>, Fields, "not_found"); 136 | _ -> 137 | riakc_obj:get_value(RiakData) 138 | end. 139 | 140 | 141 | 142 | update_got_date(RiakData, Pid) -> 143 | PutToRiak = 144 | case riakc_obj:get_content_type(RiakData) of 145 | "application/json" -> 146 | {struct, Fields} = mochijson2:decode(riakc_obj:get_value(RiakData)), 147 | NewList = lists:keyreplace(<<"got">>, 1, Fields, {<<"got">>, unix_epoch_now()}), 148 | NewObj = iolist_to_binary(mochijson2:encode({struct, NewList})), 149 | riakc_obj:update_value(RiakData, NewObj); 150 | _ -> 151 | NewJson = json_view(riakc_obj:get_value(RiakData), riakc_obj:key(RiakData)), 152 | riakc_obj:update_value(RiakData, NewJson, "application/json") 153 | end, 154 | riakc_pb_socket:put(Pid, PutToRiak). 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /apps/esli/src/esli_riakc_handler.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_riakc_handler.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : Handler of pull of esli_riakc_clients 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli_riakc_handler). 8 | 9 | -behaviour(gen_server). 10 | 11 | %%% Defines 12 | 13 | -include("esli.hrl"). 14 | %% API 15 | -export([start_link/3, request_short_link/2, request_full_link/2]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 19 | terminate/2, code_change/3]). 20 | 21 | -record(workers, {free=[], 22 | busy=[] 23 | }). 24 | %%% -------------------------------------------------------- 25 | %%% External API 26 | %%% -------------------------------------------------------- 27 | 28 | start_link(Host, Port, Number) -> 29 | gen_server:start_link({local, ?RC_HANDLER}, ?MODULE, [Host, Port, Number], []). 30 | 31 | request_short_link(ReqPid, LongLink) -> 32 | gen_server:cast(?RC_HANDLER, {req_short, ReqPid, LongLink}). 33 | 34 | request_full_link(ReqPid, ShLink) -> 35 | gen_server:cast(?RC_HANDLER, {req_full, ReqPid, ShLink}). 36 | 37 | 38 | %%% -------------------------------------------------------- 39 | %%% Callbacks 40 | %%% -------------------------------------------------------- 41 | 42 | 43 | 44 | init([Host, Port, Number]) -> 45 | start_processes(Host, Port, Number), 46 | State = #workers{free=[], busy=[]}, 47 | {ok, State}. 48 | 49 | handle_call(_Request, _From, State) -> 50 | Reply = ok, 51 | {reply, Reply, State}. 52 | 53 | 54 | handle_cast({req_short, ReqPid, LongLink}, State) -> 55 | case get_free(State) of 56 | {ok, Free, NewState} -> 57 | send2worker(Free, ReqPid, LongLink); 58 | {error, NewState} -> 59 | error_logger:error_msg("No free workers ~n"), 60 | ReqPid ! error 61 | end, 62 | {noreply, NewState}; 63 | 64 | handle_cast({req_full, ReqPid, ShortLink}, State) -> 65 | case get_free(State) of 66 | {ok, Free, NewState} -> 67 | get_from_worker(Free, ReqPid, ShortLink); 68 | {error, NewState} -> 69 | error_logger:error_msg("No free workers ~n"), 70 | ReqPid ! error 71 | end, 72 | {noreply, NewState}. 73 | 74 | handle_info({free, Id}, State) -> 75 | NewState = make_free(Id, State), 76 | {noreply, NewState}. 77 | 78 | terminate(_Reason, _State) -> 79 | ok. 80 | code_change(_OldVsn, State, _Extra) -> 81 | {ok, State}. 82 | 83 | 84 | %%% -------------------------------------------- 85 | %%% Internal functions 86 | %%% -------------------------------------------- 87 | 88 | start_processes(_Host, _Port, 0) -> 89 | ok; 90 | 91 | start_processes(Host, Port, Number) -> 92 | NewName = list_to_atom(?WC_NAME ++ integer_to_list(Number)), 93 | case supervisor:start_child(?SV_NAME, get_child_spec(Host, Port, NewName)) of 94 | {ok, Child} -> 95 | error_logger:info_msg("Start child proc: ~n name : ~p ~n child : ~p ~n",[NewName, Child]), 96 | start_processes(Host, Port, Number - 1); 97 | {ok, Child, Info} -> 98 | error_logger:info_msg("Start reader child proc: ~n child : ~p ~n info : ~p ~n ~n",[NewName, Child, Info]), 99 | start_processes(Host, Port, Number - 1); 100 | {error, Error} -> 101 | error_logger:error_msg("Error reader child proc start with: ~n name : ~p ~n error ~p ~n",[NewName, Error]), 102 | erlang:error({NewName, Error}) 103 | end. 104 | 105 | 106 | get_free(State) -> 107 | case State#workers.free of 108 | [] -> 109 | {error, State}; 110 | [One|Free] -> 111 | #workers{busy=Busy} = State, 112 | NewState = State#workers{free=Free, 113 | busy=[One|Busy]}, 114 | {ok, One, NewState} 115 | end. 116 | 117 | 118 | send2worker(FreeW, ReqPid, LongLink) -> 119 | esli_riakc_client:get_short_link(FreeW, ReqPid, LongLink). 120 | 121 | get_from_worker(FreeR, ReqPid, ShortLink) -> 122 | esli_riakc_client:get_full_link(FreeR, ReqPid, ShortLink). 123 | 124 | make_free(Id, State) -> 125 | NewBusy = lists:delete(Id, State#workers.busy), 126 | NewFree = [Id|State#workers.free], 127 | State#workers{busy=NewBusy, 128 | free=NewFree}. 129 | 130 | get_child_spec(Host, Port, NewName) -> 131 | {NewName, {esli_riakc_client, start_link, [NewName, Host, Port]}, transient, brutal_kill, worker, [esli_riakc_client]}. 132 | 133 | -------------------------------------------------------------------------------- /apps/esli/src/esli_riakc_sup.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_riakc_sup.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : 5 | %%% --------------------------------------------------------------- 6 | -module(esli_riakc_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | -include("esli.hrl"). 11 | 12 | %% API 13 | -export([start_link/0]). 14 | 15 | %% Supervisor callbacks 16 | -export([init/1]). 17 | 18 | 19 | start_link() -> 20 | supervisor:start_link({local, ?SV_NAME}, ?MODULE, []). 21 | 22 | init([]) -> 23 | {ok,{{one_for_one,10,10}, []}}. 24 | 25 | -------------------------------------------------------------------------------- /apps/esli/src/esli_sup.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_sup.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli_sup). 8 | 9 | -export([start_link/0, init/1]). 10 | 11 | -include("esli.hrl"). 12 | 13 | start_link() -> 14 | supervisor:start_link({local, ?SUP_NAME}, ?MODULE, []). 15 | 16 | init([]) -> 17 | {ok, {{one_for_one, 10, 60}, 18 | [{esli_riakc_sup, {esli_riakc_sup, start_link, []}, 19 | permanent, 5000, supervisor, []}, 20 | {esli_riakc_handler, {esli_riakc_handler, start_link, [esli_conf:get_config(riak_host), 21 | esli_conf:get_config(riak_port), 22 | esli_conf:get_config(worker_set)]}, 23 | permanent, 5000, worker, []}, 24 | {esli_web, {esli_web, start, []}, 25 | permanent, 5000, worker, dynamic}, 26 | {esli_admin, {esli_admin, start_link, [esli_conf:get_config(riak_host), 27 | esli_conf:get_config(riak_port)]}, 28 | permanent, 5000, supervisor, []} 29 | ]}}. 30 | -------------------------------------------------------------------------------- /apps/esli/src/esli_web.erl: -------------------------------------------------------------------------------- 1 | %%% --------------------------------------------------------------- 2 | %%% File : esli_web.erl 3 | %%% Author : Artem Golovinsky artemgolovinsky@gmail.com 4 | %%% Description : "loop"-file for MochiWeb 5 | %%% --------------------------------------------------------------- 6 | 7 | -module(esli_web). 8 | 9 | -include("esli.hrl"). 10 | 11 | -export([start/0, loop/2]). 12 | 13 | -define(REQUEST_TIMEOUT, 3000). 14 | 15 | start() -> 16 | DocRoot = esli_conf:get_config(docroot), 17 | ets:new(clients_ip, [duplicate_bag, 18 | public, 19 | named_table]), 20 | Loop = fun (Req) -> 21 | ?MODULE:loop(Req,DocRoot) 22 | end, 23 | WebConfig = esli_conf:get_config(web), 24 | mochiweb_http:start([{name, ?MODULE}, 25 | {loop, Loop} | WebConfig]). 26 | 27 | loop(Req, DocRoot) -> 28 | "/" ++ Path = Req:get(path), 29 | 30 | try 31 | proceed_method(Path, Req, DocRoot) 32 | catch 33 | Type:What -> 34 | Report = ["web request failed", 35 | {path, Path}, 36 | {type, Type}, {what, What}, 37 | {trace, erlang:get_stacktrace()}], 38 | error_logger:error_report(Report), 39 | server_error(Req) 40 | 41 | end. 42 | 43 | %%% 44 | %%% functions to make "nice" and readable code 45 | %%% 46 | proceed_method(Path, Req, DocRoot) -> 47 | proceed_method2(Req:get(method), Path, Req, DocRoot). 48 | 49 | proceed_method2('GET', Path, Req, DocRoot) -> 50 | proceed_get_path(Path, Req, DocRoot); 51 | 52 | proceed_method2('HEAD', Path, Req, DocRoot) -> 53 | proceed_get_path(Path, Req, DocRoot); 54 | 55 | proceed_method2('POST',Path, Req, DocRoot) -> 56 | proceed_post_path(Path, Req, DocRoot); 57 | 58 | proceed_method2(_, _Path, Req, _DocRoot) -> 59 | bad_request(Req). 60 | 61 | %%% Start short link proceeding 62 | proceed_get_path("favicon.ico", Req, _DocRoot) -> 63 | not_found(Req); 64 | 65 | proceed_get_path([], Req, DocRoot) -> 66 | index_file(Req, DocRoot); 67 | 68 | proceed_get_path(Path, Req, DocRoot) -> 69 | case is_users_file(Path, DocRoot) of 70 | true -> 71 | proceed_file(Path, Req, DocRoot); 72 | false -> 73 | proceed_short_link(Path, Req, DocRoot) 74 | end. 75 | 76 | proceed_short_link(Path, Req, DocRoot) when length(Path) =:= ?MAX_LENGTH -> 77 | get_full_link(esli_checker:check_short(Path), Path, Req, DocRoot); 78 | 79 | proceed_short_link(_Path, Req, DocRoot) -> 80 | not_found_file(Req, DocRoot). 81 | 82 | 83 | get_full_link(true, Path, Req, DocRoot) -> 84 | try esli:get_full_link(Path) of 85 | {full_link, Fl} -> 86 | Req:respond({301,[{"Location",Fl}], []}); 87 | {error, 404} -> 88 | not_found_file(Req, DocRoot); 89 | {error, 501} -> 90 | server_error_file(Req, DocRoot) 91 | catch 92 | _:_ -> 93 | server_error_file(Req, DocRoot) 94 | end; 95 | 96 | get_full_link(false, _Path, Req, DocRoot) -> 97 | not_found_file(Req, DocRoot). 98 | 99 | 100 | proceed_file(Path, Req, DocRoot) -> 101 | Req:serve_file(Path, DocRoot). 102 | %%% Stop short link proceeding. Full link is got 103 | 104 | 105 | %%% Start full link proceeding 106 | proceed_post_path("create", Req, DocRoot) -> 107 | Ip = Req:get(peer), 108 | case ets:member(clients_ip, Ip) of 109 | true -> 110 | Req:respond({423, [], ""}); 111 | false -> 112 | ets:insert(clients_ip, {Ip}), 113 | timer:apply_after(?REQUEST_TIMEOUT, ets, delete, [clients_ip, Ip]), 114 | Link = binary_to_list(Req:recv_body()), 115 | get_short_link(esli_checker:check_and_update_full(Link), Req, DocRoot) 116 | end; 117 | 118 | proceed_post_path(_, Req, _DocRoot) -> 119 | bad_request(Req). 120 | 121 | get_short_link({true, UpdatedLink}, Req, _DocRoot) -> 122 | try esli:get_short_link(UpdatedLink) of 123 | {short_link, SLink} -> 124 | Req:ok({"text/html",[], [esli_conf:get_config(domain) ++ "/" ++ SLink]}); 125 | {error, 501} -> 126 | server_error(Req) 127 | catch 128 | _:_ -> 129 | server_error(Req) 130 | end; 131 | 132 | get_short_link(false, Req, _DocRoot) -> 133 | bad_request(Req). 134 | 135 | %%% Stop full link proceeding. short link is here 136 | 137 | 138 | %%% Helpers for answer 139 | 140 | index_file(Req, DocRoot) -> 141 | Req:serve_file("index.html", DocRoot). 142 | 143 | not_found_file(Req, DocRoot) -> 144 | Req:serve_file("not_found.html", DocRoot). 145 | 146 | server_error_file(Req, DocRoot) -> 147 | Req:serve_file("server_error.html", DocRoot). 148 | 149 | not_found(Req) -> 150 | Req:respond({404, [], ""}). 151 | 152 | server_error(Req) -> 153 | Req:respond({501, [], "server_error"}). 154 | 155 | bad_request(Req) -> 156 | Req:respond({400, [], "bad_request"}). 157 | 158 | is_users_file(Path, DocRoot) -> 159 | filelib:is_file(filename:join(DocRoot, Path)). 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /deps/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2garryn/esli/6e48337b066bb230813317dcda613993c2929684/deps/.gitignore -------------------------------------------------------------------------------- /html/css/style.css: -------------------------------------------------------------------------------- 1 | #logo{ 2 | height: 160px; 3 | } 4 | 5 | #header{ 6 | text-align: center; 7 | height: 20%; 8 | } 9 | 10 | #header_text{ 11 | font-family: arial, verdana, trebuchet; 12 | font-weight: normal; 13 | font-size: 2em; 14 | } 15 | 16 | #field{ 17 | text-align: center; 18 | width: 100%; 19 | height: 10%; 20 | } 21 | 22 | #input_url{ 23 | border: thin solid black; 24 | width: 70%; 25 | padding: 0.3em 0.3em 0.3em 0.3em; 26 | font-family: arial, verdana, trebuchet; 27 | font-weight: normal; 28 | font-size: 0.9em; 29 | } 30 | 31 | #button{ 32 | text-align: right; 33 | } 34 | 35 | #b1{ 36 | padding: 0.3em 0.6em 0.3em 0.6em; 37 | margin-right: 15%; 38 | font-family: arial, verdana, trebuchet; 39 | font-weight: normal; 40 | font-size: 1em; 41 | border: 1px solid black; 42 | background-color: #cccccc; 43 | color: #000000; 44 | } 45 | 46 | #short_link{ 47 | padding-top: 5%; 48 | text-align: center; 49 | } 50 | 51 | #is_link{ 52 | color: #000000; 53 | font-size: 2em; 54 | text-decoration: underline; 55 | } 56 | #is_link:hover{ 57 | color: #000000; 58 | font-size: 2em; 59 | text-decoration: none; 60 | } 61 | 62 | #copy{ 63 | font-family: arial, verdana, trebuchet; 64 | padding-left: 25%; 65 | padding-top: 5%; 66 | color: #999999; 67 | font-size: 1.5em; 68 | } 69 | 70 | #msg_field{ 71 | font-family: serif; 72 | color: #FF0000; 73 | font-size: 2em; 74 | } 75 | 76 | #long_link{ 77 | padding-top: 5%; 78 | text-align: center; 79 | } 80 | 81 | #is_long_link{ 82 | font-size: 1em; 83 | color: #999999; 84 | } 85 | 86 | #counter{ 87 | margin-right: 25%; 88 | font-size: 2em; 89 | color: #FF0000; 90 | } -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | aes.li - web-url shortener 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 | 27 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /html/js/helper.js: -------------------------------------------------------------------------------- 1 | 2 | // just for design 3 | 4 | var link_is_shown = false; 5 | var is_grey = false; 6 | var time = 3; 7 | var timer_descr = 0; 8 | 9 | $(function () { 10 | $(window).load(function () { 11 | $('#input_url').focus(); 12 | }); 13 | }); 14 | $(document).ready(function(){ 15 | $("#copy").hover(function () { 16 | $(this).css( 'cursor', 'pointer'); 17 | $(this).css( 'color', '#333333'); 18 | }, 19 | function () { 20 | $(this).css( 'cursor', 'default'); 21 | $(this).css( 'color', '#999999'); 22 | } 23 | ); 24 | 25 | }); 26 | 27 | $(document).keypress(function(e) { 28 | if(e.which == 13) { 29 | request(); 30 | 31 | } 32 | }); 33 | 34 | 35 | function on_button(){ 36 | $('#b1').css('background-color',"#ffffff"); 37 | $('#b1').css( 'cursor', 'pointer'); 38 | }; 39 | 40 | function out_button(){ 41 | $('#b1').css('background-color',"#cccccc"); 42 | $('#b1').css( 'cursor', 'default'); 43 | }; 44 | 45 | 46 | 47 | function request(){ 48 | 49 | var value = jQuery.trim($('#input_url').val()); 50 | if (value != ""){ 51 | if (check_valid(value)){ 52 | right_url(value); 53 | return true 54 | } 55 | else{ 56 | $('#input_url').val(value); 57 | wrong_url("Url is wrong! Check it :-)"); 58 | return false; 59 | } 60 | } 61 | else { 62 | $('#input_url').val(""); 63 | $('#input_url').focus(); 64 | return false; 65 | }; 66 | } 67 | 68 | function check_valid(value){ 69 | if (value.split(' ').length > 1) 70 | return false 71 | else 72 | return true; 73 | } 74 | 75 | function wrong_url(Msg){ 76 | clear_msg_field() 77 | clear_is_link(); 78 | clear_is_long_link(); 79 | show_msg_field(Msg); 80 | $('#input_url').focus(); 81 | } 82 | 83 | 84 | function clear_msg_field(){ 85 | $('#msg_field').text(""); 86 | } 87 | 88 | function show_msg_field(msg){ 89 | $('#msg_field').fadeIn(150); 90 | $('#msg_field').text(msg); 91 | 92 | } 93 | 94 | function clear_is_link(){ 95 | $('#is_link').attr('href',""); 96 | $('#is_link').text(""); 97 | $('#copy').text(""); 98 | link_is_shown = false; 99 | } 100 | 101 | function clear_is_long_link(){ 102 | $('#is_long_link').attr('href',""); 103 | $('#is_long_link').text(""); 104 | } 105 | 106 | function show_is_link(Link){ 107 | $('#is_link').fadeIn(150); 108 | $('#is_link').attr('href',Link); 109 | $('#is_link').text(Link); 110 | $('#copy').fadeIn(150); 111 | $('#copy').text("-copy-"); 112 | link_is_shown = true; 113 | } 114 | 115 | function show_is_long_link(Link){ 116 | var shortlink; 117 | if (Link.length > 80) 118 | shortlink = Link.substr(0, 80) + "..." 119 | else 120 | shortlink = Link; 121 | 122 | $('#is_long_link').fadeIn(150); 123 | $('#is_long_link').attr('href', Link); 124 | $('#is_long_link').text(shortlink); 125 | } 126 | 127 | function clear_input_url(){ 128 | $('#input_url').val(""); 129 | } 130 | 131 | function right_url(URL){ 132 | send_request(URL); 133 | } 134 | 135 | 136 | function send_request(value){ 137 | var response; 138 | $.ajax({ 139 | type: "POST", 140 | url: "create", 141 | data: value, 142 | success: function(response){processing_ok(response);}, 143 | dataType: "text", 144 | statusCode: { 145 | 400: function() { 146 | processing_bad_request() 147 | }, 148 | 501: function() { 149 | processing_server_error() 150 | }, 151 | 423: function() { 152 | return 0 153 | } 154 | } 155 | }); 156 | } 157 | 158 | function processing_ok(response){ 159 | clear_msg_field(); 160 | clear_is_link(); 161 | show_is_link(response); 162 | var ll = $('#input_url').val(); 163 | clear_is_long_link(); 164 | show_is_long_link(ll); 165 | clear_input_url(); 166 | disabling(); 167 | } 168 | 169 | function processing_bad_request(){ 170 | wrong_url("Url is wrong! Check it :-)"); 171 | } 172 | function processing_server_error(){ 173 | wrong_url("Something goes wrong :-( Try later..."); 174 | } 175 | 176 | 177 | function cut_long_url(str, len){ 178 | return str; 179 | } 180 | 181 | function disabling(){ 182 | $('#input_url').attr('disabled','disabled'); 183 | $('#b1').attr('disabled','disabled'); 184 | $('#counter').text(time); 185 | timer_descr = setInterval(function() {check_int()}, 1000); 186 | return 0; 187 | } 188 | 189 | function check_int(){ 190 | time -= 1; 191 | if (time == 0){ 192 | $('#input_url').removeAttr('disabled'); 193 | $('#b1').removeAttr('disabled'); 194 | $('#counter').text(''); 195 | time = 3; 196 | clearInterval(timer_descr); 197 | $('#input_url').focus(); 198 | } else { 199 | $('#counter').text(time); 200 | } 201 | } -------------------------------------------------------------------------------- /html/not_found.html: -------------------------------------------------------------------------------- 1 |

Sorry, we don't have this page

2 | -------------------------------------------------------------------------------- /html/server_error.html: -------------------------------------------------------------------------------- 1 |

Sorry, something goes wrong

2 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2garryn/esli/6e48337b066bb230813317dcda613993c2929684/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | 2 | {deps_dir, ["deps"]}. 3 | 4 | 5 | {sub_dirs, ["rel", "apps/esli"]}. 6 | 7 | 8 | {erl_opts, [debug_info, warnings_as_errors]}. 9 | 10 | {deps, 11 | [ 12 | {'riakc', ".*", 13 | {git, "git://github.com/basho/riak-erlang-client.git", "master"}}, 14 | {'mochiweb', ".*", 15 | {git, "git://github.com/mochi/mochiweb.git", "master"}} 16 | ]}. -------------------------------------------------------------------------------- /rel/files/app.config: -------------------------------------------------------------------------------- 1 | [ 2 | {esli, [ 3 | %% Path to html docs 4 | {docroot, "var/htdocs"}, 5 | 6 | %% Mochiweb server config 7 | {web, [{ip, {0,0,0,0}},{port, 8081}]}, 8 | 9 | %% domain before short link id 10 | {domain, "http://localhost:8081"}, 11 | 12 | %% Place where id file (to back next link id) is saved 13 | 14 | %% Riak config 15 | {riak_host, '127.0.0.1'}, 16 | {riak_port, 8087}, 17 | 18 | %% Amount of riak client processes 19 | {worker_set, 10} 20 | 21 | ]}, 22 | %% SASL config 23 | {sasl, [ 24 | {sasl_error_logger, {file, "log/sasl-error.log"}}, 25 | {errlog_type, error}, 26 | {error_logger_mf_dir, "log/sasl"}, % Log directory 27 | {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size 28 | {error_logger_mf_maxfiles, 5} % 5 files max 29 | ]} 30 | ]. 31 | 32 | -------------------------------------------------------------------------------- /rel/files/erl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## This script replaces the default "erl" in erts-VSN/bin. This is necessary 4 | ## as escript depends on erl and in turn, erl depends on having access to a 5 | ## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect 6 | ## of running escript -- the embedded node bypasses erl and uses erlexec directly 7 | ## (as it should). 8 | ## 9 | ## Note that this script makes the assumption that there is a start_clean.boot 10 | ## file available in $ROOTDIR/release/VSN. 11 | 12 | # Determine the abspath of where this script is executing from. 13 | ERTS_BIN_DIR=$(cd ${0%/*} && pwd) 14 | 15 | # Now determine the root directory -- this script runs from erts-VSN/bin, 16 | # so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR 17 | # path. 18 | ROOTDIR=${ERTS_BIN_DIR%/*/*} 19 | 20 | # Parse out release and erts info 21 | START_ERL=`cat $ROOTDIR/releases/start_erl.data` 22 | ERTS_VSN=${START_ERL% *} 23 | APP_VSN=${START_ERL#* } 24 | 25 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 26 | EMU=beam 27 | PROGNAME=`echo $0 | sed 's/.*\\///'` 28 | CMD="$BINDIR/erlexec" 29 | export EMU 30 | export ROOTDIR 31 | export BINDIR 32 | export PROGNAME 33 | 34 | exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"} -------------------------------------------------------------------------------- /rel/files/esli: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) 4 | 5 | RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} 6 | RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc 7 | RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log 8 | PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ 9 | RUNNER_USER= 10 | 11 | # Make sure this script is running as the appropriate user 12 | if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then 13 | exec sudo -u $RUNNER_USER -i $0 $@ 14 | fi 15 | 16 | # Make sure CWD is set to runner base dir 17 | cd $RUNNER_BASE_DIR 18 | 19 | # Make sure log directory exists 20 | mkdir -p $RUNNER_LOG_DIR 21 | 22 | # Extract the target node name from node.args 23 | NAME_ARG=`grep -e '-[s]*name' $RUNNER_ETC_DIR/vm.args` 24 | if [ -z "$NAME_ARG" ]; then 25 | echo "vm.args needs to have either -name or -sname parameter." 26 | exit 1 27 | fi 28 | 29 | # Extract the target cookie 30 | COOKIE_ARG=`grep -e '-setcookie' $RUNNER_ETC_DIR/vm.args` 31 | if [ -z "$COOKIE_ARG" ]; then 32 | echo "vm.args needs to have a -setcookie parameter." 33 | exit 1 34 | fi 35 | 36 | # Identify the script name 37 | SCRIPT=`basename $0` 38 | 39 | # Parse out release and erts info 40 | START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` 41 | ERTS_VSN=${START_ERL% *} 42 | APP_VSN=${START_ERL#* } 43 | 44 | # Add ERTS bin dir to our path 45 | ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin 46 | 47 | # Setup command to control the node 48 | NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" 49 | 50 | # Check the first argument for instructions 51 | case "$1" in 52 | start) 53 | # Make sure there is not already a node running 54 | RES=`$NODETOOL ping` 55 | if [ "$RES" == "pong" ]; then 56 | echo "Node is already running!" 57 | exit 1 58 | fi 59 | export HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" 60 | mkdir -p $PIPE_DIR 61 | # Note the trailing slash on $PIPE_DIR/ 62 | $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1 63 | ;; 64 | 65 | stop) 66 | # Wait for the node to completely stop... 67 | PID=`ps -ef|grep "$RUNNER_BASE_DIR/.*/[b]eam.smp|awk '{print $2}'"` 68 | $NODETOOL stop 69 | while `kill -0 $PID 2>/dev/null`; 70 | do 71 | sleep 1 72 | done 73 | ;; 74 | 75 | restart) 76 | ## Restart the VM without exiting the process 77 | $NODETOOL restart 78 | ;; 79 | 80 | reboot) 81 | ## Restart the VM completely (uses heart to restart it) 82 | $NODETOOL reboot 83 | ;; 84 | 85 | ping) 86 | ## See if the VM is alive 87 | $NODETOOL ping 88 | ;; 89 | 90 | attach) 91 | # Make sure a node IS running 92 | RES=`$NODETOOL ping` 93 | if [ "$RES" != "pong" ]; then 94 | echo "Node is not running!" 95 | exit 1 96 | fi 97 | 98 | shift 99 | $ERTS_PATH/to_erl $PIPE_DIR 100 | ;; 101 | 102 | console) 103 | # Setup beam-required vars 104 | ROOTDIR=$RUNNER_BASE_DIR 105 | BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin 106 | EMU=beam 107 | PROGNAME=`echo $0 | sed 's/.*\\///'` 108 | CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" 109 | export EMU 110 | export ROOTDIR 111 | export BINDIR 112 | export PROGNAME 113 | 114 | # Dump environment info for logging purposes 115 | echo "Exec: $CMD" 116 | echo "Root: $ROOTDIR" 117 | 118 | # Log the startup 119 | logger -t "$SCRIPT[$$]" "Starting up" 120 | 121 | # Start the VM 122 | exec $CMD 123 | ;; 124 | 125 | *) 126 | echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach}" 127 | exit 1 128 | ;; 129 | esac 130 | 131 | exit 0 132 | -------------------------------------------------------------------------------- /rel/files/nodetool: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | %% ------------------------------------------------------------------- 3 | %% 4 | %% nodetool: Helper Script for interacting with live nodes 5 | %% 6 | %% ------------------------------------------------------------------- 7 | 8 | main(Args) -> 9 | %% Extract the args 10 | {RestArgs, TargetNode} = process_args(Args, [], undefined), 11 | 12 | %% See if the node is currently running -- if it's not, we'll bail 13 | case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of 14 | {true, pong} -> 15 | ok; 16 | {_, pang} -> 17 | io:format("Node ~p not responding to pings.\n", [TargetNode]), 18 | halt(1) 19 | end, 20 | 21 | case RestArgs of 22 | ["ping"] -> 23 | %% If we got this far, the node already responsed to a ping, so just dump 24 | %% a "pong" 25 | io:format("pong\n"); 26 | ["stop"] -> 27 | io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); 28 | ["restart"] -> 29 | io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); 30 | ["reboot"] -> 31 | io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); 32 | ["rpc", Module, Function | RpcArgs] -> 33 | case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], 60000) of 34 | ok -> 35 | ok; 36 | {badrpc, Reason} -> 37 | io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), 38 | halt(1); 39 | _ -> 40 | halt(1) 41 | end; 42 | Other -> 43 | io:format("Other: ~p\n", [Other]), 44 | io:format("Usage: nodetool {ping|stop|restart|reboot}\n") 45 | end, 46 | net_kernel:stop(). 47 | 48 | process_args([], Acc, TargetNode) -> 49 | {lists:reverse(Acc), TargetNode}; 50 | process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> 51 | erlang:set_cookie(node(), list_to_atom(Cookie)), 52 | process_args(Rest, Acc, TargetNode); 53 | process_args(["-name", TargetName | Rest], Acc, _) -> 54 | ThisNode = append_node_suffix(TargetName, "_maint_"), 55 | {ok, _} = net_kernel:start([ThisNode, longnames]), 56 | process_args(Rest, Acc, nodename(TargetName)); 57 | process_args(["-sname", TargetName | Rest], Acc, _) -> 58 | ThisNode = append_node_suffix(TargetName, "_maint_"), 59 | {ok, _} = net_kernel:start([ThisNode, shortnames]), 60 | process_args(Rest, Acc, nodename(TargetName)); 61 | process_args([Arg | Rest], Acc, Opts) -> 62 | process_args(Rest, [Arg | Acc], Opts). 63 | 64 | 65 | nodename(Name) -> 66 | case string:tokens(Name, "@") of 67 | [_Node, _Host] -> 68 | list_to_atom(Name); 69 | [Node] -> 70 | [_, Host] = string:tokens(atom_to_list(node()), "@"), 71 | list_to_atom(lists:concat([Node, "@", Host])) 72 | end. 73 | 74 | append_node_suffix(Name, Suffix) -> 75 | case string:tokens(Name, "@") of 76 | [Node, Host] -> 77 | list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); 78 | [Node] -> 79 | list_to_atom(lists:concat([Node, Suffix, os:getpid()])) 80 | end. 81 | -------------------------------------------------------------------------------- /rel/files/vm.args: -------------------------------------------------------------------------------- 1 | 2 | ## Name of the node 3 | -name esli@127.0.0.1 4 | 5 | ## Cookie for distributed erlang 6 | -setcookie esli 7 | 8 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 9 | ## (Disabled by default..use with caution!) 10 | ##-heart 11 | 12 | ## Enable kernel poll and a few async threads 13 | +K true 14 | +A 5 15 | 16 | ## Increase number of concurrent ports/sockets 17 | -env ERL_MAX_PORTS 4096 18 | 19 | ## Tweak GC to run more often 20 | -env ERL_FULLSWEEP_AFTER 10 21 | 22 | -------------------------------------------------------------------------------- /rel/reltool.config: -------------------------------------------------------------------------------- 1 | {sys, [ 2 | {lib_dirs, ["../deps", "../apps"]}, 3 | {rel, "esli", "1", 4 | [ 5 | kernel, 6 | stdlib, 7 | sasl, 8 | riakc, 9 | mochiweb, 10 | esli 11 | ]}, 12 | {rel, "start_clean", "", 13 | [ 14 | kernel, 15 | stdlib 16 | ]}, 17 | {boot_rel, "esli"}, 18 | {profile, embedded}, 19 | {excl_sys_filters, ["^bin/.*", 20 | "^erts.*/bin/(dialyzer|typer)"]}, 21 | {app, sasl, [{incl_cond, include}]} 22 | ]}. 23 | 24 | {target_dir, "esli"}. 25 | 26 | {overlay, [ 27 | {mkdir, "log/sasl"}, 28 | {copy, "files/erl", "{{erts_vsn}}/bin/erl"}, 29 | {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"}, 30 | {copy, "files/esli", "bin/esli"}, 31 | {copy, "files/app.config", "etc/app.config"}, 32 | {copy, "files/vm.args", "etc/vm.args"}, 33 | {mkdir, "var"}, 34 | {mkdir, "var/htdocs"} 35 | ]}. 36 | -------------------------------------------------------------------------------- /riak_search_schema.txt: -------------------------------------------------------------------------------- 1 | { 2 | schema, 3 | [ 4 | {version, "1.1"}, 5 | {default_field, "got"}, 6 | {default_op, "and"}, 7 | {n_val, 3} 8 | ], 9 | [ 10 | {field, [ 11 | {name, "id"}, 12 | {analyzer_factory, {erlang, text_analyzers, noop_analyzer_factory}} 13 | ]}, 14 | {field, [ 15 | {name, "short"}, 16 | {analyzer_factory, {erlang, text_analyzers, noop_analyzer_factory}} 17 | ]}, 18 | {field, [ 19 | {name, "link"}, 20 | {required, true}, 21 | {analyzer_factory, {erlang, text_analyzers, noop_analyzer_factory}} 22 | ]}, 23 | {field, [ 24 | {name, "created"}, 25 | {type, integer}, 26 | {padding_size, 15}, 27 | {analyzer_factory, {erlang, text_analyzers, integer_analyzer_factory}} 28 | ]}, 29 | {field, [ 30 | {name, "got"}, 31 | {type, integer}, 32 | {padding_size, 15}, 33 | {analyzer_factory, {erlang, text_analyzers, integer_analyzer_factory}} 34 | ]}, 35 | {dynamic_field, [ 36 | {name, "*"} 37 | ]} 38 | ] 39 | }. 40 | --------------------------------------------------------------------------------