From 20091904b7bf4b2074b45e25c7eee0e56d19348b Mon Sep 17 00:00:00 2001 From: Jeffrey Armstrong Date: Mon, 21 Jun 2021 11:04:31 -0400 Subject: Groups of instructions are now supported, allowing launching multiple jobs at once --- captain/db.f90 | 355 +++++++++++++++++++++++++++++++++++++++-- captain/external.f90 | 2 +- captain/launch.f90 | 20 +++ captain/levitating-captain.prj | 2 +- captain/queryutils.f90 | 9 +- captain/sql/create.sql | 3 + captain/template.f90 | 2 +- captain/templates/home.html | 1 + captain/templates/index.html | 1 + captain/web.f90 | 251 ++++++++++++++++++++++++++++- 10 files changed, 630 insertions(+), 16 deletions(-) diff --git a/captain/db.f90 b/captain/db.f90 index 2cf8e4f..e4b4a4b 100644 --- a/captain/db.f90 +++ b/captain/db.f90 @@ -38,16 +38,27 @@ implicit none character(1024)::database_file type(c_ptr)::db - type :: job + type :: work_pair - integer::id integer::instruction integer::player + + end type + + type, extends(work_pair) :: job + + integer::id integer::status character(32)::time end type + type, extends(work_pair) :: group_entry + + integer::group + + end type + type :: task integer::job @@ -189,21 +200,48 @@ contains end function get_player_names - function get_instuctions_count() + function get_instructions_count() implicit none type(sqlite3_stmt)::stmt - integer::get_instuctions_count + integer::get_instructions_count - get_instuctions_count = 0 + get_instructions_count = 0 if(stmt%prepare(db, "SELECT COUNT(*) FROM instructions") == SQLITE_OK) then if(stmt%step() == SQLITE_ROW) then - get_instuctions_count = stmt%column_int(0) + get_instructions_count = stmt%column_int(0) end if end if call stmt%finalize() - end function get_instuctions_count + end function get_instructions_count + + function get_instruction_ids() result(res) + implicit none + + type(sqlite3_stmt)::stmt + integer, dimension(:), pointer::res + integer::i,n + + res => null() + + n = get_instructions_count() + if(n > 0) then + allocate(res(n)) + res = -1 + + if(stmt%prepare(db, "SELECT id, name FROM instructions ORDER BY name") == SQLITE_OK) then + i = 1 + do while(stmt%step() == SQLITE_ROW .and. i <= n) + res(i) = stmt%column_int(0) + i = i + 1 + end do + end if + call stmt%finalize() + + end if + + end function get_instruction_ids function get_instruction_names() result(res) implicit none @@ -212,7 +250,7 @@ contains character(len=PLAYER_NAME_LENGTH), dimension(:), pointer::res integer::i,n - n = get_instuctions_count() + n = get_instructions_count() if(n > 0) then allocate(res(n)) if(stmt%prepare(db, "SELECT name FROM instructions ORDER BY name") == SQLITE_OK) then @@ -295,7 +333,7 @@ contains type(sqlite3_stmt)::stmt str = " " - + Print *, id if(stmt%prepare(db, "SELECT name FROM instructions WHERE id=?") == SQLITE_OK) then if(stmt%bind_int(1, id) == SQLITE_OK) then if(stmt%step() == SQLITE_ROW) then @@ -776,4 +814,303 @@ contains end subroutine scan_instructions_for_db + subroutine add_group_db(name) + implicit none + + character(*), intent(in)::name + + type(sqlite3_stmt)::stmt + + if(stmt%prepare(db, "INSERT INTO groups(name) VALUES(?)") == SQLITE_OK) then + if(stmt%bind_text(1, name) == SQLITE_OK) then + + call stmt%step_now() + + end if + end if + call stmt%finalize() + + end subroutine add_group_db + + subroutine get_group_name_db(id, name) + implicit none + + integer, intent(in)::id + character(*), intent(out)::name + type(sqlite3_stmt)::stmt + + if(stmt%prepare(db, "SELECT name FROM groups WHERE id=?") == SQLITE_OK) then + if(stmt%bind_int(1, id) == SQLITE_OK) then + + if(stmt%step() == SQLITE_ROW) then + call stmt%column_text(0, name) + end if + + end if + end if + call stmt%finalize() + + end subroutine get_group_name_db + + function get_group_id_db(name) + implicit none + + character(*), intent(in)::name + integer::get_group_id_db + type(sqlite3_stmt)::stmt + + get_group_id_db = -1 + + if(stmt%prepare(db, "SELECT id FROM groups WHERE name=?") == SQLITE_OK) then + if(stmt%bind_text(1, name) == SQLITE_OK) then + + if(stmt%step() == SQLITE_ROW) then + get_group_id_db = stmt%column_int(0) + end if + + end if + end if + call stmt%finalize() + + end function get_group_id_db + + subroutine delete_group_db(id) + implicit none + + integer, intent(in)::id + type(sqlite3_stmt)::stmt + + if(stmt%prepare(db, "DELETE FROM groups WHERE id=?") == SQLITE_OK) then + if(stmt%bind_int(1, id) == SQLITE_OK) then + + call stmt%step_now() + + end if + end if + call stmt%finalize() + + end subroutine delete_group_db + + function get_group_count_db() + implicit none + + integer::get_group_count_db + type(sqlite3_stmt)::stmt + + get_group_count_db = 0 + + if(stmt%prepare(db, "SELECT COUNT(*) FROM groups") == SQLITE_OK) then + if(stmt%step() == SQLITE_ROW) then + get_group_count_db = stmt%column_int(0) + end if + end if + call stmt%finalize() + + end function get_group_count_db + + function get_groups_db() result(res) + implicit none + + integer, dimension(:), pointer::res + type(sqlite3_stmt)::stmt + integer::i, n + + res => null() + n = get_group_count_db() + + if(n > 0) then + + allocate(res(n)) + res = -1 + + if(stmt%prepare(db, "SELECT id FROM groups") == SQLITE_OK) then + i = 0 + do while(stmt%step() == SQLITE_ROW .AND. i <= n) + i = i + 1 + res(i) = stmt%column_int(0) + end do + end if + call stmt%finalize() + + end if + + end function get_groups_db + + function is_entry_in_group_db(group, instruction, player) + implicit none + + integer, intent(in)::group, instruction, player + type(sqlite3_stmt)::stmt + logical::is_entry_in_group_db + + is_entry_in_group_db = .FALSE. + + if(stmt%prepare(db, & + "SELECT COUNT(*) FROM group_instructions WHERE group_id=? AND instruction=? AND player=?") == SQLITE_OK) & + then + if(stmt%bind_int(1, group) == SQLITE_OK .AND. & + stmt%bind_int(2, instruction) == SQLITE_OK .AND. & + stmt%bind_int(3, player) == SQLITE_OK) & + then + + if(stmt%step() == SQLITE_ROW) then + is_entry_in_group_db = (stmt%column_int(0) > 0) + end if + + end if + end if + call stmt%finalize() + + end function is_entry_in_group_db + + subroutine add_entry_to_group_db(group, instruction, player) + implicit none + + integer, intent(in)::group, instruction, player + type(sqlite3_stmt)::stmt + + if(instruction >= 0 .AND. player >= 0 .AND. group >= 0 .AND. & + .NOT. is_entry_in_group_db(group, instruction, player)) & + then + + if(stmt%prepare(db, "INSERT INTO group_instructions(group_id, instruction, player) VALUES(?, ?, ?)") == SQLITE_OK) then + if(stmt%bind_int(1, group) == SQLITE_OK .AND. & + stmt%bind_int(2, instruction) == SQLITE_OK .AND. & + stmt%bind_int(3, player) == SQLITE_OK) & + then + + call stmt%step_now() + + end if + end if + call stmt%finalize() + + end if + + end subroutine add_entry_to_group_db + + subroutine remove_entry_from_group_db(group, instruction, player) + implicit none + + integer, intent(in)::group, instruction, player + type(sqlite3_stmt)::stmt + + if(stmt%prepare(db, "DELETE FROM group_instructions WHERE group_id=? AND instruction=? AND player=?") == SQLITE_OK) then + if(stmt%bind_int(1, group) == SQLITE_OK .AND. & + stmt%bind_int(2, instruction) == SQLITE_OK .AND. & + stmt%bind_int(3, player) == SQLITE_OK) & + then + + call stmt%step_now() + + end if + end if + call stmt%finalize() + + end subroutine remove_entry_from_group_db + + function get_group_entries_count_db(group) + implicit none + + integer, intent(in)::group + integer::get_group_entries_count_db + type(sqlite3_stmt)::stmt + + get_group_entries_count_db = 0 + + if(stmt%prepare(db, "SELECT COUNT(*) FROM group_instructions WHERE group_id=?") == SQLITE_OK) then + if(stmt%bind_int(1, group) == SQLITE_OK) then + + if(stmt%step() == SQLITE_ROW) then + get_group_entries_count_db = stmt%column_int(0) + end if + + end if + end if + call stmt%finalize() + + end function get_group_entries_count_db + + function get_group_entries_db(group) result(res) + implicit none + + integer, intent(in)::group + type(group_entry), dimension(:), pointer::res + type(sqlite3_stmt)::stmt + integer::i, n + + res => null() + + n = get_group_entries_count_db(group) + if(n > 0) then + + allocate(res(n)) + res%instruction = -1 + res%player = -1 + res%group = group + + if(stmt%prepare(db, "SELECT instruction, player FROM group_instructions WHERE group_id=?") == SQLITE_OK) then + if(stmt%bind_int(1, group) == SQLITE_OK) then + + i = 0 + do while(stmt%step() == SQLITE_ROW .and. i <= n) + i = i + 1 + res(i)%instruction = stmt%column_int(0) + res(i)%player = stmt%column_int(1) + end do + + end if + end if + call stmt%finalize() + + end if + + end function get_group_entries_db + + function get_available_count_db() + implicit none + + integer::get_available_count_db + type(sqlite3_stmt)::stmt + + get_available_count_db = 0 + + if(stmt%prepare(db, "SELECT COUNT(*) FROM available") == SQLITE_OK) then + if(stmt%step() == SQLITE_ROW) then + get_available_count_db = stmt%column_int(0) + end if + end if + call stmt%finalize() + + end function get_available_count_db + + function get_available_work_pairs_db() result(res) + implicit none + + type(work_pair), dimension(:), pointer::res + integer::n, i + type(sqlite3_stmt)::stmt + + n = get_available_count_db() + res => null() + + if(n > 0) then + allocate(res(n)) + res%instruction = -1 + res%player = -1 + + if(stmt%prepare(db, "SELECT instruction, player FROM available") == SQLITE_OK) then + i = 1 + do while(stmt%step() == SQLITE_ROW .AND. i <= n) + res(i)%instruction = stmt%column_int(0) + res(i)%player = stmt%column_int(1) + i = i + 1 + end do + end if + call stmt%finalize() + + end if + + end function get_available_work_pairs_db + end module captain_db diff --git a/captain/external.f90 b/captain/external.f90 index 2a0bc23..edbe997 100644 --- a/captain/external.f90 +++ b/captain/external.f90 @@ -408,7 +408,7 @@ contains character(len=3*PLAYER_NAME_LENGTH)::one_player - n = get_instuctions_count() + n = get_instructions_count() if(n == 0) then diff --git a/captain/launch.f90 b/captain/launch.f90 index c397143..e889ccc 100644 --- a/captain/launch.f90 +++ b/captain/launch.f90 @@ -38,5 +38,25 @@ contains end subroutine launch_instructions_on_player + subroutine launch_group(group) + use captain_db + implicit none + + integer, intent(in)::group + type(group_entry), dimension(:), pointer::work + integer::i + + work => get_group_entries_db(group) + if(associated(work)) then + + do i=1, size(work) + + call add_new_job(work(i)%instruction, work(i)%player) + + end do + + end if + + end subroutine launch_group end module remote_launch \ No newline at end of file diff --git a/captain/levitating-captain.prj b/captain/levitating-captain.prj index 4501070..392ac52 100644 --- a/captain/levitating-captain.prj +++ b/captain/levitating-captain.prj @@ -146,7 +146,7 @@ "Target":"levitating-captain", "Fortran Options":{ "Use C Preprocessor":"false", - "Runtime Diagnostics":"false", + "Runtime Diagnostics":"true", "Floating Point Exception Trap":0, "Cray Pointers":"false", "Enable Coarrays":"false", diff --git a/captain/queryutils.f90 b/captain/queryutils.f90 index 6faec80..4f3d805 100644 --- a/captain/queryutils.f90 +++ b/captain/queryutils.f90 @@ -56,20 +56,23 @@ implicit none contains subroutine query_component_parse(self, comptext) + use logging implicit none class(query_component), intent(out)::self character(*), intent(in)::comptext - character(len=:), allocatable::decoded + character(len=:), pointer::decoded integer::i_in, i_out, i_equals, chnum allocate(character(len=len_trim(comptext)) :: decoded) + decoded = " " i_equals = 0 i_out = 1 i_in = 1 do while(i_in <= len_trim(comptext)) + if(comptext(i_in:i_in) /= '%') then decoded(i_out:i_out) = comptext(i_in:i_in) if(comptext(i_in:i_in) == '=') then @@ -207,6 +210,8 @@ contains integer, intent(in)::i character(len=:), pointer::res + res => null() + if(i <= self%component_count()) then res => self%components(i)%value end if @@ -222,6 +227,8 @@ contains integer::i + res => null() + do i = 1, self%component_count() if(self%components(i)%has_key()) then if(self%components(i)%key == trim(k)) then diff --git a/captain/sql/create.sql b/captain/sql/create.sql index c09d120..6457296 100644 --- a/captain/sql/create.sql +++ b/captain/sql/create.sql @@ -9,3 +9,6 @@ CREATE TABLE instructions(id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL); CREATE TABLE available(instruction INTEGER, player INTEGER, FOREIGN KEY(instruction) REFERENCES instructions(id), FOREIGN KEY(player) REFERENCES players(id), UNIQUE(instruction, player)); +CREATE TABLE groups(id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL); + +CREATE TABLE group_instructions(group_id INTEGER, instruction INTEGER, player INTEGER, FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE, FOREIGN KEY(instruction) REFERENCES instructions(id) ON DELETE CASCADE, FOREIGN KEY(player) REFERENCES players(id) ON DELETE CASCADE); diff --git a/captain/template.f90 b/captain/template.f90 index ad4ea19..3ae3dae 100644 --- a/captain/template.f90 +++ b/captain/template.f90 @@ -281,7 +281,7 @@ contains call self%variables(i)%assign(name, value) - call write_log(name//"=|||"//trim(value)//"|||", LOG_INFO) + !call write_log(name//"=|||"//trim(value)//"|||", LOG_INFO) end subroutine template_assign_string diff --git a/captain/templates/home.html b/captain/templates/home.html index a804d62..330fb0e 100644 --- a/captain/templates/home.html +++ b/captain/templates/home.html @@ -9,5 +9,6 @@
  • Jobs - View pending and completed jobs along with results
  • Players - Manage players, including adding new players to the team
  • Instructions - Launch certain instruction sets or assign instructions to players
  • +
  • Groups - Manage or launch a group of instructions, all at once
  • About - Learn more about this service
  • diff --git a/captain/templates/index.html b/captain/templates/index.html index ae87e72..5d90ad4 100644 --- a/captain/templates/index.html +++ b/captain/templates/index.html @@ -16,6 +16,7 @@
  • Jobs
  • Players
  • Instructions
  • +
  • Groups
  • About
  • diff --git a/captain/web.f90 b/captain/web.f90 index ccd3df0..9254658 100644 --- a/captain/web.f90 +++ b/captain/web.f90 @@ -253,7 +253,7 @@ contains character(len=:), pointer::one_player, scanlink - n = get_instuctions_count() + n = get_instructions_count() if(n == 0) then @@ -291,6 +291,227 @@ contains deallocate(scanlink) end function generate_instructions_html + + function generate_groups_html(req) result(res) + use captain_db + use server_response, only:request + implicit none + + type(request)::req + character(len=:), pointer::res + integer, dimension(:), pointer::groups + character(128)::one_group + integer::n, i + + character(len=:), pointer::one_link + + n = get_group_count_db() + + if(n == 0) then + + allocate(character(len=1024) :: res) + res = "None Yet" + + else + + allocate(character(len=(n*(256+64) + 384)) :: res) + + res = "

    Groups

    "//new_line(' ')//"" + deallocate(groups) + + end if + + res = trim(res)//new_line(' ')//"

    Management

    " + + res = trim(res)//new_line(' ')// & + '
    '// & + '
    ' + + end function generate_groups_html + + function generate_one_group_html(req) result(res) + use captain_db + use server_response, only:request + use request_utils + use query_utilities + use logging + use remote_launch, only: launch_group + implicit none + + type(request)::req + character(len=:), pointer::res + + type(query)::q + + character(128)::group_name, instruction_name, player_name + integer::id + + type(group_entry), dimension(:), pointer::entries + type(work_pair), dimension(:), pointer::all_entries + character(len=:), pointer::one_link, delete_link, play_link, qreq + + character(128)::launch_msg + + integer::i, j, n_instructions_group, n_instructions_total + + call req%path_component(2, group_name) + i = index(group_name, ".html") + group_name(i:128) = ' ' + + id = get_group_id_db(trim(group_name)) + entries => null() + all_entries => null() + + launch_msg = " " + + if(associated(req%query_string)) then + + call q%init(req%query_string) + qreq => q%get_value("add") + + if(associated(qreq)) then + + call write_log("ADD: "//trim(qreq)) + + i = index(qreq, ',') + player_name = qreq(i+1:len_trim(qreq)) + instruction_name = qreq(1:i-1) + + i = get_instruction_id(trim(instruction_name)) + j = get_player_id(trim(player_name)) + + call add_entry_to_group_db(id, i, j) + + deallocate(qreq) + + else + + qreq => q%get_value("delete") + + if(associated(qreq)) then + + i = index(qreq, ',') + player_name = qreq(i+1:len(qreq)) + instruction_name = qreq(1:i-1) + + i = get_instruction_id(trim(instruction_name)) + j = get_player_id(trim(player_name)) + + call remove_entry_from_group_db(id, i, j) + + deallocate(qreq) + + else if(trim(req%query_string) == "launch") then + + call launch_group(id) + write(launch_msg, '(I4, 1X, A13)') get_group_entries_count_db(id), "jobs launched" + + else if(trim(req%query_string) == "destroy") then + + call delete_group_db(id) + + end if + + end if + + call q%destroy() + + end if + + n_instructions_group = get_group_entries_count_db(id) + n_instructions_total = get_available_count_db() + + allocate(character( len=(n_instructions_total*384 + 512) ) :: res) + + res = "

    "//trim(group_name)//"

    " + + if(n_instructions_group == 0) then + + res = trim(res)//new_line(' ')//"

    Contains no instructions.

    " + + else + + if(len_trim(launch_msg) > 0) then + res = trim(res)//new_line(' ')//'

    '//trim(launch_msg)//'

    ' + else + res = trim(res)//new_line(' ')//'

    🚀 Launch Now

    ' + end if + + res = trim(res)//new_line(' ')//"

    Work to Be Performed

    "//new_line(' ')//"" + + end if + + if(n_instructions_total > 0) then + + res = trim(res)//new_line(' ')//"

    Add Instructions

    " + + all_entries => get_available_work_pairs_db() + if(associated(all_entries)) then + + res = trim(res)//new_line(' ')//'
    '// & + ''//new_line(' ')// & + ''//'
    ' + end if + end if + + res = trim(res)//new_line(' ')//'

    Destroy This Group

    '//new_line(' ')// & + '

    💣 Destroy

    '//new_line(' ')// & + '

    This operation will not destroy any instructions

    ' + + end function generate_one_group_html function generate_players_html() result(res) use captain_db @@ -694,7 +915,19 @@ contains call page%assign('title', trim(job_page_title)) contents => generate_one_job_html(req) call page%assign('contents', contents) - + + else if(trim(req%location) == "/groups.html") then + + call page%assign('title', 'Instruction Groups') + contents => generate_groups_html(req) + call page%assign('contents', contents) + + else if(trim(first) == "groups") then + + call page%assign('title', 'Instruction Group') + contents => generate_one_group_html(req) + call page%assign('contents', contents) + else call page%assign('title', 'Not Found') @@ -716,7 +949,7 @@ contains end function request_templated function handle_post(req) result(resp) - use captain_db, only: add_player_db + use captain_db, only: add_player_db, add_group_db use page_template use config, only: template_filepath use logging @@ -757,6 +990,18 @@ contains end if + else if(trim(category) == "groups") then + + call req%path_component(2, second) + + ! Add a group + if(trim(second) == "add.html") then + + call add_group_db(posted%get_value("name")) + call page%assign('destination', 'groups.html') + + end if + end if ! Handle the template -- cgit v1.2.3