Files
Bubberstation/code/datums/json_savefile.dm
Zephyr fbec9c14e9 JSON Logging Take Two (#73604)
## About The Pull Request

Converts all logging, excluding perf and investigate, to json.
I focused on making the system as easy to use and as easy to add new
categories as possible.

Due to issues related to logging to world at global creation logger is
now a byond real, which is created directly before Master

Log categories support versioning, secret flagging, and sub-category
filtering. Although all of this is entirely optional for coders.
If you ever want to add a new category and use it, all you need to do is
make the barebones category datum and the define.
I've kept existing procs such as log_game, and simply turned them into a
wrapper for Logger.Log(xxx, ...)
## Why It's Good For The Game

Makes processing and filtering logs much easier in the future, while
only minimally downgrading log crawling experience.
I am also working on a log viewer frontend for admin usage however that
will take a little bit longer to finish up.
Also makes special logging and data tracking much easier thanks to a
data list processing implementation and handling
## Changelog
🆑
server: All logs are now formatted in json, excluding perf and
investigations
/🆑

---------

Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: tattle <66640614+dragomagol@users.noreply.github.com>
Co-authored-by: Kyle Spier-Swenson <kyleshome@gmail.com>
Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
2023-05-22 14:51:00 +12:00

127 lines
4.9 KiB
Plaintext

/**
* A savefile implementation that handles all data using json.
* Also saves it using JSON too, fancy.
* If you pass in a null path, it simply acts as a memory tree instead, and cannot be saved.
*/
/datum/json_savefile
var/path = ""
VAR_PRIVATE/list/tree
/// If this is set to true, calling set_entry or remove_entry will automatically call save(), this does not catch modifying a sub-tree, nor do I know how to do that
var/auto_save = FALSE
/// Cooldown that tracks the time between attempts to download the savefile.
COOLDOWN_DECLARE(download_cooldown)
GENERAL_PROTECT_DATUM(/datum/json_savefile)
/datum/json_savefile/New(path)
src.path = path
tree = list()
if(path && fexists(path))
load()
/**
* Gets an entry from the json tree, with an optional default value.
* If no key is specified it throws the entire tree at you instead
*/
/datum/json_savefile/proc/get_entry(key, default_value)
if(!key)
return tree
return (key in tree) ? tree[key] : default_value
/// Sets an entry in the tree to the given value
/datum/json_savefile/proc/set_entry(key, value)
tree[key] = value
if(auto_save)
save()
/// Removes the given key from the tree
/datum/json_savefile/proc/remove_entry(key)
if(key)
tree -= key
if(auto_save)
save()
/// Wipes the entire tree
/datum/json_savefile/proc/wipe()
tree?.Cut()
/datum/json_savefile/proc/load()
if(!path || !fexists(path))
return FALSE
try
tree = json_decode(rustg_file_read(path))
return TRUE
catch(var/exception/err)
stack_trace("failed to load json savefile at '[path]': [err]")
return FALSE
/datum/json_savefile/proc/save()
if(path)
rustg_file_write(json_encode(tree), path)
/datum/json_savefile/serialize_list(list/options, list/semvers)
SHOULD_CALL_PARENT(FALSE)
SET_SERIALIZATION_SEMVER(semvers, "1.0.0")
return tree.Copy()
/// Traverses the entire dir tree of the given savefile and dynamically assembles the tree from it
/datum/json_savefile/proc/import_byond_savefile(savefile/savefile)
tree.Cut()
var/list/dirs_to_go = list("/" = tree)
while(length(dirs_to_go))
var/dir = dirs_to_go[1]
var/list/region = dirs_to_go[dir]
dirs_to_go.Cut(1, 2)
savefile.cd = dir
for(var/entry in savefile.dir)
var/entry_value
savefile.cd = "[dir]/[entry]"
//eof refers to the path you are cd'ed into, not the savefile as a whole. being false right after cding into an entry means this entry has no buffer, which only happens with nested save file directories
if (savefile.eof)
region[entry] = list()
dirs_to_go["[dir]/[entry]"] = region[entry]
continue
READ_FILE(savefile, entry_value) //we are cd'ed to the entry, so we don't need to specify a path to read from
region[entry] = entry_value
/// Proc that handles generating a JSON file (prettified if 515 and over!) of a user's preferences and showing it to them.
/// Requester is passed in to the ftp() and tgui_alert() procs, and account_name is just used to generate the filename.
/// We don't _need_ to pass in account_name since this is reliant on the json_savefile datum already knowing what we correspond to, but it's here to help people keep track of their stuff.
/datum/json_savefile/proc/export_json_to_client(mob/requester, account_name)
if(!istype(requester) || !path)
return
if(!json_export_checks(requester))
return
COOLDOWN_START(src, download_cooldown, (CONFIG_GET(number/seconds_cooldown_for_preferences_export) * (1 SECONDS)))
var/file_name = "[account_name ? "[account_name]_" : ""]preferences_[time2text(world.timeofday, "MMM_DD_YYYY_hh-mm-ss")].json"
var/temporary_file_storage = "data/preferences_export_working_directory/[file_name]"
#if DM_VERSION >= 515
if(!text2file(json_encode(tree, JSON_PRETTY_PRINT), temporary_file_storage))
tgui_alert(requester, "Failed to export preferences to JSON! You might need to try again later.", "Export Preferences JSON")
return
#else
if(!text2file(json_encode(tree), temporary_file_storage))
tgui_alert(requester, "Failed to export preferences to JSON! You might need to try again later.", "Export Preferences JSON")
return
#endif
var/exportable_json = file(temporary_file_storage)
DIRECT_OUTPUT(requester, ftp(exportable_json, file_name))
fdel(temporary_file_storage)
/// Proc that just handles all of the checks for exporting a preferences file, returns TRUE if all checks are passed, FALSE otherwise.
/// Just done like this to make the code in the export_json_to_client() proc a bit cleaner.
/datum/json_savefile/proc/json_export_checks(mob/requester)
if(!COOLDOWN_FINISHED(src, download_cooldown))
tgui_alert(requester, "You must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, download_cooldown))] before exporting your preferences again!", "Export Preferences JSON")
return FALSE
if(tgui_alert(requester, "Are you sure you want to export your preferences as a JSON file? This will save to a file on your computer.", "Export Preferences JSON", list("Cancel", "Yes")) == "Yes")
return TRUE
return FALSE