@ -4,14 +4,16 @@
- export ( [ start_link / 0 , init / 1 , handle_cast / 2 , handle_call / 3 ] ) .
- export ( [ init_db / 2 , generate / 2 ] ) .
- include ( " erlk.hrl " ) .
- record ( state , { database_file = none , database = none , teams = [ ] , config = none } ) .
start_link ( ) - >
gen_server : start_link ( { local , ? MODULE } , ? MODULE , [ ] , [ ] ) .
init ( [ ] ) - >
rand : seed ( default ) ,
{ ok , [ ] } .
{ ok , #state { } } .
duplicate ( 1 , Original , Constructed ) - > lists : append ( Original , Constructed ) ;
duplicate ( N , Original , Constructed ) - > duplicate ( N - 1 , Original , lists : append ( Original , Constructed ) ) .
@ -40,14 +42,7 @@ fill_padding_schedule(Matches, Teams) ->
hash_teams_list ( Teams ) - >
[ Y | | < < X : 4 > > < = crypto : hash ( sha , Teams ) , Y < - integer_to_list ( X , 16 ) ] .
generate ( Process , { DatabaseFile , Division , Round , Seed , Matches , Teams } ) - > gen_server : call ( Process , { new_schedule , DatabaseFile , Division , Round , Seed , Matches , Teams } ) .
init_db ( Process , DatabaseFile ) - > gen_server : call ( Process , { init_db , DatabaseFile } ) .
init_db ( File ) - >
IsFile = filelib : is_regular ( File ) ,
if IsFile - > file : delete ( File ) ;
true - > true end ,
{ ok , Database } = sqlite3 : open ( anonymous , [ { file , File } ] ) ,
init_db ( Database ) - >
ok = sqlite3 : create_table ( Database , teams , [ { name , text , [ not_null ] } ,
{ inspection , blob } ] ,
[ { primary_key , [ name ] } ] ) ,
@ -61,30 +56,30 @@ init_db(File) ->
{ red_2 , text , [ not_null ] } ,
{ blue_1 , text , [ not_null ] } ,
{ blue_2 , text , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , seed ] } ,
{ check , " round IN ('practice', 'qualification', 'elimination') " } ,
{ foreign_key , { [ blue_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ blue_2 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ red_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ red_2 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ] ) ,
[ { primary_key , [ division , round , size , teams , number , seed ] } ,
{ check , " round IN ('practice', 'qualification', 'elimination') " } ,
{ foreign_key , { [ blue_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ blue_2 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ red_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ red_2 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ] ) ,
ok = sqlite3 : create_table ( Database , match_scores , [ { division , integer , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ score , blob , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , seed , instance ] } ,
{ foreign_key , { [ division , round , size , teams , number , seed ] , matches , [ division , round , size , teams , number , seed ] , " ON DELETE CASCADE " } } ] ) ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ score , blob , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , seed , instance ] } ,
{ foreign_key , { [ division , round , size , teams , number , seed ] , matches , [ division , round , size , teams , number , seed ] , " ON DELETE CASCADE " } } ] ) ,
ok = sqlite3 : create_table ( Database , match_states , [ { division , integer , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed , integer , [ not_null ] } ,
{ time , integer , [ not_null ] } ,
{ state , blob , [ not_null ] } ] ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed , integer , [ not_null ] } ,
{ time , integer , [ not_null ] } ,
{ state , blob , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , seed , time ] } ,
{ foreign_key , { [ division , round , size , teams , number , seed ] , matches , [ division , round , size , teams , number , seed ] , " ON DELETE CASCADE " } } ] ) ,
@ -96,25 +91,43 @@ init_db(File) ->
{ score , blob , [ not_null ] } ] ,
[ { primary_key , [ team , type , attempt ] } ,
{ foreign_key , { [ team ] , teams , [ name ] , " ON DELETE CASCADE " } } ] ) ,
sqlite3 : close ( Database ) .
write ( DatabaseFile , Division , Round , Seed , Matches , Teams ) - >
MatchList = create_schedule ( Seed , Matches , Teams ) ,
ok .
delete_teams ( Database , Teams ) - >
SQL = lists : append ( [ " BEGIN; " ,
lists : append ( [ io_lib : format ( " DELETE FROM teams WHERE name = ~p ; " ,
[ Team ] ) | | Team < - Teams ] ) ,
" COMMIT; " ] ) ,
sqlite3 : sql_exec_script ( Database , SQL ) .
add_teams ( Database , Teams ) - >
SQL = lists : append ( [ " BEGIN; " ,
lists : append ( [ io_lib : format ( " INSERT INTO teams(name) VALUES(' ~s ') ON CONFLICT(name) DO NOTHING; " ,
[ Team ] ) | | Team < - Teams ] ) ,
" COMMIT; " ] ) ,
sqlite3 : sql_exec_script ( Database , SQL ) .
first_error ( [ ] ) - > ok ;
first_error ( [ ok | Rest ] ) - > first_error ( Rest ) ;
first_error ( [ Error | _ ] ) - > Error .
delete_matches ( Database , Division , Size , Seed , TeamsHash , Round ) - >
SQL = io_lib : format ( " DELETE FROM matches WHERE division = ~p AND size = ~p AND seed = ~p AND teams = ' ~s ' AND round = ' ~p ' " , [ Division , Size , Seed , TeamsHash , Round ] ) ,
sqlite3 : sql_exec_script ( Database , SQL ) .
write_matches ( Database , Division , Size , Seed , TeamsHash , Round , MatchTeams ) - >
Schedule = [ [ { division , Division } ,
{ round , atom_to_list ( Round ) } ,
{ size , Matches } ,
{ teams , hash_teams_list ( Teams ) } ,
{ size , Size } ,
{ teams , TeamsHash } ,
{ number , N } ,
{ seed , Seed } ,
{ blue_1 , B1 } ,
{ blue_2 , B2 } ,
{ red_1 , R1 } ,
{ red_2 , R2 } ] | | { N , [ B1 , B2 , R1 , R2 ] } < - lists : enumerate ( MatchList ) ] ,
{ ok , Database } = sqlite3 : open ( anonymous , [ { file , DatabaseFile } ] ) ,
{ red_2 , R2 } ] | | { N , [ B1 , B2 , R1 , R2 ] } < - lists : enumerate ( MatchTeams ) ] ,
[ ok | Resp ] = sqlite3 : write_many ( Database , matches , Schedule ) ,
ok = lists : last ( Resp ) ,
ok = sqlite3 : close ( Database ) .
lists : last ( Resp ) .
create_schedule ( Seed , Matches , Teams ) - >
TeamsRepeated = duplicate ( Matches , Teams ) ,
@ -139,9 +152,92 @@ pick_matches(Teams, Matches) ->
pick_matches ( Teams ) - > pick_matches ( Teams , [ ] ) .
handle_cast ( _ , State ) - > { noreply , State } .
handle_call ( { new_db , DatabaseFile } , _ , State ) - >
ok = if State #state.database =:= none - > ok ;
true - > sqlite3 : close ( State #state.database )
end ,
Exists = filelib : is_regular ( DatabaseFile ) ,
ok = if Exists - > file : delete ( DatabaseFile ) ;
true - > ok
end ,
{ ok , Database } = sqlite3 : open ( schedule_db , [ { file , DatabaseFile } ] ) ,
ok = sqlite3 : sql_exec ( Database , " PRAGMA foreign_keys = ON; " ) ,
{ reply , init_db ( Database ) , State #state { database_file = DatabaseFile , database = Database } } ;
handle_call ( { load_db , DatabaseFile } , _ , State ) - >
true = filelib : is_regular ( DatabaseFile ) ,
{ ok , Database } = if DatabaseFile == State #state.database_file - >
{ ok , State #state.database } ;
true - >
ok = if State #state.database =:= none - > ok ;
true - > sqlite3 : close ( State #state.database )
end ,
{ ok , DB } = sqlite3 : open ( schedule_db , [ { file , DatabaseFile } ] ) ,
{ sqlite3 : sql_exec ( DB , " PRAGMA foreign_keys = ON; " ) , DB }
end ,
{ reply , ok , State #state { database_file = DatabaseFile , database = Database } } .
handle_cast ( { delete_teams , Removed } , State ) - >
Teams = lists : filter ( fun ( X ) - > lists : member ( X , Removed ) =:= false end , State #state.teams ) ,
ok = first_error ( delete_teams ( State #state.database , Removed ) ) ,
{ noreply , State #state { teams = Teams } } ;
handle_cast ( { add_teams , Teams } , State ) - >
ok = first_error ( add_teams ( State #state.database , Teams ) ) ,
{ noreply , State #state { teams = Teams } } ;
handle_cast ( { new_schedule , Division , Size , Seed , Teams , Round } , State ) - >
MatchTeams = create_schedule ( Seed , Size , Teams ) ,
ok = write_matches ( State #state.database , Division , Size , Seed , hash_teams_list ( Teams ) , Round , MatchTeams ) ,
{ noreply , State } ;
handle_cast ( { delete_schedule , Division , Size , Seed , TeamsHash , Round } , State ) - >
ok = first_error ( delete_matches ( State #state.database , Division , Size , Seed , TeamsHash , Round ) ) ,
{ noreply , State } .
get_matches_from_db ( Database , Division , Round , Seed , Size , TeamsHash ) - >
if Size == 0 - > [ ] ;
Size > 0 - > SQL = io_lib : format ( " SELECT number, blue_1, blue_2, red_1, red_2 FROM
matches WHERE
division = '~p' AND
round = '~p' AND
size = '~p' AND
seed = '~p' AND
teams = '~s' ; " ,
[ Division , Round , Size , Seed , TeamsHash ] ) ,
[ { columns , Columns } , { rows , Rows } ] = sqlite3 : sql_exec ( Database , SQL ) ,
[ ]
end .
handle_call ( { init_db , DatabaseFile } , _ , State ) - >
{ reply , init_db ( DatabaseFile ) , State } ;
handle_call ( { new_schedule , DatabaseFile , Division , Round , Seed , Matches , Teams } , _ , State ) - >
{ reply , write ( DatabaseFile , Division , Round , Seed , Matches , Teams ) , State } .
parse_division ( Division ) - >
TeamsBinary = maps : get ( < < " teams " > > , Division ) ,
TeamsList = [ binary_to_list ( X ) | | X < - TeamsBinary ] ,
TeamsHash = schedule : hash_teams_list ( TeamsList ) ,
PracticeRounds = maps : get ( < < " practice_rounds " > > , Division ) ,
PracticeSeed = maps : get ( < < " practice_seed " > > , Division ) ,
QualificationRounds = maps : get ( < < " qualification_rounds " > > , Division ) ,
QualificationSeed = maps : get ( < < " qualification_seed " > > , Division ) ,
#division_config {
fields = [ binary_to_list ( X ) | | X < - maps : get ( < < " fields " > > , Division ) ] ,
elimination_alliances = maps : get ( < < " elimination_alliances " > > , Division ) ,
practice_rounds = PracticeRounds ,
practice_seed = PracticeSeed ,
qualification_rounds = QualificationRounds ,
qualification_seed = QualificationSeed ,
teams = TeamsList ,
teams_hash = TeamsHash
} .
parse_event_config ( ConfigJSON ) - >
Config = jsone : decode ( list_to_binary ( ConfigJSON ) ) ,
Divisions = lists : map ( fun parse_division / 1 , maps : get ( < < " divisions " > > , Config ) ) ,
TestingFields = [ binary_to_list ( X ) | | X < - maps : get ( < < " testing_fields " > > , Config ) ] ,
SkillsFields = [ binary_to_list ( X ) | | X < - maps : get ( < < " skills_fields " > > , Config ) ] ,
FinalsFields = [ binary_to_list ( X ) | | X < - maps : get ( < < " finals_fields " > > , Config ) ] ,
#event_config {
database_file = binary_to_list ( maps : get ( < < " database " > > , Config ) ) ,
finals_fields = FinalsFields ,
skills_fields = SkillsFields ,
testing_fields = TestingFields ,
divisions = Divisions
} .