@ -3,18 +3,43 @@
- behaviour ( gen_server ) .
- behaviour ( gen_server ) .
- export ( [ start_link / 1 , init / 1 , handle_cast / 2 , handle_call / 3 ] ) .
- export ( [ start_link / 1 , init / 1 , handle_cast / 2 , handle_call / 3 ] ) .
- export ( [ get_tick / 0 ] ) .
- record ( state , { database_file = none , database = none }) .
- record ( state , { database_file = none , database = none , tick_start = none }) .
start_link ( DatabaseFile ) - >
start_link ( DatabaseFile ) - >
gen_server : start_link ( { local , ? MODULE } , ? MODULE , [ DatabaseFile ] , [ ] ) .
gen_server : start_link ( { local , ? MODULE } , ? MODULE , [ DatabaseFile ] , [ ] ) .
open_db ( DatabaseFile ) - >
{ ok , DB } = sqlite3 : open ( event_db , [ { file , DatabaseFile } ] ) ,
ok = sqlite3 : sql_exec ( DB , " PRAGMA foreign_keys = ON; " ) ,
HT = case sqlite3 : sql_exec ( DB , " SELECT tick FROM match_states; " ) of
[ { columns , C1 } , { rows , R1 } ] - >
if length ( R1 ) == 0 - > 0 ;
true - >
T1 = get_column ( " tick " , C1 , R1 ) ,
lists : max ( T1 )
end ;
_ - > 0
end ,
HFT = case sqlite3 : sql_exec ( DB , " SELECT tick FROM finals_states; " ) of
[ { columns , C2 } , { rows , R2 } ] - >
if length ( R2 ) == 0 - > 0 ;
true - >
T2 = get_column ( " tick " , C2 , R2 ) ,
lists : max ( T2 )
end ;
_ - > 0
end ,
HighestTick = lists : max ( [ HT , HFT ] ) ,
TickStart = erlang : monotonic_time ( millisecond ) - HighestTick ,
{ TickStart , DB } .
init ( [ DatabaseFile ] ) - >
init ( [ DatabaseFile ] ) - >
Exists = filelib : is_regular ( DatabaseFile ) ,
Exists = filelib : is_regular ( DatabaseFile ) ,
if Exists =:= true - >
if Exists =:= true - >
{ ok , DB } = sqlite3 : open ( schedule_db , [ { file , DatabaseFile } ] ) ,
{ TickStart , DB } = open_db ( DatabaseFile ) ,
ok = sqlite3 : sql_exec ( DB , " PRAGMA foreign_keys = ON; " ) ,
{ ok , #state { database_file = DatabaseFile , database = DB , tick_start = TickStart } } ;
{ ok , #state { database_file = DatabaseFile , database = DB } } ;
true - >
true - >
io : format ( " Failed to load database ~s ~n " , [ DatabaseFile ] ) ,
io : format ( " Failed to load database ~s ~n " , [ DatabaseFile ] ) ,
{ ok , #state { database_file = none , database = none } }
{ ok , #state { database_file = none , database = none } }
@ -53,6 +78,8 @@ init_db(Database) ->
{ type , text , [ not_null ] } ,
{ type , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ red , integer , [ not_null ] } ,
{ blue , integer , [ not_null ] } ,
{ score , blob , [ not_null ] } ] ,
{ score , blob , [ not_null ] } ] ,
[ { primary_key , [ division , type , number , instance ] } ,
[ { primary_key , [ division , type , number , instance ] } ,
{ foreign_key , { [ division , type , number ] , matches , [ division , type , number ] , " ON DELETE CASCADE " } } ] ) ,
{ foreign_key , { [ division , type , number ] , matches , [ division , type , number ] , " ON DELETE CASCADE " } } ] ) ,
@ -60,9 +87,9 @@ init_db(Database) ->
ok = sqlite3 : create_table ( Database , match_states , [ { division , integer , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , match_states , [ { division , integer , [ not_null ] } ,
{ type , text , [ not_null ] } ,
{ type , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ ti me , integer , [ not_null ] } ,
{ ti ck , integer , [ not_null ] } ,
{ state , blob , [ not_null ] } ] ,
{ state , text , [ not_null ] } ] ,
[ { primary_key , [ division , type , number , ti me ] } ,
[ { primary_key , [ division , type , number , ti ck ] } ,
{ foreign_key , { [ division , type , number ] , matches , [ division , type , number ] , " ON DELETE CASCADE " } } ] ) ,
{ foreign_key , { [ division , type , number ] , matches , [ division , type , number ] , " ON DELETE CASCADE " } } ] ) ,
ok = sqlite3 : create_table ( Database , finals , [ { number , integer , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , finals , [ { number , integer , [ not_null ] } ,
@ -79,14 +106,16 @@ init_db(Database) ->
ok = sqlite3 : create_table ( Database , finals_scores , [ { number , integer , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , finals_scores , [ { number , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ red , integer , [ not_null ] } ,
{ blue , integer , [ not_null ] } ,
{ score , blob , [ not_null ] } ] ,
{ score , blob , [ not_null ] } ] ,
[ { primary_key , [ number , instance ] } ,
[ { primary_key , [ number , instance ] } ,
{ foreign_key , { [ number ] , finals , [ number ] , " " } } ] ) ,
{ foreign_key , { [ number ] , finals , [ number ] , " " } } ] ) ,
ok = sqlite3 : create_table ( Database , finals_states , [ { number , integer , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , finals_states , [ { number , integer , [ not_null ] } ,
{ ti me , integer , [ not_null ] } ,
{ ti ck , integer , [ not_null ] } ,
{ state , blob , [ not_null ] } ] ,
{ state , blob , [ not_null ] } ] ,
[ { primary_key , [ number , ti me ] } ,
[ { primary_key , [ number , ti ck ] } ,
{ foreign_key , { [ number ] , finals , [ number ] , " " } } ] ) ,
{ foreign_key , { [ number ] , finals , [ number ] , " " } } ] ) ,
ok = sqlite3 : create_table ( Database , skills_scores , [ { team , text , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , skills_scores , [ { team , text , [ not_null ] } ,
@ -129,8 +158,8 @@ write_division_matches(Database, Division, Type, Matches) ->
[ Division , atom_to_list ( Type ) ] ) ,
[ Division , atom_to_list ( Type ) ] ) ,
lists : append ( [ io_lib : format (
lists : append ( [ io_lib : format (
" INSERT INTO matches(division, type, number, blue_1, blue_2, red_1, red_2) VALUES ( ~p , ' ~s ', ~p , ' ~s ', ' ~s ', ' ~s ', ' ~s '); " ,
" INSERT INTO matches(division, type, number, blue_1, blue_2, red_1, red_2) VALUES ( ~p , ' ~s ', ~p , ' ~s ', ' ~s ', ' ~s ', ' ~s '); " ,
[ Division , atom_to_list ( Type ) , N , B1 , B2 , R1 , R2 ] )
[ Division , atom_to_list ( Type ) , N , B1 , B2 , R1 , R2 ] )
| | { N , [ B1 , B2 , R1 , R2 ] } < - lists : enumerate ( Matches ) ] ) ,
| | { N , [ B1 , B2 , R1 , R2 ] } < - lists : enumerate ( Matches ) ] ) ,
" COMMIT; " ] ) ,
" COMMIT; " ] ) ,
first_error ( sqlite3 : sql_exec_script ( Database , SQL ) ) .
first_error ( sqlite3 : sql_exec_script ( Database , SQL ) ) .
@ -147,6 +176,8 @@ get_column(Name, Columns, Rows) ->
Index = index_of ( Name , Columns ) ,
Index = index_of ( Name , Columns ) ,
[ element ( Index , Row ) | | Row < - Rows ] .
[ element ( Index , Row ) | | Row < - Rows ] .
handle_call ( get_tick_start , _ , State ) - >
{ reply , State #state.tick_start , State } ;
handle_call ( { new_db , DatabaseFile } , _ , State ) - >
handle_call ( { new_db , DatabaseFile } , _ , State ) - >
ok = if State #state.database =:= none - > ok ;
ok = if State #state.database =:= none - > ok ;
true - > sqlite3 : close ( State #state.database )
true - > sqlite3 : close ( State #state.database )
@ -155,21 +186,19 @@ handle_call({new_db, DatabaseFile}, _, State) ->
ok = if Exists - > file : delete ( DatabaseFile ) ;
ok = if Exists - > file : delete ( DatabaseFile ) ;
true - > ok
true - > ok
end ,
end ,
{ ok , Database } = sqlite3 : open ( schedule_db , [ { file , DatabaseFile } ] ) ,
{ TickStart , Database } = open_db ( DatabaseFile ) ,
ok = sqlite3 : sql_exec ( Database , " PRAGMA foreign_keys = ON; " ) ,
{ reply , init_db ( Database ) , State #state { database_file = DatabaseFile , database = Database , tick_start = TickStart } } ;
{ reply , init_db ( Database ) , State #state { database_file = DatabaseFile , database = Database } } ;
handle_call ( { load_db , DatabaseFile } , _ , State ) - >
handle_call ( { load_db , DatabaseFile } , _ , State ) - >
true = filelib : is_regular ( DatabaseFile ) ,
true = filelib : is_regular ( DatabaseFile ) ,
{ ok , Database } = if DatabaseFile == State #state.database_file - >
{ TickStart , Database } = if DatabaseFile == State #state.database_file - >
{ ok , State #state.database } ;
{ State #state.tick_start , State #state.database } ;
true - >
true - >
ok = if State #state.database =:= none - > ok ;
ok = if State #state.database =:= none - > ok ;
true - > sqlite3 : close ( State #state.database )
true - > sqlite3 : close ( State #state.database )
end ,
end ,
{ ok , DB } = sqlite3 : open ( schedule_db , [ { file , DatabaseFile } ] ) ,
open_db ( DatabaseFile )
{ sqlite3 : sql_exec ( DB , " PRAGMA foreign_keys = ON; " ) , DB }
end ,
end ,
{ reply , ok , State #state { database_file = DatabaseFile , database = Database , tick_start = TickStart } } ;
{ reply , ok , State #state { database_file = DatabaseFile , database = Database } } ;
handle_call ( close_db , _ , State ) - >
handle_call ( close_db , _ , State ) - >
if State #state.database =:= none - > { reply , ok , State } ;
if State #state.database =:= none - > { reply , ok , State } ;
true - >
true - >
@ -193,19 +222,38 @@ handle_call({delete_teams, Removed}, _, State) ->
ok = delete_teams ( State #state.database , Removed ) ,
ok = delete_teams ( State #state.database , Removed ) ,
{ reply , ok , State } ;
{ reply , ok , State } ;
handle_call ( { match_score , Division , Round , Number , Red , Blue , Score } , _ , State ) - >
[ { columns , Columns } , { rows , Rows } ] = sqlite3 : sql_exec ( State #state.database ,
io_lib : format (
" SELECT instance FROM match_scores WHERE Division = ~p AND type = ' ~s ' AND number = ~p ; " ,
[ Division , atom_to_list ( Round ) , Number ] ) ) ,
Instances = get_column ( " instance " , Columns , Rows ) ,
NextInstance = if length ( Instances ) == 0 - > 1 ;
true - > lists : max ( Instances ) + 1
end ,
ok = first_error ( sqlite3 : sql_exec_script ( State #state.database , lists : append (
[ io_lib : format ( " INSERT INTO match_scores(division, type, number, instance, red, blue, score) VALUES( ~p , ' ~s ', ~p , ~p , ~p , ~p , ' ~s '); " , [ Division , atom_to_list ( Round ) , Number , NextInstance , Red , Blue , Score ] ) ,
io_lib : format ( " INSERT INTO match_states(division, type, number, tick, state) VALUES( ~p , ' ~s ', ~p , ~p , ' ~s '); " , [ Division , atom_to_list ( Round ) , Number , get_tick ( State ) , " scored " ] ) ] ) ) ) ,
{ reply , ok , State } ;
handle_call ( { match_state , Division , Round , Number , MatchState } , _ , State ) - >
{ rowid , _ } = sqlite3 : sql_exec ( State #state.database ,
io_lib : format ( " INSERT INTO match_states(division, type, number, tick, state) VALUES( ~p , ' ~s ', ~p , ~p , ' ~s '); " ,
[ Division , atom_to_list ( Round ) , Number , get_tick ( State ) , MatchState ] ) ) ,
{ reply , ok , State } ;
handle_call ( { add_division , PSize , QSize , ESize } , _ , State ) - >
handle_call ( { add_division , PSize , QSize , ESize } , _ , State ) - >
[ { columns , Columns } , { rows , Rows } ] = sqlite3 : sql_exec ( State #state.database , " SELECT division FROM divisions; " ) ,
[ { columns , Columns } , { rows , Rows } ] = sqlite3 : sql_exec ( State #state.database , " SELECT division FROM divisions; " ) ,
Divisions = lists : sort ( get_column ( " division " , Columns , Rows ) ) ,
Divisions = lists : sort ( get_column ( " division " , Columns , Rows ) ) ,
NextDivision = first_empty ( [ 0 | Divisions ] ) ,
NextDivision = first_empty ( [ 0 | Divisions ] ) ,
{ rowid , _ } = sqlite3 : sql_exec ( State #state.database ,
{ rowid , _ } = sqlite3 : sql_exec ( State #state.database ,
io_lib : format ( " INSERT INTO divisions(division, psize, qsize, esize) VALUES( ~p , ~p , ~p , ~p ); " ,
io_lib : format ( " INSERT INTO divisions(division, psize, qsize, esize) VALUES( ~p , ~p , ~p , ~p ); " ,
[ NextDivision , PSize , QSize , ESize ] ) ) ,
[ NextDivision , PSize , QSize , ESize ] ) ) ,
{ reply , ok , State } ;
{ reply , ok , State } ;
handle_call ( { edit_division , Division , PSize , QSize , ESize } , _ , State ) - >
handle_call ( { edit_division , Division , PSize , QSize , ESize } , _ , State ) - >
ok = sqlite3 : sql_exec ( State #state.database ,
ok = sqlite3 : sql_exec ( State #state.database ,
io_lib : format (
io_lib : format (
" UPDATE divisions SET psize = ~p , qsize = ~p , esize = ~p WHERE division = ~p ; " ,
" UPDATE divisions SET psize = ~p , qsize = ~p , esize = ~p WHERE division = ~p ; " ,
[ PSize , QSize , ESize , Division ] ) ) ,
[ PSize , QSize , ESize , Division ] ) ) ,
{ reply , ok , State } ;
{ reply , ok , State } ;
handle_call ( { delete_division , Division } , _ , State ) - >
handle_call ( { delete_division , Division } , _ , State ) - >
ok = first_error ( sqlite3 : sql_exec_script ( State #state.database , io_lib : format ( " UPDATE teams SET division = NULL WHERE division = ~p ; DELETE FROM divisions WHERE division = ~p ; " , [ Division , Division ] ) ) ) ,
ok = first_error ( sqlite3 : sql_exec_script ( State #state.database , io_lib : format ( " UPDATE teams SET division = NULL WHERE division = ~p ; DELETE FROM divisions WHERE division = ~p ; " , [ Division , Division ] ) ) ) ,
@ -236,3 +284,7 @@ get_teams(Database) ->
handle_cast ( _ , State ) - >
handle_cast ( _ , State ) - >
{ noreply , State } .
{ noreply , State } .
get_tick ( State ) - > erlang : monotonic_time ( millisecond ) - State #state.tick_start .
get_tick ( ) - >
erlang : monotonic_time ( millisecond ) - gen_server : call ( ? MODULE , get_tick_start ) .