|
| 1 | +/** |
| 2 | + * A savefile implementation that handles all data using json. |
| 3 | + * Also saves it using JSON too, fancy. |
| 4 | + * If you pass in a null path, it simply acts as a memory tree instead, and cannot be saved. |
| 5 | + */ |
| 6 | +/datum/json_savefile |
| 7 | + var/path = "" |
| 8 | + VAR_PRIVATE/list/tree |
| 9 | + /// 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 |
| 10 | + var/auto_save = FALSE |
| 11 | + /// Cooldown that tracks the time between attempts to download the savefile. |
| 12 | + COOLDOWN_DECLARE(download_cooldown) |
| 13 | + |
| 14 | +GENERAL_PROTECT_DATUM(/datum/json_savefile) |
| 15 | + |
| 16 | +/datum/json_savefile/New(path) |
| 17 | + src.path = path |
| 18 | + tree = list() |
| 19 | + if(path && fexists(path)) |
| 20 | + load() |
| 21 | + |
| 22 | +/** |
| 23 | + * Gets an entry from the json tree, with an optional default value. |
| 24 | + * If no key is specified it throws the entire tree at you instead |
| 25 | + */ |
| 26 | +/datum/json_savefile/proc/get_entry(key, default_value) |
| 27 | + if(!key) |
| 28 | + return tree |
| 29 | + return (key in tree) ? tree[key] : default_value |
| 30 | + |
| 31 | +/// Sets an entry in the tree to the given value |
| 32 | +/datum/json_savefile/proc/set_entry(key, value) |
| 33 | + tree[key] = value |
| 34 | + if(auto_save) |
| 35 | + save() |
| 36 | + |
| 37 | +/// Removes the given key from the tree |
| 38 | +/datum/json_savefile/proc/remove_entry(key) |
| 39 | + if(key) |
| 40 | + tree -= key |
| 41 | + if(auto_save) |
| 42 | + save() |
| 43 | + |
| 44 | +/// Wipes the entire tree |
| 45 | +/datum/json_savefile/proc/wipe() |
| 46 | + tree?.Cut() |
| 47 | + |
| 48 | +/datum/json_savefile/proc/load() |
| 49 | + if(!path || !fexists(path)) |
| 50 | + return FALSE |
| 51 | + try |
| 52 | + tree = json_decode(rustg_file_read(path)) |
| 53 | + return TRUE |
| 54 | + catch(var/exception/err) |
| 55 | + stack_trace("failed to load json savefile at '[path]': [err]") |
| 56 | + return FALSE |
| 57 | + |
| 58 | +/datum/json_savefile/proc/save() |
| 59 | + if(path) |
| 60 | + rustg_file_write(json_encode(tree, JSON_PRETTY_PRINT), path) |
| 61 | + |
| 62 | +/datum/json_savefile/serialize_list(list/options, list/semvers) |
| 63 | + SHOULD_CALL_PARENT(FALSE) |
| 64 | + //SET_SERIALIZATION_SEMVER(semvers, "1.0.0") |
| 65 | + return tree.Copy() |
| 66 | + |
| 67 | +/// Traverses the entire dir tree of the given savefile and dynamically assembles the tree from it |
| 68 | +/datum/json_savefile/proc/import_byond_savefile(savefile/savefile) |
| 69 | + tree.Cut() |
| 70 | + var/list/dirs_to_go = list("/" = tree) |
| 71 | + while(length(dirs_to_go)) |
| 72 | + var/dir = dirs_to_go[1] |
| 73 | + var/list/region = dirs_to_go[dir] |
| 74 | + dirs_to_go.Cut(1, 2) |
| 75 | + savefile.cd = dir |
| 76 | + for(var/entry in savefile.dir) |
| 77 | + var/entry_value |
| 78 | + savefile.cd = "[dir]/[entry]" |
| 79 | + //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 |
| 80 | + if (savefile.eof) |
| 81 | + region[entry] = list() |
| 82 | + dirs_to_go["[dir]/[entry]"] = region[entry] |
| 83 | + continue |
| 84 | + READ_FILE(savefile, entry_value) //we are cd'ed to the entry, so we don't need to specify a path to read from |
| 85 | + region[entry] = entry_value |
| 86 | + |
| 87 | +/* no. not touching this right now. |
| 88 | +/// Proc that handles generating a JSON file (prettified if 515 and over!) of a user's preferences and showing it to them. |
| 89 | +/// Requester is passed in to the ftp() and tgui_alert() procs, and account_name is just used to generate the filename. |
| 90 | +/// 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. |
| 91 | +/datum/json_savefile/proc/export_json_to_client(mob/requester, account_name) |
| 92 | + if(!istype(requester) || !path) |
| 93 | + return |
| 94 | +
|
| 95 | + if(!json_export_checks(requester)) |
| 96 | + return |
| 97 | +
|
| 98 | + COOLDOWN_START(src, download_cooldown, (CONFIG_GET(number/seconds_cooldown_for_preferences_export) * (1 SECONDS))) |
| 99 | + var/file_name = "[account_name ? "[account_name]_" : ""]preferences_[time2text(world.timeofday, "MMM_DD_YYYY_hh-mm-ss")].json" |
| 100 | + var/temporary_file_storage = "data/preferences_export_working_directory/[file_name]" |
| 101 | +
|
| 102 | + if(!text2file(json_encode(tree, JSON_PRETTY_PRINT), temporary_file_storage)) |
| 103 | + tgui_alert(requester, "Failed to export preferences to JSON! You might need to try again later.", "Export Preferences JSON") |
| 104 | + return |
| 105 | +
|
| 106 | + var/exportable_json = file(temporary_file_storage) |
| 107 | +
|
| 108 | + DIRECT_OUTPUT(requester, ftp(exportable_json, file_name)) |
| 109 | + fdel(temporary_file_storage) |
| 110 | +
|
| 111 | +/// Proc that just handles all of the checks for exporting a preferences file, returns TRUE if all checks are passed, FALSE otherwise. |
| 112 | +/// Just done like this to make the code in the export_json_to_client() proc a bit cleaner. |
| 113 | +/datum/json_savefile/proc/json_export_checks(mob/requester) |
| 114 | + if(!COOLDOWN_FINISHED(src, download_cooldown)) |
| 115 | + tgui_alert(requester, "You must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, download_cooldown))] before exporting your preferences again!", "Export Preferences JSON") |
| 116 | + return FALSE |
| 117 | +
|
| 118 | + 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") |
| 119 | + return TRUE |
| 120 | +
|
| 121 | + return FALSE |
| 122 | +*/ |
0 commit comments