Skip to content

Commit

Permalink
[MIRROR] Refactored admin backup saving. No longer at round end, more…
Browse files Browse the repository at this point in the history
… data backed up (#1758) (#2708)

* Refactored admin backup saving. No longer at round end, more data backed up (#81891)

Admin verified connections now cache all verified connections for all
admins. (Rather then just the last connection data of the currently
connected admins)

Sync with the db now happens at admin load time, not at round end. (this
was causing annoyances because servers with long rounds could override
the admin db with old/stale data overwritting the fresher data that was
written by a server with a shorter round)

Fix backup verification not working if the db thinks it still connected
but its not actually still connected.

@Mothblocks @Jordie0608

---------



* Refactored admin backup saving. No longer at round end, more data backed up

---------

Co-authored-by: NovaBot <[email protected]>
Co-authored-by: Kyle Spier-Swenson <[email protected]>
Co-authored-by: Zephyr <[email protected]>
  • Loading branch information
4 people authored Apr 3, 2024
1 parent 43dc067 commit 839c717
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 116 deletions.
80 changes: 0 additions & 80 deletions code/__HELPERS/roundend.dm
Original file line number Diff line number Diff line change
Expand Up @@ -742,86 +742,6 @@ GLOBAL_LIST_INIT(achievements_unlocked, list())
count++
return objective_parts.Join("<br>")

/datum/controller/subsystem/ticker/proc/save_admin_data()
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return
if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save
return
else if(load_admins(TRUE)) //returns true if there was a database failure and the backup was loaded from
return
sync_ranks_with_db()
var/list/sql_admins = list()
for(var/i in GLOB.protected_admins)
var/datum/admins/A = GLOB.protected_admins[i]
sql_admins += list(list("ckey" = A.target, "rank" = A.rank_names()))
SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
query_admin_rank_update.Execute()
qdel(query_admin_rank_update)

//json format backup file generation stored per server
var/json_file = file("data/admins_backup.json")
var/list/file_data = list(
"ranks" = list(),
"admins" = list(),
"connections" = list(),
)
for(var/datum/admin_rank/R in GLOB.admin_ranks)
file_data["ranks"]["[R.name]"] = list()
file_data["ranks"]["[R.name]"]["include rights"] = R.include_rights
file_data["ranks"]["[R.name]"]["exclude rights"] = R.exclude_rights
file_data["ranks"]["[R.name]"]["can edit rights"] = R.can_edit_rights

for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins)
var/datum/admins/admin = GLOB.admin_datums[admin_ckey]

if(!admin)
admin = GLOB.deadmins[admin_ckey]
if (!admin)
continue

file_data["admins"][admin_ckey] = admin.rank_names()

if (admin.owner)
file_data["connections"][admin_ckey] = list(
"cid" = admin.owner.computer_id,
"ip" = admin.owner.address,
)

fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))

/datum/controller/subsystem/ticker/proc/update_everything_flag_in_db()
for(var/datum/admin_rank/R in GLOB.admin_ranks)
var/list/flags = list()
if(R.include_rights == R_EVERYTHING)
flags += "flags"
if(R.exclude_rights == R_EVERYTHING)
flags += "exclude_flags"
if(R.can_edit_rights == R_EVERYTHING)
flags += "can_edit_flags"
if(!flags.len)
continue
var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]"
var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery(
"SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])",
list("rank" = R.name)
)
if(!query_check_everything_ranks.Execute())
qdel(query_check_everything_ranks)
return
if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value
var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]"
var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery(
"UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank",
list("rank" = R.name)
)
if(!query_update_everything_ranks.Execute())
qdel(query_update_everything_ranks)
return
qdel(query_update_everything_ranks)
qdel(query_check_everything_ranks)

/datum/controller/subsystem/ticker/proc/cheevo_report()
var/list/parts = list()
Expand Down
2 changes: 0 additions & 2 deletions code/controllers/subsystem/ticker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -765,8 +765,6 @@ SUBSYSTEM_DEF(ticker)

/datum/controller/subsystem/ticker/Shutdown()
gather_newscaster() //called here so we ensure the log is created even upon admin reboot
save_admin_data()
update_everything_flag_in_db()
if(!round_end_sound)
round_end_sound = choose_round_end_song()
///The reference to the end of round sound that we have chosen.
Expand Down
137 changes: 116 additions & 21 deletions code/modules/admin/admin_ranks.dm
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,8 @@ GLOBAL_PROTECT(protected_ranks)
if(3)
can_edit_rights |= flag

/proc/sync_ranks_with_db()
set waitfor = FALSE

if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>", confidential = TRUE)
return

var/list/sql_ranks = list()
for(var/datum/admin_rank/R in GLOB.protected_ranks)
sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights))
SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)

//load our rank - > rights associations
/// Loads admin ranks.
/// Return a list containing the backup data if they were loaded from the database backup json
/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Reload blocked: Advanced ProcCall detected.</span>", confidential = TRUE)
Expand All @@ -137,7 +126,7 @@ GLOBAL_PROTECT(protected_ranks)
GLOB.admin_ranks += R
GLOB.protected_ranks += R
previous_rank = R
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail)
if(CONFIG_GET(flag/load_legacy_ranks_only))
if(!no_update)
sync_ranks_with_db()
Expand All @@ -146,7 +135,7 @@ GLOBAL_PROTECT(protected_ranks)
if(!query_load_admin_ranks.Execute())
message_admins("Error loading admin ranks from database. Loading from backup.")
log_sql("Error loading admin ranks from database. Loading from backup.")
dbfail = 1
dbfail = TRUE
else
while(query_load_admin_ranks.NextRow())
var/skip
Expand Down Expand Up @@ -220,12 +209,14 @@ GLOBAL_PROTECT(protected_ranks)

return jointext(names, "+")

/// (Re)Loads the admin list.
/// returns TRUE if database admins had to be loaded from the backup json
/proc/load_admins(no_update)
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
log_sql("Failed to connect to database while loading admins. Loading from backup.")
dbfail = 1
dbfail = TRUE
//clear the datums references
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
Expand All @@ -251,7 +242,7 @@ GLOBAL_PROTECT(protected_ranks)
var/admin_rank = admins_regex.group[2]
new /datum/admins(ranks_from_rank_name(admin_rank), ckey(admin_key), force_active = FALSE, protected = TRUE)

if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
if(!CONFIG_GET(flag/admin_legacy_system) && !dbfail)
var/datum/db_query/query_load_admins = SSdbcore.NewQuery("SELECT ckey, `rank`, feedback FROM [format_table_name("admin")] ORDER BY `rank`")
if(!query_load_admins.Execute())
message_admins("Error loading admins from database. Loading from backup.")
Expand All @@ -275,6 +266,9 @@ GLOBAL_PROTECT(protected_ranks)
var/datum/admins/admin_holder = new(admin_ranks, admin_ckey)
admin_holder.cached_feedback_link = admin_feedback || NO_FEEDBACK_LINK
qdel(query_load_admins)
if (!no_update)
save_admin_backup()
sync_admins_with_db()
//load admins from backup file
if(dbfail)
if(!backup_file_json)
Expand All @@ -286,14 +280,15 @@ GLOBAL_PROTECT(protected_ranks)
log_world("Unable to locate admins backup file.")
return
backup_file_json = json_decode(backup_file)
for(var/J in backup_file_json["admins"])
for(var/backup_admin_ckey in backup_file_json["admins"])
var/skip
for(var/A in GLOB.admin_datums + GLOB.deadmins)
if(A == "[J]") //this admin was already loaded from txt override
for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins)
if(ckey(admin_ckey) == ckey("[backup_admin_ckey]")) //this admin was already loaded from txt override
skip = TRUE
break
if(skip)
continue
new /datum/admins(ranks_from_rank_name(backup_file_json["admins"]["[J]"]), ckey("[J]"))
new /datum/admins(ranks_from_rank_name(backup_file_json["admins"]["[backup_admin_ckey]"]), ckey("[backup_admin_ckey]"))
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
Expand All @@ -302,3 +297,103 @@ GLOBAL_PROTECT(protected_ranks)
testing(msg)
#endif
return dbfail


/proc/sync_ranks_with_db()
set waitfor = FALSE

if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>", confidential = TRUE)
return

var/list/sql_ranks = list()
for(var/datum/admin_rank/R as anything in GLOB.protected_ranks)
sql_ranks += list(list("rank" = R.name, "flags" = R.include_rights, "exclude_flags" = R.exclude_rights, "can_edit_flags" = R.can_edit_rights))
SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
update_everything_flag_in_db()


/proc/update_everything_flag_in_db()
for(var/datum/admin_rank/R as anything in GLOB.admin_ranks)
var/list/flags = list()
if(R.include_rights == R_EVERYTHING)
flags += "flags"
if(R.exclude_rights == R_EVERYTHING)
flags += "exclude_flags"
if(R.can_edit_rights == R_EVERYTHING)
flags += "can_edit_flags"
if(!flags.len)
continue
var/flags_to_check = flags.Join(" != [R_EVERYTHING] AND ") + " != [R_EVERYTHING]"
var/datum/db_query/query_check_everything_ranks = SSdbcore.NewQuery(
"SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = :rank AND ([flags_to_check])",
list("rank" = R.name)
)
if(!query_check_everything_ranks.Execute())
qdel(query_check_everything_ranks)
return
if(query_check_everything_ranks.NextRow()) //no row is returned if the rank already has the correct flag value
var/flags_to_update = flags.Join(" = [R_EVERYTHING], ") + " = [R_EVERYTHING]"
var/datum/db_query/query_update_everything_ranks = SSdbcore.NewQuery(
"UPDATE [format_table_name("admin_ranks")] SET [flags_to_update] WHERE rank = :rank",
list("rank" = R.name)
)
if(!query_update_everything_ranks.Execute())
qdel(query_update_everything_ranks)
return
qdel(query_update_everything_ranks)
qdel(query_check_everything_ranks)


/proc/sync_admins_with_db()
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return

if(CONFIG_GET(flag/admin_legacy_system) || !SSdbcore.IsConnected()) //we're already using legacy system so there's nothing to save
return
sync_ranks_with_db()
var/list/sql_admins = list()
for(var/holder_ckey in GLOB.protected_admins)
var/datum/admins/holder = GLOB.protected_admins[holder_ckey]
sql_admins += list(list("ckey" = holder.target, "rank" = holder.rank_names()))
SSdbcore.MassInsert(format_table_name("admin"), sql_admins, duplicate_key = TRUE)
var/datum/db_query/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] AS p INNER JOIN [format_table_name("admin")] AS a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
query_admin_rank_update.Execute()
qdel(query_admin_rank_update)


/proc/save_admin_backup()
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return

if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save
return

//json format backup file generation stored per server
var/json_file = file("data/admins_backup.json")
var/list/file_data = list(
"ranks" = list(),
"admins" = list()
)
for(var/datum/admin_rank/R as anything in GLOB.admin_ranks)
file_data["ranks"]["[R.name]"] = list()
file_data["ranks"]["[R.name]"]["include rights"] = R.include_rights
file_data["ranks"]["[R.name]"]["exclude rights"] = R.exclude_rights
file_data["ranks"]["[R.name]"]["can edit rights"] = R.can_edit_rights

for(var/admin_ckey in GLOB.admin_datums + GLOB.deadmins)
var/datum/admins/admin = GLOB.admin_datums[admin_ckey]

if(!admin)
admin = GLOB.deadmins[admin_ckey]
if (!admin)
continue

file_data["admins"][admin_ckey] = admin.rank_names()

admin.backup_connections()

fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data, JSON_PRETTY_PRINT))
Loading

0 comments on commit 839c717

Please sign in to comment.