diff --git a/web/api/flukso/Makefile b/web/api/flukso/Makefile index 361dbd2..3e6720e 100644 --- a/web/api/flukso/Makefile +++ b/web/api/flukso/Makefile @@ -2,7 +2,7 @@ ERL ?= erl EBIN_DIRS := $(wildcard deps/*/ebin) APP := flukso -all: erl ebin/$(APP).app erlrrd +all: erl ebin/$(APP).app erlrrd mysql erl: @$(ERL) -pa $(EBIN_DIRS) -noinput +B \ @@ -21,3 +21,5 @@ ebin/$(APP).app: src/$(APP).app erlrrd: @(cd deps/erlrrd;$(MAKE)) +mysql: + @(cd deps/mysql;$(MAKE)) diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd.app b/web/api/flukso/deps/erlrrd/ebin/erlrrd.app deleted file mode 100644 index af306e2..0000000 --- a/web/api/flukso/deps/erlrrd/ebin/erlrrd.app +++ /dev/null @@ -1,12 +0,0 @@ -{application, erlrrd, - [{description, "erlang rrdtool port"}, - {vsn, "1.0"}, - {modules, [ - erlrrd, - erlrrd_app, - erlrrd_sup - ]}, - {registered, [erlrrd, erlrrd_sup]}, - {mod, {erlrrd_app, []}}, - {env, []}, - {applications, [kernel, stdlib]}]}. diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd.beam b/web/api/flukso/deps/erlrrd/ebin/erlrrd.beam deleted file mode 100644 index f0d148d..0000000 Binary files a/web/api/flukso/deps/erlrrd/ebin/erlrrd.beam and /dev/null differ diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd_app.beam b/web/api/flukso/deps/erlrrd/ebin/erlrrd_app.beam deleted file mode 100644 index bb2207a..0000000 Binary files a/web/api/flukso/deps/erlrrd/ebin/erlrrd_app.beam and /dev/null differ diff --git a/web/api/flukso/deps/erlrrd/ebin/erlrrd_sup.beam b/web/api/flukso/deps/erlrrd/ebin/erlrrd_sup.beam deleted file mode 100644 index 4cb24f6..0000000 Binary files a/web/api/flukso/deps/erlrrd/ebin/erlrrd_sup.beam and /dev/null differ diff --git a/web/api/flukso/deps/mysql/Makefile b/web/api/flukso/deps/mysql/Makefile new file mode 100644 index 0000000..2f6cace --- /dev/null +++ b/web/api/flukso/deps/mysql/Makefile @@ -0,0 +1,11 @@ +all: + (cd src;$(MAKE) all) + +edoc: + (cd src;$(MAKE) edoc) + +test: + (cd src;$(MAKE) test) + +clean: + (cd src;$(MAKE) clean) diff --git a/web/api/flukso/deps/mysql/doc/edoc-info b/web/api/flukso/deps/mysql/doc/edoc-info new file mode 100644 index 0000000..db35f7c --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/edoc-info @@ -0,0 +1,3 @@ +{application,mysql}. +{packages,[]}. +{modules,[mysql,mysql_auth,mysql_conn,mysql_recv]}. diff --git a/web/api/flukso/deps/mysql/doc/erlang.png b/web/api/flukso/deps/mysql/doc/erlang.png new file mode 100644 index 0000000..987a618 Binary files /dev/null and b/web/api/flukso/deps/mysql/doc/erlang.png differ diff --git a/web/api/flukso/deps/mysql/doc/index.html b/web/api/flukso/deps/mysql/doc/index.html new file mode 100644 index 0000000..0626291 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/index.html @@ -0,0 +1,17 @@ + + + +The mysql application + + + + + + +<h2>This page uses frames</h2> +<p>Your browser does not accept frames. +<br>You should go to the <a href="overview-summary.html">non-frame version</a> instead. +</p> + + + \ No newline at end of file diff --git a/web/api/flukso/deps/mysql/doc/modules-frame.html b/web/api/flukso/deps/mysql/doc/modules-frame.html new file mode 100644 index 0000000..9737a4f --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/modules-frame.html @@ -0,0 +1,15 @@ + + + +The mysql application + + + +

Modules

+ + + + +
mysql
mysql_auth
mysql_conn
mysql_recv
+ + \ No newline at end of file diff --git a/web/api/flukso/deps/mysql/doc/mysql.html b/web/api/flukso/deps/mysql/doc/mysql.html new file mode 100644 index 0000000..5c4e284 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql.html @@ -0,0 +1,145 @@ + + + +Module mysql + + + + +
+ +

Module mysql

+ + +

Behaviours: gen_server.

+ +

Function Index

+ + + + + + + + + + + + + + + + + + + + +
asciz_binary/2
code_change/3
connect/7
fetch/2
fetch/3
get_result_affected_rows/1
get_result_field_info/1
get_result_reason/1
get_result_rows/1
handle_call/3
handle_cast/2
handle_info/2
init/1
log/3
log/4
quote/1
start_link/5
start_link/6
start_link/7
terminate/2
+ +

Function Details

+ +

asciz_binary/2

+
+

asciz_binary() -> term()

+
+ +

code_change/3

+
+

code_change() -> term()

+
+ +

connect/7

+
+

connect() -> term()

+
+ +

fetch/2

+
+

fetch() -> term()

+
+ +

fetch/3

+
+

fetch() -> term()

+
+ +

get_result_affected_rows/1

+
+

get_result_affected_rows() -> term()

+
+ +

get_result_field_info/1

+
+

get_result_field_info() -> term()

+
+ +

get_result_reason/1

+
+

get_result_reason() -> term()

+
+ +

get_result_rows/1

+
+

get_result_rows() -> term()

+
+ +

handle_call/3

+
+

handle_call() -> term()

+
+ +

handle_cast/2

+
+

handle_cast() -> term()

+
+ +

handle_info/2

+
+

handle_info() -> term()

+
+ +

init/1

+
+

init() -> term()

+
+ +

log/3

+
+

log() -> term()

+
+ +

log/4

+
+

log() -> term()

+
+ +

quote/1

+
+

quote() -> term()

+
+ +

start_link/5

+
+

start_link() -> term()

+
+ +

start_link/6

+
+

start_link() -> term()

+
+ +

start_link/7

+
+

start_link() -> term()

+
+ +

terminate/2

+
+

terminate() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/mysql_auth.html b/web/api/flukso/deps/mysql/doc/mysql_auth.html new file mode 100644 index 0000000..403930d --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql_auth.html @@ -0,0 +1,36 @@ + + + +Module mysql_auth + + + + +
+ +

Module mysql_auth

+ + + +

Function Index

+ + +
do_new_auth/8
do_old_auth/7
+ +

Function Details

+ +

do_new_auth/8

+
+

do_new_auth() -> term()

+
+ +

do_old_auth/7

+
+

do_old_auth() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/mysql_conn.html b/web/api/flukso/deps/mysql/doc/mysql_conn.html new file mode 100644 index 0000000..353089e --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql_conn.html @@ -0,0 +1,60 @@ + + + +Module mysql_conn + + + + +
+ +

Module mysql_conn

+ + + +

Function Index

+ + + + + + +
do_recv/3
fetch/3
fetch/4
squery/4
start/6
start_link/6
+ +

Function Details

+ +

do_recv/3

+
+

do_recv() -> term()

+
+ +

fetch/3

+
+

fetch() -> term()

+
+ +

fetch/4

+
+

fetch() -> term()

+
+ +

squery/4

+
+

squery() -> term()

+
+ +

start/6

+
+

start() -> term()

+
+ +

start_link/6

+
+

start_link() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/mysql_recv.html b/web/api/flukso/deps/mysql/doc/mysql_recv.html new file mode 100644 index 0000000..e0d7943 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/mysql_recv.html @@ -0,0 +1,30 @@ + + + +Module mysql_recv + + + + +
+ +

Module mysql_recv

+ + + +

Function Index

+ +
start_link/4
+ +

Function Details

+ +

start_link/4

+
+

start_link() -> term()

+
+
+ + +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/overview-summary.html b/web/api/flukso/deps/mysql/doc/overview-summary.html new file mode 100644 index 0000000..b2a1853 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/overview-summary.html @@ -0,0 +1,15 @@ + + + +The mysql application + + + + +

The mysql application

+ +
+ +

Generated by EDoc, Nov 12 2009, 13:11:27.

+ + diff --git a/web/api/flukso/deps/mysql/doc/packages-frame.html b/web/api/flukso/deps/mysql/doc/packages-frame.html new file mode 100644 index 0000000..3c63b4a --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/packages-frame.html @@ -0,0 +1,11 @@ + + + +The mysql application + + + +

Packages

+
+ + \ No newline at end of file diff --git a/web/api/flukso/deps/mysql/doc/stylesheet.css b/web/api/flukso/deps/mysql/doc/stylesheet.css new file mode 100644 index 0000000..e426a90 --- /dev/null +++ b/web/api/flukso/deps/mysql/doc/stylesheet.css @@ -0,0 +1,55 @@ +/* standard EDoc style sheet */ +body { + font-family: Verdana, Arial, Helvetica, sans-serif; + margin-left: .25in; + margin-right: .2in; + margin-top: 0.2in; + margin-bottom: 0.2in; + color: #000000; + background-color: #ffffff; +} +h1,h2 { + margin-left: -0.2in; +} +div.navbar { + background-color: #add8e6; + padding: 0.2em; +} +h2.indextitle { + padding: 0.4em; + background-color: #add8e6; +} +h3.function,h3.typedecl { + background-color: #add8e6; + padding-left: 1em; +} +div.spec { + margin-left: 2em; + background-color: #eeeeee; +} +a.module,a.package { + text-decoration:none +} +a.module:hover,a.package:hover { + background-color: #eeeeee; +} +ul.definitions { + list-style-type: none; +} +ul.index { + list-style-type: none; + background-color: #eeeeee; +} + +/* + * Minor style tweaks + */ +ul { + list-style-type: square; +} +table { + border-collapse: collapse; +} +td { + padding: 3 +} diff --git a/web/api/flukso/deps/mysql/src/Makefile b/web/api/flukso/deps/mysql/src/Makefile new file mode 100644 index 0000000..af7b8d9 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/Makefile @@ -0,0 +1,20 @@ +include ../support/include.mk + +APPLICATION=mysql +DOC_OPTS={dir,\"../doc\"} + +all: $(EBIN_FILES) + +debug: + $(MAKE) DEBUG=-DDEBUG + +clean: + rm -rf $(EBIN_FILES) + +edoc: + $(ERL) -noshell -pa ../ebin \ + -eval "edoc:application($(APPLICATION), \".\", [$(DOC_OPTS)])" \ + -s init stop + +test: all + $(ERL) -noshell -pa ../ebin -s $(APPLICATION) test -s init stop diff --git a/web/api/flukso/deps/mysql/src/mysql.app b/web/api/flukso/deps/mysql/src/mysql.app new file mode 100644 index 0000000..60c65d3 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql.app @@ -0,0 +1,12 @@ +{application, mysql, + [{description, "erlang mysql driver"}, + {vsn, "1.0"}, + {modules, [ + mysql, + mysql_app, + mysql_sup + ]}, + {registered, [mysql, mysql_sup]}, + {mod, {mysql_app, []}}, + {env, []}, + {applications, [kernel, stdlib]}]}. diff --git a/web/api/flukso/deps/mysql/src/mysql.erl b/web/api/flukso/deps/mysql/src/mysql.erl new file mode 100644 index 0000000..7dde778 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql.erl @@ -0,0 +1,656 @@ +%%%------------------------------------------------------------------- +%%% File : mysql.erl +%%% Author : Magnus Ahltorp +%%% Descrip.: MySQL client. +%%% +%%% Created : 4 Aug 2005 by Magnus Ahltorp +%%% +%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan +%%% See the file COPYING +%%% +%%% Usage: +%%% +%%% +%%% Call one of the start-functions before any call to fetch/2 +%%% +%%% start_link(Id, Host, User, Password, Database) +%%% start_link(Id, Host, Port, User, Password, Database) +%%% start_link(Id, Host, User, Password, Database, LogFun) +%%% start_link(Id, Host, Port, User, Password, Database, LogFun) +%%% +%%% Id is a connection group identifier. If you want to have more +%%% than one connection to a server (or a set of MySQL replicas), +%%% add more with +%%% +%%% connect(Id, Host, Port, User, Password, Database, Reconnect) +%%% +%%% use 'undefined' as Port to get default MySQL port number (3306). +%%% MySQL querys will be sent in a per-Id round-robin fashion. +%%% Set Reconnect to 'true' if you want the dispatcher to try and +%%% open a new connection, should this one die. +%%% +%%% When you have a mysql_dispatcher running, this is how you make a +%%% query : +%%% +%%% fetch(Id, "select * from hello") -> Result +%%% Result = {data, MySQLRes} | {updated, MySQLRes} | +%%% {error, MySQLRes} +%%% +%%% Actual data can be extracted from MySQLRes by calling the following API +%%% functions: +%%% - on data received: +%%% FieldInfo = mysql:get_result_field_info(MysqlRes) +%%% AllRows = mysql:get_result_rows(MysqlRes) +%%% with FieldInfo = list() of {Table, Field, Length, Name} +%%% and AllRows = list() of list() representing records +%%% - on update: +%%% Affected = mysql:get_result_affected_rows(MysqlRes) +%%% with Affected = integer() +%%% - on error: +%%% Reason = mysql:get_result_reason(MysqlRes) +%%% with Reason = string() +%%% +%%% If you just want a single MySQL connection, or want to manage your +%%% connections yourself, you can use the mysql_conn module as a +%%% stand-alone single MySQL connection. See the comment at the top of +%%% mysql_conn.erl. +%%% +%%%------------------------------------------------------------------- +-module(mysql). + +-behaviour(gen_server). + +%%-------------------------------------------------------------------- +%% External exports +%%-------------------------------------------------------------------- +-export([start_link/5, + start_link/6, + start_link/7, + + fetch/2, + fetch/3, + + get_result_field_info/1, + get_result_rows/1, + get_result_affected_rows/1, + get_result_reason/1, + + quote/1, + asciz_binary/2, + + connect/7 + ]). + +%%-------------------------------------------------------------------- +%% Internal exports - just for mysql_* modules +%%-------------------------------------------------------------------- +-export([log/3, + log/4 + ]). + +%%-------------------------------------------------------------------- +%% Internal exports - gen_server callbacks +%%-------------------------------------------------------------------- +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + +%%-------------------------------------------------------------------- +%% Records +%%-------------------------------------------------------------------- +-include("mysql.hrl"). +-record(state, { + conn_list, %% list() of mysql_connection record() + log_fun %% undefined | function for logging, + }). + +-record(mysql_connection, { + id, %% term(), user of 'mysql' modules id of this socket group + conn_pid, %% pid(), mysql_conn process + reconnect, %% true | false, should mysql_dispatcher try to reconnect if this connection dies? + host, %% undefined | string() + port, %% undefined | integer() + user, %% undefined | string() + password, %% undefined | string() + database %% undefined | string() + }). + +%%-------------------------------------------------------------------- +%% Macros +%%-------------------------------------------------------------------- +-define(SERVER, mysql_dispatcher). +-define(CONNECT_TIMEOUT, 5000). +-define(LOCAL_FILES, 128). + +-define(PORT, 3306). + + +%%==================================================================== +%% External functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: start_link(Id, Host, User, Password, Database) +%% start_link(Id, Host, Port, User, Password, Database) +%% start_link(Id, Host, User, Password, Database, LogFun) +%% start_link(Id, Host, Port, User, Password, Database, +%% LogFun) +%% Id = term(), first connection-group Id +%% Host = string() +%% Port = integer() +%% User = string() +%% Password = string() +%% Database = string() +%% LogFun = undefined | function() of arity 3 +%% Descrip.: Starts the MySQL client gen_server process. +%% Returns : {ok, Pid} | ignore | {error, Error} +%%-------------------------------------------------------------------- +start_link(Id, Host, User, Password, Database) when is_list(Host), is_list(User), is_list(Password), + is_list(Database) -> + start_link(Id, Host, ?PORT, User, Password, Database, undefined). + +start_link(Id, Host, Port, User, Password, Database) when is_list(Host), is_integer(Port), is_list(User), + is_list(Password), is_list(Database) -> + start_link(Id, Host, Port, User, Password, Database, undefined); + +start_link(Id, Host, User, Password, Database, LogFun) when is_list(Host), is_list(User), is_list(Password), + is_list(Database) -> + start_link(Id, Host, ?PORT, User, Password, Database, LogFun). + +start_link(Id, Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User), + is_list(Password), is_list(Database) -> + crypto:start(), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Id, Host, Port, User, Password, Database, LogFun], []). + +%%-------------------------------------------------------------------- +%% Function: fetch(Id, Query) +%% fetch(Id, Query, Timeout) +%% Id = term(), connection-group Id +%% Query = string(), MySQL query in verbatim +%% Timeout = integer() | infinity, gen_server timeout value +%% Descrip.: Send a query and wait for the result. +%% Returns : {data, MySQLRes} | +%% {updated, MySQLRes} | +%% {error, MySQLRes} +%% MySQLRes = term() +%%-------------------------------------------------------------------- +fetch(Id, Query) when is_list(Query) -> + gen_server:call(?SERVER, {fetch, Id, Query}). +fetch(Id, Query, Timeout) when is_list(Query) -> + gen_server:call(?SERVER, {fetch, Id, Query}, Timeout). + +%%-------------------------------------------------------------------- +%% Function: get_result_field_info(MySQLRes) +%% MySQLRes = term(), result of fetch function on "data" +%% Descrip.: Extract the FieldInfo from MySQL Result on data received +%% Returns : FieldInfo +%% FieldInfo = list() of {Table, Field, Length, Name} +%%-------------------------------------------------------------------- +get_result_field_info(#mysql_result{fieldinfo = FieldInfo}) -> + FieldInfo. + +%%-------------------------------------------------------------------- +%% Function: get_result_rows(MySQLRes) +%% MySQLRes = term(), result of fetch function on "data" +%% Descrip.: Extract the Rows from MySQL Result on data received +%% Returns : Rows +%% Rows = list() of list() representing records +%%-------------------------------------------------------------------- +get_result_rows(#mysql_result{rows=AllRows}) -> + AllRows. + +%%-------------------------------------------------------------------- +%% Function: get_result_affected_rows(MySQLRes) +%% MySQLRes = term(), result of fetch function on "updated" +%% Descrip.: Extract the Rows from MySQL Result on update +%% Returns : AffectedRows +%% AffectedRows = integer() +%%-------------------------------------------------------------------- +get_result_affected_rows(#mysql_result{affectedrows=AffectedRows}) -> + AffectedRows. + +%%-------------------------------------------------------------------- +%% Function: get_result_reason(MySQLRes) +%% MySQLRes = term(), result of fetch function on "error" +%% Descrip.: Extract the error Reason from MySQL Result on error +%% Returns : Reason +%% Reason = string() +%%-------------------------------------------------------------------- +get_result_reason(#mysql_result{error=Reason}) -> + Reason. + +%%-------------------------------------------------------------------- +%% Function: quote(String) +%% String = string() +%% Descrip.: Quote a string so that it can be included safely in a +%% MySQL query. +%% Returns : Quoted = string() +%%-------------------------------------------------------------------- +quote(String) when is_list(String) -> + [34 | lists:reverse([34 | quote(String, [])])]. %% 34 is $" + +quote([], Acc) -> + Acc; +quote([0 | Rest], Acc) -> + quote(Rest, [$0, $\\ | Acc]); +quote([10 | Rest], Acc) -> + quote(Rest, [$n, $\\ | Acc]); +quote([13 | Rest], Acc) -> + quote(Rest, [$r, $\\ | Acc]); +quote([$\\ | Rest], Acc) -> + quote(Rest, [$\\ , $\\ | Acc]); +quote([39 | Rest], Acc) -> %% 39 is $' + quote(Rest, [39, $\\ | Acc]); %% 39 is $' +quote([34 | Rest], Acc) -> %% 34 is $" + quote(Rest, [34, $\\ | Acc]); %% 34 is $" +quote([26 | Rest], Acc) -> + quote(Rest, [$Z, $\\ | Acc]); +quote([C | Rest], Acc) -> + quote(Rest, [C | Acc]). + +%%-------------------------------------------------------------------- +%% Function: asciz_binary(Data, Acc) +%% Data = binary() +%% Acc = list(), input accumulator +%% Descrip.: Find the first zero-byte in Data and add everything +%% before it to Acc, as a string. +%% Returns : {NewList, Rest} +%% NewList = list(), Acc plus what we extracted from Data +%% Rest = binary(), whatever was left of Data, not +%% including the zero-byte +%%-------------------------------------------------------------------- +asciz_binary(<<>>, Acc) -> + {lists:reverse(Acc), <<>>}; +asciz_binary(<<0:8, Rest/binary>>, Acc) -> + {lists:reverse(Acc), Rest}; +asciz_binary(<>, Acc) -> + asciz_binary(Rest, [C | Acc]). + +%%-------------------------------------------------------------------- +%% Function: connect(Id, Host, Port, User, Password, Database, +%% Reconnect) +%% Id = term(), connection-group Id +%% Host = string() +%% Port = undefined | integer() +%% User = string() +%% Password = string() +%% Database = string() +%% Reconnect = true | false +%% Descrip.: Starts a MySQL connection and, if successfull, registers +%% it with the mysql_dispatcher. +%% Returns : {ok, ConnPid} | {error, Reason} +%%-------------------------------------------------------------------- +connect(Id, Host, undefined, User, Password, Database, Reconnect) -> + connect(Id, Host, ?PORT, User, Password, Database, Reconnect); +connect(Id, Host, Port, User, Password, Database, Reconnect) -> + {ok, LogFun} = gen_server:call(?SERVER, get_logfun), + case mysql_conn:start(Host, Port, User, Password, Database, LogFun) of + {ok, ConnPid} -> + MysqlConn = + case Reconnect of + true -> + #mysql_connection{id = Id, + conn_pid = ConnPid, + reconnect = true, + host = Host, + port = Port, + user = User, + password = Password, + database = Database + }; + false -> + #mysql_connection{id = Id, + conn_pid = ConnPid, + reconnect = false + } + end, + case gen_server:call(?SERVER, {add_mysql_connection, MysqlConn}) of + ok -> + {ok, ConnPid}; + Res -> + Res + end; + {error, Reason} -> + {error, Reason} + end. + +%%-------------------------------------------------------------------- +%% Function: log(LogFun, Level, Format) +%% log(LogFun, Level, Format, Arguments) +%% LogFun = undefined | function() with arity 3 +%% Level = debug | normal | error +%% Format = string() +%% Arguments = list() of term() +%% Descrip.: Either call the function LogFun with the Level, Format +%% and Arguments as parameters or log it to the console if +%% LogFun is undefined. +%% Returns : void() +%% +%% Note : Exported only for use by the mysql_* modules. +%% +%%-------------------------------------------------------------------- +log(LogFun, Level, Format) -> + log(LogFun, Level, Format, []). + +log(LogFun, Level, Format, Arguments) when is_function(LogFun) -> + LogFun(Level, Format, Arguments); +log(undefined, _Level, Format, Arguments) -> + %% default is to log to console + io:format(Format, Arguments), + io:format("~n", []). + + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Args = [Id, Host, Port, User, Password, Database, LogFun] +%% Id = term(), connection-group Id +%% Host = string() +%% Port = integer() +%% User = string() +%% Password = string() +%% Database = string() +%% LogFun = undefined | function() with arity 3 +%% Descrip.: Initiates the gen_server (MySQL dispatcher). +%%-------------------------------------------------------------------- +init([Id, Host, Port, User, Password, Database, LogFun]) -> + case mysql_conn:start(Host, Port, User, Password, Database, LogFun) of + {ok, ConnPid} -> + MysqlConn = #mysql_connection{id = Id, + conn_pid = ConnPid, + reconnect = true, + host = Host, + port = Port, + user = User, + password = Password, + database = Database + }, + case add_mysql_conn(MysqlConn, []) of + {ok, ConnList} -> + {ok, #state{log_fun = LogFun, + conn_list = ConnList + }}; + error -> + Msg = "mysql: Failed adding first MySQL connection handler to my list, exiting", + log(LogFun, error, Msg), + {error, Msg} + end; + {error, Reason} -> + log(LogFun, error, "mysql: Failed starting first MySQL connection handler, exiting"), + {stop, {error, Reason}} + end. + +%%-------------------------------------------------------------------- +%% Function: handle_call(Msg, From, State) +%% Descrip.: Handling call messages. +%% Returns : {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- + + +%%-------------------------------------------------------------------- +%% Function: handle_call({fetch, Id, Query}, From, State) +%% Id = term(), connection-group id +%% Query = string(), MySQL query +%% Descrip.: Make a MySQL query. Use the first connection matching Id +%% in our connection-list. Don't block the mysql_dispatcher +%% by returning {noreply, ...} here and let the mysql_conn +%% do gen_server:reply(...) when it has an answer. +%% Returns : {noreply, NewState} | +%% {reply, {error, Reason}, State} +%% NewState = state record() +%% Reason = atom() | string() +%%-------------------------------------------------------------------- +handle_call({fetch, Id, Query}, From, State) -> + log(State#state.log_fun, debug, "mysql: fetch ~p (id ~p)", [Query, Id]), + case get_next_mysql_connection_for_id(Id, State#state.conn_list) of + {ok, MysqlConn, RestOfConnList} when is_record(MysqlConn, mysql_connection) -> + mysql_conn:fetch(MysqlConn#mysql_connection.conn_pid, Query, From), + %% move this mysql socket to the back of the list + NewConnList = RestOfConnList ++ [MysqlConn], + %% The ConnPid process does a gen_server:reply() when it has an answer + {noreply, State#state{conn_list = NewConnList}}; + nomatch -> + %% we have no active connection matching Id + {reply, {error, no_connection}, State} + end; + +%%-------------------------------------------------------------------- +%% Function: handle_call({add_mysql_connection, Conn}, From, State) +%% Conn = mysql_connection record() +%% Descrip.: Add Conn to our list of connections. +%% Returns : {reply, Reply, NewState} +%% Reply = ok | {error, Reason} +%% NewState = state record() +%% Reason = string() +%%-------------------------------------------------------------------- +handle_call({add_mysql_connection, Conn}, _From, State) when is_record(Conn, mysql_connection) -> + case add_mysql_conn(Conn, State#state.conn_list) of + {ok, NewConnList} -> + {Id, ConnPid} = {Conn#mysql_connection.id, Conn#mysql_connection.conn_pid}, + log(State#state.log_fun, normal, "mysql: Added connection with id '~p' (pid ~p) to my list", + [Id, ConnPid]), + {reply, ok, State#state{conn_list = NewConnList}}; + error -> + {reply, {error, "failed adding MySQL connection to my list"}, State} + end; + +%%-------------------------------------------------------------------- +%% Function: handle_call(get_logfun, From, State) +%% Descrip.: Fetch our logfun. +%% Returns : {reply, {ok, LogFun}, State} +%% LogFun = undefined | function() with arity 3 +%%-------------------------------------------------------------------- +handle_call(get_logfun, _From, State) -> + {reply, {ok, State#state.log_fun}, State}; + +handle_call(Unknown, _From, State) -> + log(State#state.log_fun, error, "mysql: Received unknown gen_server call : ~p", [Unknown]), + {reply, {error, "unknown gen_server call in mysql client"}, State}. + + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) +%% Descrip.: Handling cast messages +%% Returns : {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_cast(Unknown, State) -> + log(State#state.log_fun, error, "mysql: Received unknown gen_server cast : ~p", [Unknown]), + {noreply, State}. + + +%%-------------------------------------------------------------------- +%% Function: handle_info(Msg, State) +%% Descrip.: Handling all non call/cast messages +%% Returns : {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Function: handle_info({'DOWN', ...}, State) +%% Descrip.: Handle a message that one of our monitored processes +%% (mysql_conn processes in our connection list) has exited. +%% Remove the entry from our list. +%% Returns : {noreply, NewState} | +%% {stop, normal, State} +%% NewState = state record() +%% +%% Note : For now, we stop if our connection list becomes empty. +%% We should try to reconnect for a while first, to not +%% eventually stop the whole OTP application if the MySQL- +%% server is shut down and the mysql_dispatcher was super- +%% vised by an OTP supervisor. +%%-------------------------------------------------------------------- +handle_info({'DOWN', _MonitorRef, process, Pid, Info}, State) -> + LogFun = State#state.log_fun, + case remove_mysql_connection_using_pid(Pid, State#state.conn_list, []) of + {ok, Conn, NewConnList} -> + LogLevel = case Info of + normal -> normal; + _ -> error + end, + log(LogFun, LogLevel, "mysql: MySQL connection pid ~p exited : ~p", [Pid, Info]), + log(LogFun, normal, "mysql: Removed MySQL connection with pid ~p from list", + [Pid]), + case Conn#mysql_connection.reconnect of + true -> + start_reconnect(Conn, LogFun); + false -> + ok + end, + {noreply, State#state{conn_list = NewConnList}}; + nomatch -> + log(LogFun, error, "mysql: Received 'DOWN' signal from pid ~p not in my list", [Pid]), + {noreply, State} + end; + +handle_info(Info, State) -> + log(State#state.log_fun, error, "mysql: Received unknown signal : ~p", [Info]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) +%% Descrip.: Shutdown the server +%% Returns : Reason +%%-------------------------------------------------------------------- +terminate(Reason, State) -> + LogFun = State#state.log_fun, + LogLevel = case Reason of + normal -> debug; + _ -> error + end, + log(LogFun, LogLevel, "mysql: Terminating with reason : ~p", [Reason]), + Reason. + +%%-------------------------------------------------------------------- +%% Function: code_change(_OldVsn, State, _Extra) +%% Descrip.: Convert process state when code is changed +%% Returns : {ok, State} +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: add_mysql_conn(Conn, ConnList) +%% Conn = mysql_connection record() +%% ConnList = list() of mysql_connection record() +%% Descrip.: Set up process monitoring of the mysql_conn process and +%% then add it (first) to ConnList. +%% Returns : NewConnList = list() of mysql_connection record() +%%-------------------------------------------------------------------- +add_mysql_conn(Conn, ConnList) when is_record(Conn, mysql_connection), is_list(ConnList) -> + erlang:monitor(process, Conn#mysql_connection.conn_pid), + {ok, [Conn | ConnList]}. + +%%-------------------------------------------------------------------- +%% Function: remove_mysql_connection_using_pid(Pid, ConnList) +%% Pid = pid() +%% ConnList = list() of mysql_connection record() +%% Descrip.: Removes the first mysql_connection in ConnList that has +%% a pid matching Pid. +%% Returns : {ok, Conn, NewConnList} | nomatch +%% Conn = mysql_connection record() +%% NewConnList = list() of mysql_connection record() +%%-------------------------------------------------------------------- +remove_mysql_connection_using_pid(Pid, [#mysql_connection{conn_pid = Pid} = H | T], Res) -> + {ok, H, lists:reverse(Res) ++ T}; +remove_mysql_connection_using_pid(Pid, [H | T], Res) when is_record(H, mysql_connection) -> + remove_mysql_connection_using_pid(Pid, T, [H | Res]); +remove_mysql_connection_using_pid(_Pid, [], _Res) -> + nomatch. + +%%-------------------------------------------------------------------- +%% Function: get_next_mysql_connection_for_id(Id, ConnList) +%% Id = term(), connection-group id +%% ConnList = list() of mysql_connection record() +%% Descrip.: Find the first mysql_connection in ConnList that has an +%% id matching Id. +%% Returns : {ok, Conn, NewConnList} | nomatch +%% Conn = mysql_connection record() +%% NewConnList = list() of mysql_connection record(), same +%% as ConnList but without Conn +%%-------------------------------------------------------------------- +get_next_mysql_connection_for_id(Id, ConnList) -> + get_next_mysql_connection_for_id(Id, ConnList, []). + +get_next_mysql_connection_for_id(Id, [#mysql_connection{id = Id} = H | T], Res) -> + {ok, H, lists:reverse(Res) ++ T}; +get_next_mysql_connection_for_id(Id, [H | T], Res) when is_record(H, mysql_connection) -> + get_next_mysql_connection_for_id(Id, T, [H | Res]); +get_next_mysql_connection_for_id(_Id, [], _Res) -> + nomatch. + +%%-------------------------------------------------------------------- +%% Function: start_reconnect(Conn, LogFun) +%% Conn = mysql_connection record() +%% LogFun = undefined | function() with arity 3 +%% Descrip.: Spawns a process that will try to re-establish a new +%% connection instead of the one in Conn which has just +%% died. +%% Returns : ok +%%-------------------------------------------------------------------- +start_reconnect(Conn, LogFun) when is_record(Conn, mysql_connection) -> + Pid = spawn(fun () -> + reconnect_loop(Conn#mysql_connection{conn_pid = undefined}, LogFun, 0) + end), + {Id, Host, Port} = {Conn#mysql_connection.id, Conn#mysql_connection.host, Conn#mysql_connection.port}, + log(LogFun, debug, "mysql: Started pid ~p to try and reconnect to ~p:~s:~p (replacing " + "connection with pid ~p)", [Pid, Id, Host, Port, Conn#mysql_connection.conn_pid]), + ok. + +%%-------------------------------------------------------------------- +%% Function: reconnect_loop(Conn, LogFun, 0) +%% Conn = mysql_connection record() +%% LogFun = undefined | function() with arity 3 +%% Descrip.: Loop indefinately until we are able to reconnect to the +%% server specified in the now dead connection Conn. +%% Returns : ok +%%-------------------------------------------------------------------- +reconnect_loop(Conn, LogFun, N) when is_record(Conn, mysql_connection) -> + {Id, Host, Port} = {Conn#mysql_connection.id, Conn#mysql_connection.host, Conn#mysql_connection.port}, + case connect(Id, + Host, + Port, + Conn#mysql_connection.user, + Conn#mysql_connection.password, + Conn#mysql_connection.database, + Conn#mysql_connection.reconnect) of + {ok, ConnPid} -> + log(LogFun, debug, "mysql_reconnect: Managed to reconnect to ~p:~s:~p (connection pid ~p)", + [Id, Host, Port, ConnPid]), + ok; + {error, Reason} -> + %% log every once in a while + NewN = case N of + 10 -> + log(LogFun, debug, "mysql_reconnect: Still unable to connect to ~p:~s:~p (~p)", + [Id, Host, Port, Reason]), + 0; + _ -> + N + 1 + end, + %% sleep between every unsuccessfull attempt + timer:sleep(20 * 1000), + reconnect_loop(Conn, LogFun, NewN) + end. diff --git a/web/api/flukso/deps/mysql/src/mysql.hrl b/web/api/flukso/deps/mysql/src/mysql.hrl new file mode 100644 index 0000000..aee5611 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql.hrl @@ -0,0 +1,6 @@ +%% MySQL result record: +-record(mysql_result, + {fieldinfo=[], + rows=[], + affectedrows=0, + error=""}). diff --git a/web/api/flukso/deps/mysql/src/mysql_app.erl b/web/api/flukso/deps/mysql/src/mysql_app.erl new file mode 100644 index 0000000..46e629e --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_app.erl @@ -0,0 +1,18 @@ +-module(mysql_app). + +-export([start/0, stop/0]). +-behavior(application). +-export([start/2, stop/1]). + + +start() -> + application:start(mysql). + +start(_Type, _Args) -> + mysql_sup:start_link(). + +stop() -> + application:stop(mysql). + +stop(_State) -> + ok. diff --git a/web/api/flukso/deps/mysql/src/mysql_auth.erl b/web/api/flukso/deps/mysql/src/mysql_auth.erl new file mode 100644 index 0000000..bfa226d --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_auth.erl @@ -0,0 +1,194 @@ +%%%------------------------------------------------------------------- +%%% File : mysql_auth.erl +%%% Author : Fredrik Thulin +%%% Descrip.: MySQL client authentication functions. +%%% Created : 4 Aug 2005 by Fredrik Thulin +%%% +%%% Note : All MySQL code was written by Magnus Ahltorp, originally +%%% in the file mysql.erl - I just moved it here. +%%% +%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan +%%% See the file COPYING +%%% +%%%------------------------------------------------------------------- +-module(mysql_auth). + +%%-------------------------------------------------------------------- +%% External exports (should only be used by the 'mysql_conn' module) +%%-------------------------------------------------------------------- +-export([ + do_old_auth/7, + do_new_auth/8 + ]). + +%%-------------------------------------------------------------------- +%% Macros +%%-------------------------------------------------------------------- +-define(LONG_PASSWORD, 1). +-define(LONG_FLAG, 4). +-define(PROTOCOL_41, 512). +-define(TRANSACTIONS, 8192). +-define(SECURE_CONNECTION, 32768). +-define(CONNECT_WITH_DB, 8). + +-define(MAX_PACKET_SIZE, 1000000). + +%%==================================================================== +%% External functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, +%% LogFun) +%% Sock = term(), gen_tcp socket +%% RecvPid = pid(), receiver process pid +%% SeqNum = integer(), first sequence number we should use +%% User = string(), MySQL username +%% Password = string(), MySQL password +%% Salt1 = string(), salt 1 from server greeting +%% LogFun = undefined | function() of arity 3 +%% Descrip.: Perform old-style MySQL authentication. +%% Returns : result of mysql_conn:do_recv/3 +%%-------------------------------------------------------------------- +do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, LogFun) -> + Auth = password_old(Password, Salt1), + Packet2 = make_auth(User, Auth), + do_send(Sock, Packet2, SeqNum, LogFun), + mysql_conn:do_recv(LogFun, RecvPid, SeqNum). + +%%-------------------------------------------------------------------- +%% Function: do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, +%% Salt2, LogFun) +%% Sock = term(), gen_tcp socket +%% RecvPid = pid(), receiver process pid +%% SeqNum = integer(), first sequence number we should use +%% User = string(), MySQL username +%% Password = string(), MySQL password +%% Salt1 = string(), salt 1 from server greeting +%% Salt2 = string(), salt 2 from server greeting +%% LogFun = undefined | function() of arity 3 +%% Descrip.: Perform MySQL authentication. +%% Returns : result of mysql_conn:do_recv/3 +%%-------------------------------------------------------------------- +do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, Salt2, LogFun) -> + Auth = password_new(Password, Salt1 ++ Salt2), + Packet2 = make_new_auth(User, Auth, none), + do_send(Sock, Packet2, SeqNum, LogFun), + case mysql_conn:do_recv(LogFun, RecvPid, SeqNum) of + {ok, Packet3, SeqNum2} -> + case Packet3 of + <<254:8>> -> + AuthOld = password_old(Password, Salt1), + do_send(Sock, <>, SeqNum2 + 1, LogFun), + mysql_conn:do_recv(LogFun, RecvPid, SeqNum2 + 1); + _ -> + {ok, Packet3, SeqNum2} + end; + {error, Reason} -> + {error, Reason} + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +password_old(Password, Salt) -> + {P1, P2} = hash(Password), + {S1, S2} = hash(Salt), + Seed1 = P1 bxor S1, + Seed2 = P2 bxor S2, + List = rnd(9, Seed1, Seed2), + {L, [Extra]} = lists:split(8, List), + list_to_binary(lists:map(fun (E) -> + E bxor (Extra - 64) + end, L)). + +%% part of do_old_auth/4, which is part of mysql_init/4 +make_auth(User, Password) -> + Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS, + Maxsize = 0, + UserB = list_to_binary(User), + PasswordB = Password, + <>. + +%% part of do_new_auth/4, which is part of mysql_init/4 +make_new_auth(User, Password, Database) -> + DBCaps = case Database of + none -> + 0; + _ -> + ?CONNECT_WITH_DB + end, + Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS bor + ?PROTOCOL_41 bor ?SECURE_CONNECTION bor DBCaps, + Maxsize = ?MAX_PACKET_SIZE, + UserB = list_to_binary(User), + PasswordL = size(Password), + DatabaseB = case Database of + none -> + <<>>; + _ -> + list_to_binary(Database) + end, + <>. + +hash(S) -> + hash(S, 1345345333, 305419889, 7). + +hash([C | S], N1, N2, Add) -> + N1_1 = N1 bxor (((N1 band 63) + Add) * C + N1 * 256), + N2_1 = N2 + ((N2 * 256) bxor N1_1), + Add_1 = Add + C, + hash(S, N1_1, N2_1, Add_1); +hash([], N1, N2, _Add) -> + Mask = (1 bsl 31) - 1, + {N1 band Mask , N2 band Mask}. + +rnd(N, Seed1, Seed2) -> + Mod = (1 bsl 30) - 1, + rnd(N, [], Seed1 rem Mod, Seed2 rem Mod). + +rnd(0, List, _, _) -> + lists:reverse(List); +rnd(N, List, Seed1, Seed2) -> + Mod = (1 bsl 30) - 1, + NSeed1 = (Seed1 * 3 + Seed2) rem Mod, + NSeed2 = (NSeed1 + Seed2 + 33) rem Mod, + Float = (float(NSeed1) / float(Mod))*31, + Val = trunc(Float)+64, + rnd(N - 1, [Val | List], NSeed1, NSeed2). + + + +dualmap(_F, [], []) -> + []; +dualmap(F, [E1 | R1], [E2 | R2]) -> + [F(E1, E2) | dualmap(F, R1, R2)]. + +bxor_binary(B1, B2) -> + list_to_binary(dualmap(fun (E1, E2) -> + E1 bxor E2 + end, binary_to_list(B1), binary_to_list(B2))). + +password_new(Password, Salt) -> + %% Check for blank password + case length(Password) of + 0 -> + <<>>; + _ -> + Stage1 = crypto:sha(Password), + Stage2 = crypto:sha(Stage1), + Res = crypto:sha_final( + crypto:sha_update( + crypto:sha_update(crypto:sha_init(), Salt), + Stage2) + ), + bxor_binary(Res, Stage1) + end. + +do_send(Sock, Packet, Num, LogFun) -> + mysql:log(LogFun, debug, "mysql_auth send packet ~p: ~p", [Num, Packet]), + Data = <<(size(Packet)):24/little, Num:8, Packet/binary>>, + gen_tcp:send(Sock, Data). diff --git a/web/api/flukso/deps/mysql/src/mysql_conn.erl b/web/api/flukso/deps/mysql/src/mysql_conn.erl new file mode 100644 index 0000000..414890f --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_conn.erl @@ -0,0 +1,656 @@ +%%%------------------------------------------------------------------- +%%% File : mysql_conn.erl +%%% Author : Fredrik Thulin +%%% Descrip.: MySQL connection handler, handles de-framing of messages +%%% received by the MySQL receiver process. +%%% Created : 5 Aug 2005 by Fredrik Thulin +%%% Modified: 11 Jan 2006 by Mickael Remond +%%% +%%% Note : All MySQL code was written by Magnus Ahltorp, originally +%%% in the file mysql.erl - I just moved it here. +%%% +%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan +%%% See the file COPYING +%%% +%%% +%%% This module handles a single connection to a single MySQL server. +%%% You can use it stand-alone, or through the 'mysql' module if you +%%% want to have more than one connection to the server, or +%%% connections to different servers. +%%% +%%% To use it stand-alone, set up the connection with +%%% +%%% {ok, Pid} = mysql_conn:start(Host, Port, User, Password, +%%% Database, LogFun) +%%% +%%% Host = string() +%%% Port = integer() +%%% User = string() +%%% Password = string() +%%% Database = string() +%%% LogFun = undefined | (gives logging to console) +%%% function() of arity 3 (Level, Fmt, Args) +%%% +%%% Note: In stand-alone mode you have to start Erlang crypto application by +%%% yourself with crypto:start() +%%% +%%% and then make MySQL querys with +%%% +%%% Result = mysql_conn:fetch(Pid, Query, self()) +%%% +%%% Result = {data, MySQLRes} | +%%% {updated, MySQLRes} | +%%% {error, MySQLRes} +%%% Where: MySQLRes = #mysql_result +%%% +%%% Actual data can be extracted from MySQLRes by calling the following API +%%% functions: +%%% - on data received: +%%% FieldInfo = mysql:get_result_field_info(MysqlRes) +%%% AllRows = mysql:get_result_rows(MysqlRes) +%%% with FieldInfo = list() of {Table, Field, Length, Name} +%%% and AllRows = list() of list() representing records +%%% - on update: +%%% Affected= mysql:get_result_affected_rows(MysqlRes) +%%% with Affected = integer() +%%% - on error: +%%% Reason = mysql:get_result_reason(MysqlRes) +%%% with Reason = string() +%%%------------------------------------------------------------------- + +-module(mysql_conn). + +%%-------------------------------------------------------------------- +%% External exports +%%-------------------------------------------------------------------- +-export([start/6, + start_link/6, + fetch/3, + fetch/4, + squery/4 + ]). + +%%-------------------------------------------------------------------- +%% External exports (should only be used by the 'mysql_auth' module) +%%-------------------------------------------------------------------- +-export([do_recv/3 + ]). + +-include("mysql.hrl"). +-record(state, { + mysql_version, + log_fun, + recv_pid, + socket, + data + }). + +-define(SECURE_CONNECTION, 32768). +-define(MYSQL_QUERY_OP, 3). +-define(DEFAULT_STANDALONE_TIMEOUT, 5000). +-define(DEFAULT_RESULT_TYPE, list). +-define(MYSQL_4_0, 40). %% Support for MySQL 4.0.x +-define(MYSQL_4_1, 41). %% Support for MySQL 4.1.x et 5.0.x + +%%==================================================================== +%% External functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: start(Host, Port, User, Password, Database, LogFun) +%% Function: start_link(Host, Port, User, Password, Database, LogFun) +%% Host = string() +%% Port = integer() +%% User = string() +%% Password = string() +%% Database = string() +%% LogFun = undefined | function() of arity 3 +%% Descrip.: Starts a mysql_conn process that connects to a MySQL +%% server, logs in and chooses a database. +%% Returns : {ok, Pid} | {error, Reason} +%% Pid = pid() +%% Reason = string() +%%-------------------------------------------------------------------- +start(Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User), + is_list(Password), is_list(Database) -> + ConnPid = self(), + Pid = spawn(fun () -> + init(Host, Port, User, Password, Database, LogFun, ConnPid) + end), + post_start(Pid, LogFun). + +start_link(Host, Port, User, Password, Database, LogFun) when is_list(Host), is_integer(Port), is_list(User), + is_list(Password), is_list(Database) -> + ConnPid = self(), + Pid = spawn_link(fun () -> + init(Host, Port, User, Password, Database, LogFun, ConnPid) + end), + post_start(Pid, LogFun). + +%% part of start/6 or start_link/6: +post_start(Pid, _LogFun) -> + %%Timeout = get_option(timeout, Options, ?DEFAULT_STANDALONE_TIMEOUT), + %%TODO find a way to get configured Options here + Timeout= ?DEFAULT_STANDALONE_TIMEOUT, + receive + {mysql_conn, Pid, ok} -> + {ok, Pid}; + {mysql_conn, Pid, {error, Reason}} -> + {error, Reason} +% Unknown -> +% mysql:log(_LogFun, error, "mysql_conn: Received unknown signal, exiting"), +% mysql:log(_LogFun, debug, "mysql_conn: Unknown signal : ~p", [Unknown]), +% {error, "unknown signal received"} + after Timeout -> + {error, "timed out"} + end. + +%%-------------------------------------------------------------------- +%% Function: fetch(Pid, Query, From) +%% fetch(Pid, Query, From, Timeout) +%% Pid = pid(), mysql_conn to send fetch-request to +%% Query = string(), MySQL query in verbatim +%% From = pid() or term(), use a From of self() when +%% using this module for a single connection, +%% or pass the gen_server:call/3 From argument if +%% using a gen_server to do the querys (e.g. the +%% mysql_dispatcher) +%% Timeout = integer() | infinity, gen_server timeout value +%% Descrip.: Send a query and wait for the result if running stand- +%% alone (From = self()), but don't block the caller if we +%% are not running stand-alone (From = gen_server From). +%% Returns : ok | (non-stand-alone mode) +%% {data, #mysql_result} | (stand-alone mode) +%% {updated, #mysql_result} | (stand-alone mode) +%% {error, #mysql_result} (stand-alone mode) +%% FieldInfo = term() +%% Rows = list() of [string()] +%% Reason = term() +%%-------------------------------------------------------------------- + +fetch(Pid, Query, From) -> + squery(Pid, Query, From, []). +fetch(Pid, Query, From, Timeout) -> + squery(Pid, Query, From, [{timeout, Timeout}]). + +squery(Pid, Query, From, Options) when is_pid(Pid), is_list(Query) -> + Self = self(), + Timeout = get_option(timeout, Options, ?DEFAULT_STANDALONE_TIMEOUT), + Pid ! {fetch, Query, From, Options}, + case From of + Self -> + %% We are not using a mysql_dispatcher, await the response + receive + {fetch_result, Pid, Result} -> + Result + after Timeout -> + {error, "query timed out"} + end; + _ -> + %% From is gen_server From, Pid will do gen_server:reply() when it has an answer + ok + end. + +%%-------------------------------------------------------------------- +%% Function: do_recv(LogFun, RecvPid, SeqNum) +%% LogFun = undefined | function() with arity 3 +%% RecvPid = pid(), mysql_recv process +%% SeqNum = undefined | integer() +%% Descrip.: Wait for a frame decoded and sent to us by RecvPid. +%% Either wait for a specific frame if SeqNum is an integer, +%% or just any frame if SeqNum is undefined. +%% Returns : {ok, Packet, Num} | +%% {error, Reason} +%% Reason = term() +%% +%% Note : Only to be used externally by the 'mysql_auth' module. +%%-------------------------------------------------------------------- +do_recv(LogFun, RecvPid, SeqNum) when is_function(LogFun); LogFun == undefined, SeqNum == undefined -> + receive + {mysql_recv, RecvPid, data, Packet, Num} -> + %%mysql:log(LogFun, debug, "mysql_conn: recv packet ~p: ~p", [Num, Packet]), + {ok, Packet, Num}; + {mysql_recv, RecvPid, closed, _E} -> + {error, "mysql_recv: socket was closed"} + end; +do_recv(LogFun, RecvPid, SeqNum) when is_function(LogFun); LogFun == undefined, is_integer(SeqNum) -> + ResponseNum = SeqNum + 1, + receive + {mysql_recv, RecvPid, data, Packet, ResponseNum} -> + %%mysql:log(LogFun, debug, "mysql_conn: recv packet ~p: ~p", [ResponseNum, Packet]), + {ok, Packet, ResponseNum}; + {mysql_recv, RecvPid, closed, _E} -> + {error, "mysql_recv: socket was closed"} + end. + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Host, Port, User, Password, Database, LogFun, +%% Parent) +%% Host = string() +%% Port = integer() +%% User = string() +%% Password = string() +%% Database = string() +%% LogFun = undefined | function() of arity 3 +%% Parent = pid() of process starting this mysql_conn +%% Descrip.: Connect to a MySQL server, log in and chooses a database. +%% Report result of this to Parent, and then enter loop() if +%% we were successfull. +%% Returns : void() | does not return +%%-------------------------------------------------------------------- +init(Host, Port, User, Password, Database, LogFun, Parent) -> + case mysql_recv:start_link(Host, Port, LogFun, self()) of + {ok, RecvPid, Sock} -> + case mysql_init(Sock, RecvPid, User, Password, LogFun) of + {ok, Version} -> + case do_query(Sock, RecvPid, LogFun, "use " ++ Database, Version, [{result_type, binary}]) of + {error, MySQLRes} -> + mysql:log(LogFun, error, "mysql_conn: Failed changing to database ~p : ~p", + [Database, mysql:get_result_reason(MySQLRes)]), + Parent ! {mysql_conn, self(), {error, failed_changing_database}}; + %% ResultType: data | updated + {_ResultType, _MySQLRes} -> + Parent ! {mysql_conn, self(), ok}, + State = #state{mysql_version=Version, + recv_pid = RecvPid, + socket = Sock, + log_fun = LogFun, + data = <<>> + }, + loop(State) + end; + {error, _Reason} -> + Parent ! {mysql_conn, self(), {error, login_failed}} + end; + E -> + mysql:log(LogFun, error, "mysql_conn: Failed connecting to ~p:~p : ~p", + [Host, Port, E]), + Parent ! {mysql_conn, self(), {error, connect_failed}} + end. + +%%-------------------------------------------------------------------- +%% Function: loop(State) +%% State = state record() +%% Descrip.: Wait for signals asking us to perform a MySQL query, or +%% signals that the socket was closed. +%% Returns : error | does not return +%%-------------------------------------------------------------------- +loop(State) -> + RecvPid = State#state.recv_pid, + receive + {fetch, Query, GenSrvFrom, Options} -> + %% GenSrvFrom is either a gen_server:call/3 From term(), or a pid if no + %% gen_server was used to make the query + Res = do_query(State, Query, Options), + case is_pid(GenSrvFrom) of + true -> + %% The query was not sent using gen_server mechanisms + GenSrvFrom ! {fetch_result, self(), Res}; + false -> + gen_server:reply(GenSrvFrom, Res) + end, + loop(State); + {mysql_recv, RecvPid, data, Packet, Num} -> + mysql:log(State#state.log_fun, error, "mysql_conn: Received MySQL data when not expecting any " + "(num ~p) - ignoring it", [Num]), + mysql:log(State#state.log_fun, error, "mysql_conn: Unexpected MySQL data (num ~p) :~n~p", + [Num, Packet]), + loop(State); + Unknown -> + mysql:log(State#state.log_fun, error, "mysql_conn: Received unknown signal, exiting"), + mysql:log(State#state.log_fun, debug, "mysql_conn: Unknown signal : ~p", [Unknown]), + error + end. + +%%-------------------------------------------------------------------- +%% Function: mysql_init(Sock, RecvPid, User, Password, LogFun) +%% Sock = term(), gen_tcp socket +%% RecvPid = pid(), mysql_recv process +%% User = string() +%% Password = string() +%% LogFun = undefined | function() with arity 3 +%% Descrip.: Try to authenticate on our new socket. +%% Returns : ok | {error, Reason} +%% Reason = string() +%%-------------------------------------------------------------------- +mysql_init(Sock, RecvPid, User, Password, LogFun) -> + case do_recv(LogFun, RecvPid, undefined) of + {ok, Packet, InitSeqNum} -> + {Version, Salt1, Salt2, Caps} = greeting(Packet, LogFun), + AuthRes = + case Caps band ?SECURE_CONNECTION of + ?SECURE_CONNECTION -> + mysql_auth:do_new_auth(Sock, RecvPid, InitSeqNum + 1, User, Password, Salt1, Salt2, LogFun); + _ -> + mysql_auth:do_old_auth(Sock, RecvPid, InitSeqNum + 1, User, Password, Salt1, LogFun) + end, + case AuthRes of + {ok, <<0:8, _Rest/binary>>, _RecvNum} -> + {ok,Version}; + {ok, <<255:8, Code:16/little, Message/binary>>, _RecvNum} -> + mysql:log(LogFun, error, "mysql_conn: init error ~p: ~p~n", [Code, binary_to_list(Message)]), + {error, binary_to_list(Message)}; + {ok, RecvPacket, _RecvNum} -> + mysql:log(LogFun, error, "mysql_conn: init unknown error ~p~n", [binary_to_list(RecvPacket)]), + {error, binary_to_list(RecvPacket)}; + {error, Reason} -> + mysql:log(LogFun, error, "mysql_conn: init failed receiving data : ~p~n", [Reason]), + {error, Reason} + end; + {error, Reason} -> + {error, Reason} + end. + +%% part of mysql_init/4 +greeting(Packet, LogFun) -> + <> = Packet, + {Version, Rest2} = asciz(Rest), + <<_TreadID:32/little, Rest3/binary>> = Rest2, + {Salt, Rest4} = asciz(Rest3), + <> = Rest4, + <> = Rest5, + {Salt2, _Rest7} = asciz(Rest6), + mysql:log(LogFun, debug, "mysql_conn: greeting version ~p (protocol ~p) salt ~p caps ~p serverchar ~p salt2 ~p", + [Version, Protocol, Salt, Caps, ServerChar, Salt2]), + {normalize_version(Version, LogFun), Salt, Salt2, Caps}. + +%% part of greeting/2 +asciz(Data) when binary(Data) -> + mysql:asciz_binary(Data, []); +asciz(Data) when list(Data) -> + {String, [0 | Rest]} = lists:splitwith(fun (C) -> + C /= 0 + end, Data), + {String, Rest}. + +%%-------------------------------------------------------------------- +%% Function: get_query_response(LogFun, RecvPid) +%% LogFun = undefined | function() with arity 3 +%% RecvPid = pid(), mysql_recv process +%% Version = integer(), Representing MySQL version used +%% Descrip.: Wait for frames until we have a complete query response. +%% Returns : {data, #mysql_result} +%% {updated, #mysql_result} +%% {error, #mysql_result} +%% FieldInfo = list() of term() +%% Rows = list() of [string()] +%% AffectedRows = int() +%% Reason = term() +%%-------------------------------------------------------------------- +get_query_response(LogFun, RecvPid, Version, Options) -> + case do_recv(LogFun, RecvPid, undefined) of + {ok, <>, _} -> + case Fieldcount of + 0 -> + %% No Tabular data + <> = Rest, + {updated, #mysql_result{affectedrows=AffectedRows}}; + 255 -> + <<_Code:16/little, Message/binary>> = Rest, + {error, #mysql_result{error=binary_to_list(Message)}}; + _ -> + %% Tabular data received + case get_fields(LogFun, RecvPid, [], Version) of + {ok, Fields} -> + ResultType = get_option(result_type, Options, ?DEFAULT_RESULT_TYPE), + case get_rows(Fieldcount, LogFun, RecvPid, ResultType, []) of + {ok, Rows} -> + {data, #mysql_result{fieldinfo=Fields, rows=Rows}}; + {error, Reason} -> + {error, #mysql_result{error=Reason}} + end; + {error, Reason} -> + {error, #mysql_result{error=Reason}} + end + end; + {error, Reason} -> + {error, #mysql_result{error=Reason}} + end. + +%%-------------------------------------------------------------------- +%% Function: get_fields(LogFun, RecvPid, [], Version) +%% LogFun = undefined | function() with arity 3 +%% RecvPid = pid(), mysql_recv process +%% Version = integer(), Representing MySQL version used +%% Descrip.: Received and decode field information. +%% Returns : {ok, FieldInfo} | +%% {error, Reason} +%% FieldInfo = list() of term() +%% Reason = term() +%%-------------------------------------------------------------------- +%% Support for MySQL 4.0.x: +get_fields(LogFun, RecvPid, Res, ?MYSQL_4_0) -> + case do_recv(LogFun, RecvPid, undefined) of + {ok, Packet, _Num} -> + case Packet of + <<254:8>> -> + {ok, lists:reverse(Res)}; + <<254:8, Rest/binary>> when size(Rest) < 8 -> + {ok, lists:reverse(Res)}; + _ -> + {Table, Rest} = get_with_length(Packet), + {Field, Rest2} = get_with_length(Rest), + {LengthB, Rest3} = get_with_length(Rest2), + LengthL = size(LengthB) * 8, + <> = LengthB, + {Type, Rest4} = get_with_length(Rest3), + {_Flags, _Rest5} = get_with_length(Rest4), + This = {binary_to_list(Table), + binary_to_list(Field), + Length, + %% TODO: Check on MySQL 4.0 if types are specified + %% using the same 4.1 formalism and could + %% be expanded to atoms: + binary_to_list(Type)}, + get_fields(LogFun, RecvPid, [This | Res], ?MYSQL_4_0) + end; + {error, Reason} -> + {error, Reason} + end; +%% Support for MySQL 4.1.x and 5.x: +get_fields(LogFun, RecvPid, Res, ?MYSQL_4_1) -> + case do_recv(LogFun, RecvPid, undefined) of + {ok, Packet, _Num} -> + case Packet of + <<254:8>> -> + {ok, lists:reverse(Res)}; + <<254:8, Rest/binary>> when size(Rest) < 8 -> + {ok, lists:reverse(Res)}; + _ -> + {_Catalog, Rest} = get_with_length(Packet), + {_Database, Rest2} = get_with_length(Rest), + {Table, Rest3} = get_with_length(Rest2), + %% OrgTable is the real table name if Table is an alias + {_OrgTable, Rest4} = get_with_length(Rest3), + {Field, Rest5} = get_with_length(Rest4), + %% OrgField is the real field name if Field is an alias + {_OrgField, Rest6} = get_with_length(Rest5), + + <<_Metadata:8/little, _Charset:16/little, + Length:32/little, Type:8/little, + _Flags:16/little, _Decimals:8/little, + _Rest7/binary>> = Rest6, + + This = {binary_to_list(Table), + binary_to_list(Field), + Length, + get_field_datatype(Type)}, + get_fields(LogFun, RecvPid, [This | Res], ?MYSQL_4_1) + end; + {error, Reason} -> + {error, Reason} + end. + +%%-------------------------------------------------------------------- +%% Function: get_rows(N, LogFun, RecvPid, []) +%% N = integer(), number of rows to get +%% LogFun = undefined | function() with arity 3 +%% RecvPid = pid(), mysql_recv process +%% Descrip.: Receive and decode a number of rows. +%% Returns : {ok, Rows} | +%% {error, Reason} +%% Rows = list() of [string()] +%%-------------------------------------------------------------------- +get_rows(N, LogFun, RecvPid, ResultType, Res) -> + case do_recv(LogFun, RecvPid, undefined) of + {ok, Packet, _Num} -> + case Packet of + <<254:8, Rest/binary>> when size(Rest) < 8 -> + {ok, lists:reverse(Res)}; + _ -> + {ok, This} = get_row(N, Packet, ResultType, []), + get_rows(N, LogFun, RecvPid, ResultType, [This | Res]) + end; + {error, Reason} -> + {error, Reason} + end. + + +%% part of get_rows/4 +get_row(0, _Data, _ResultType, Res) -> + {ok, lists:reverse(Res)}; +get_row(N, Data, ResultType, Res) -> + {Col, Rest} = get_with_length(Data), + This = case Col of + null -> + null; + _ -> + if + ResultType == list -> + binary_to_list(Col); + ResultType == binary -> + Col + end + end, + get_row(N - 1, Rest, ResultType, [This | Res]). + +get_with_length(<<251:8, Rest/binary>>) -> + {null, Rest}; +get_with_length(<<252:8, Length:16/little, Rest/binary>>) -> + split_binary(Rest, Length); +get_with_length(<<253:8, Length:24/little, Rest/binary>>) -> + split_binary(Rest, Length); +get_with_length(<<254:8, Length:64/little, Rest/binary>>) -> + split_binary(Rest, Length); +get_with_length(<>) when Length < 251 -> + split_binary(Rest, Length). + +%%-------------------------------------------------------------------- +%% Function: do_query(State, Query) +%% do_query(Sock, RecvPid, LogFun, Query) +%% Sock = term(), gen_tcp socket +%% RecvPid = pid(), mysql_recv process +%% LogFun = undefined | function() with arity 3 +%% Query = string() +%% Descrip.: Send a MySQL query and block awaiting it's response. +%% Returns : result of get_query_response/2 | {error, Reason} +%%-------------------------------------------------------------------- +do_query(State, Query, Options) when is_record(State, state) -> + do_query(State#state.socket, + State#state.recv_pid, + State#state.log_fun, + Query, + State#state.mysql_version, + Options + ). + +do_query(Sock, RecvPid, LogFun, Query, Version, Options) when is_pid(RecvPid), + is_list(Query) -> + Packet = list_to_binary([?MYSQL_QUERY_OP, Query]), + case do_send(Sock, Packet, 0, LogFun) of + ok -> + get_query_response(LogFun, RecvPid, Version, Options); + {error, Reason} -> + Msg = io_lib:format("Failed sending data on socket : ~p", [Reason]), + {error, Msg} + end. + +%%-------------------------------------------------------------------- +%% Function: do_send(Sock, Packet, SeqNum, LogFun) +%% Sock = term(), gen_tcp socket +%% Packet = binary() +%% SeqNum = integer(), packet sequence number +%% LogFun = undefined | function() with arity 3 +%% Descrip.: Send a packet to the MySQL server. +%% Returns : result of gen_tcp:send/2 +%%-------------------------------------------------------------------- +do_send(Sock, Packet, SeqNum, _LogFun) when is_binary(Packet), is_integer(SeqNum) -> + Data = <<(size(Packet)):24/little, SeqNum:8, Packet/binary>>, + %%mysql:log(LogFun, debug, "mysql_conn: send packet ~p: ~p", [SeqNum, Data]), + gen_tcp:send(Sock, Data). + +%%-------------------------------------------------------------------- +%% Function: normalize_version(Version, LogFun) +%% Version = string() +%% LogFun = undefined | function() with arity 3 +%% Descrip.: Return a flag corresponding to the MySQL version used. +%% The protocol used depends on this flag. +%% Returns : Version = string() +%%-------------------------------------------------------------------- +normalize_version([$4,$.,$0|_T], LogFun) -> + mysql:log(LogFun, debug, "Switching to MySQL 4.0.x protocol.~n"), + ?MYSQL_4_0; +normalize_version([$4,$.,$1|_T], _LogFun) -> + ?MYSQL_4_1; +normalize_version([$5|_T], _LogFun) -> + %% MySQL version 5.x protocol is compliant with MySQL 4.1.x: + ?MYSQL_4_1; +normalize_version(_Other, LogFun) -> + mysql:log(LogFun, error, "MySQL version not supported: MySQL Erlang module might not work correctly.~n"), + %% Error, but trying the oldest protocol anyway: + ?MYSQL_4_0. + +%%-------------------------------------------------------------------- +%% Function: get_field_datatype(DataType) +%% DataType = integer(), MySQL datatype +%% Descrip.: Return MySQL field datatype as description string +%% Returns : String, MySQL datatype +%%-------------------------------------------------------------------- +get_field_datatype(0) -> 'DECIMAL'; +get_field_datatype(1) -> 'TINY'; +get_field_datatype(2) -> 'SHORT'; +get_field_datatype(3) -> 'LONG'; +get_field_datatype(4) -> 'FLOAT'; +get_field_datatype(5) -> 'DOUBLE'; +get_field_datatype(6) -> 'NULL'; +get_field_datatype(7) -> 'TIMESTAMP'; +get_field_datatype(8) -> 'LONGLONG'; +get_field_datatype(9) -> 'INT24'; +get_field_datatype(10) -> 'DATE'; +get_field_datatype(11) -> 'TIME'; +get_field_datatype(12) -> 'DATETIME'; +get_field_datatype(13) -> 'YEAR'; +get_field_datatype(14) -> 'NEWDATE'; +get_field_datatype(16) -> 'BIT'; +get_field_datatype(246) -> 'DECIMAL'; +get_field_datatype(247) -> 'ENUM'; +get_field_datatype(248) -> 'SET'; +get_field_datatype(249) -> 'TINYBLOB'; +get_field_datatype(250) -> 'MEDIUM_BLOG'; +get_field_datatype(251) -> 'LONG_BLOG'; +get_field_datatype(252) -> 'BLOB'; +get_field_datatype(253) -> 'VAR_STRING'; +get_field_datatype(254) -> 'STRING'; +get_field_datatype(255) -> 'GEOMETRY'. + +%%-------------------------------------------------------------------- +%% Function: get_option(Key1, Options, Default) -> Value1 +%% Options = [Option] +%% Option = {Key2, Value2} +%% Key1 = Key2 = atom() +%% Value1 = Value2 = Default = term() +%% Descrip.: Return the option associated with Key passed to squery/4 +%%-------------------------------------------------------------------- + +get_option(Key, Options, Default) -> + case lists:keysearch(Key, 1, Options) of + {value, {_, Value}} -> + Value; + false -> + Default + end. diff --git a/web/api/flukso/deps/mysql/src/mysql_recv.erl b/web/api/flukso/deps/mysql/src/mysql_recv.erl new file mode 100644 index 0000000..d770d9b --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_recv.erl @@ -0,0 +1,161 @@ +%%%------------------------------------------------------------------- +%%% File : mysql_recv.erl +%%% Author : Fredrik Thulin +%%% Descrip.: Handles data being received on a MySQL socket. Decodes +%%% per-row framing and sends each row to parent. +%%% +%%% Created : 4 Aug 2005 by Fredrik Thulin +%%% +%%% Note : All MySQL code was written by Magnus Ahltorp, originally +%%% in the file mysql.erl - I just moved it here. +%%% +%%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan +%%% See the file COPYING +%%% +%%% Signals this receiver process can send to it's parent +%%% (the parent is a mysql_conn connection handler) : +%%% +%%% {mysql_recv, self(), data, Packet, Num} +%%% {mysql_recv, self(), closed, {error, Reason}} +%%% {mysql_recv, self(), closed, normal} +%%% +%%% Internally (from inside init/4 to start_link/4) the +%%% following signals may be sent to the parent process : +%%% +%%% {mysql_recv, self(), init, {ok, Sock}} +%%% {mysql_recv, self(), init, {error, E}} +%%% +%%%------------------------------------------------------------------- +-module(mysql_recv). + +%%-------------------------------------------------------------------- +%% External exports (should only be used by the 'mysql_conn' module) +%%-------------------------------------------------------------------- +-export([start_link/4 + ]). + +-record(state, { + socket, + parent, + log_fun, + data + }). + +-define(SECURE_CONNECTION, 32768). +-define(CONNECT_TIMEOUT, 5000). + +%%==================================================================== +%% External functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: start_link(Host, Port, LogFun, Parent) +%% Host = string() +%% Port = integer() +%% LogFun = undefined | function() of arity 3 +%% Parent = pid(), process that should get received frames +%% Descrip.: Start a process that connects to Host:Port and waits for +%% data. When it has received a MySQL frame, it sends it to +%% Parent and waits for the next frame. +%% Returns : {ok, RecvPid, Socket} | +%% {error, Reason} +%% RecvPid = pid(), receiver process pid +%% Socket = term(), gen_tcp socket +%% Reason = atom() | string() +%%-------------------------------------------------------------------- +start_link(Host, Port, LogFun, Parent) when is_list(Host), is_integer(Port) -> + RecvPid = + spawn_link(fun () -> + init(Host, Port, LogFun, Parent) + end), + %% wait for the socket from the spawned pid + receive + {mysql_recv, RecvPid, init, {error, E}} -> + {error, E}; + {mysql_recv, RecvPid, init, {ok, Socket}} -> + {ok, RecvPid, Socket} + after ?CONNECT_TIMEOUT -> + catch exit(RecvPid, kill), + {error, "timeout"} + end. + + + +%%==================================================================== +%% Internal functions +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init((Host, Port, LogFun, Parent) +%% Host = string() +%% Port = integer() +%% LogFun = undefined | function() of arity 3 +%% Parent = pid(), process that should get received frames +%% Descrip.: Connect to Host:Port and then enter receive-loop. +%% Returns : error | never returns +%%-------------------------------------------------------------------- +init(Host, Port, LogFun, Parent) -> + case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of + {ok, Sock} -> + Parent ! {mysql_recv, self(), init, {ok, Sock}}, + State = #state{socket = Sock, + parent = Parent, + log_fun = LogFun, + data = <<>> + }, + loop(State); + E -> + mysql:log(LogFun, error, "mysql_recv: Failed connecting to ~p:~p : ~p", + [Host, Port, E]), + Msg = lists:flatten(io_lib:format("connect failed : ~p", [E])), + Parent ! {mysql_recv, self(), init, {error, Msg}} + end. + +%%-------------------------------------------------------------------- +%% Function: loop(State) +%% State = state record() +%% Descrip.: The main loop. Wait for data from our TCP socket and act +%% on received data or signals that our socket was closed. +%% Returns : error | never returns +%%-------------------------------------------------------------------- +loop(State) -> + Sock = State#state.socket, + receive + {tcp, Sock, InData} -> + NewData = list_to_binary([State#state.data, InData]), + %% send data to parent if we have enough data + Rest = sendpacket(State#state.parent, NewData), + loop(State#state{data = Rest}); + {tcp_error, Sock, Reason} -> + mysql:log(State#state.log_fun, error, "mysql_recv: Socket ~p closed : ~p", [Sock, Reason]), + State#state.parent ! {mysql_recv, self(), closed, {error, Reason}}, + error; + {tcp_closed, Sock} -> + mysql:log(State#state.log_fun, debug, "mysql_recv: Socket ~p closed", [Sock]), + State#state.parent ! {mysql_recv, self(), closed, normal}, + error + end. + +%%-------------------------------------------------------------------- +%% Function: sendpacket(Parent, Data) +%% Parent = pid() +%% Data = binary() +%% Descrip.: Check if we have received one or more complete frames by +%% now, and if so - send them to Parent. +%% Returns : Rest = binary() +%%-------------------------------------------------------------------- +%% send data to parent if we have enough data +sendpacket(Parent, Data) -> + case Data of + <> -> + if + Length =< size(D) -> + {Packet, Rest} = split_binary(D, Length), + Parent ! {mysql_recv, self(), data, Packet, Num}, + sendpacket(Parent, Rest); + true -> + Data + end; + _ -> + Data + end. diff --git a/web/api/flukso/deps/mysql/src/mysql_sup.erl b/web/api/flukso/deps/mysql/src/mysql_sup.erl new file mode 100644 index 0000000..06c35a9 --- /dev/null +++ b/web/api/flukso/deps/mysql/src/mysql_sup.erl @@ -0,0 +1,22 @@ +-module(mysql_sup). + +-export([start_link/0]). + +-behaviour(supervisor). + +-export([init/1]). + +%% @spec start_link() -> Result +%% Result = {ok,Pid} | ignore | {error,Error} +%% Pid = pid() +%% Error = {already_started,Pid} | shutdown | term() +start_link() -> + supervisor:start_link(mysql_sup, []). + +init([]) -> + MysqlConfig = [pool, "localhost", "flukso", "your_mysql_password_here", "flukso"], + Mysql = {mysql, + {mysql, start_link, MysqlConfig}, + permanent, 3000, worker, [mysql]}, + Processes = [Mysql], + {ok, {{one_for_one, 5, 10}, Processes}}. diff --git a/web/api/flukso/deps/mysql/support/include.mk b/web/api/flukso/deps/mysql/support/include.mk new file mode 100644 index 0000000..065e409 --- /dev/null +++ b/web/api/flukso/deps/mysql/support/include.mk @@ -0,0 +1,39 @@ +## -*- makefile -*- + +###################################################################### +## Erlang + +ERL := erl +ERLC := $(ERL)c + +INCLUDE_DIRS := ../include $(wildcard ../deps/*/include) +EBIN_DIRS := $(wildcard ../deps/*/ebin) +ERLC_FLAGS := -W $(INCLUDE_DIRS:../%=-I ../%) $(EBIN_DIRS:%=-pa %) + +ifndef no_debug_info + ERLC_FLAGS += +debug_info +endif + +ifdef debug + ERLC_FLAGS += -Ddebug +endif + +EBIN_DIR := ../ebin +EMULATOR := beam + +ERL_SOURCES := $(wildcard *.erl) +ERL_HEADERS := $(wildcard *.hrl) $(wildcard ../include/*.hrl) +ERL_OBJECTS := $(ERL_SOURCES:%.erl=$(EBIN_DIR)/%.$(EMULATOR)) +ERL_OBJECTS_LOCAL := $(ERL_SOURCES:%.erl=./%.$(EMULATOR)) +APP_FILES := $(wildcard *.app) +EBIN_FILES = $(ERL_OBJECTS) $(APP_FILES:%.app=../ebin/%.app) +MODULES = $(ERL_SOURCES:%.erl=%) + +../ebin/%.app: %.app + cp $< $@ + +$(EBIN_DIR)/%.$(EMULATOR): %.erl + $(ERLC) $(ERLC_FLAGS) -o $(EBIN_DIR) $< + +./%.$(EMULATOR): %.erl + $(ERLC) $(ERLC_FLAGS) -o . $< diff --git a/web/api/flukso/src/flukso.app b/web/api/flukso/src/flukso.app index b139907..f88de78 100644 --- a/web/api/flukso/src/flukso.app +++ b/web/api/flukso/src/flukso.app @@ -11,4 +11,4 @@ {registered, []}, {mod, {flukso_app, []}}, {env, []}, - {applications, [kernel, stdlib, crypto]}]}. + {applications, [kernel, stdlib, crypto, erlrrd, mysql, webmachine]}]}. diff --git a/web/api/flukso/src/flukso.erl b/web/api/flukso/src/flukso.erl index 698849e..701351b 100644 --- a/web/api/flukso/src/flukso.erl +++ b/web/api/flukso/src/flukso.erl @@ -19,8 +19,9 @@ ensure_started(App) -> %% @doc Starts the app for inclusion in a supervisor tree start_link() -> flukso_deps:ensure(), - ensure_started(erlrrd), ensure_started(crypto), + ensure_started(erlrrd), + ensure_started(mysql), ensure_started(webmachine), flukso_sup:start_link(). @@ -28,17 +29,18 @@ start_link() -> %% @doc Start the flukso server. start() -> flukso_deps:ensure(), - ensure_started(erlrrd), ensure_started(crypto), + ensure_started(erlrrd), + ensure_started(mysql), ensure_started(webmachine), - application:start(flukso). + application:start(flukso). %% @spec stop() -> ok %% @doc Stop the flukso server. stop() -> Res = application:stop(flukso), - application:stop(erlrrd), application:stop(webmachine), + application:stop(mysql), + application:stop(erlrrd), application:stop(crypto), Res. -