Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/__defines/serde.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@

#define FINALIZE_REAGENTS_SERDE(V) if(islist(V)) { FINALIZE_REAGENTS_SERDE_BODY(V); }
#define FINALIZE_REAGENTS_SERDE_AND_RETURN(V) if(islist(V)) { FINALIZE_REAGENTS_SERDE_BODY(V); return; }

#define BACKUP_TIMESTAMP "[time2text(REALTIMEOFDAY, "YY-MM-DD_hh-mm")].backup"
3 changes: 2 additions & 1 deletion code/__defines/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.

#define SS_INIT_INPUT 23
#define SS_INIT_INPUT 24
#define SS_INIT_VERY_EARLY 23
#define SS_INIT_EARLY 22
#define SS_INIT_WEBHOOKS 21
#define SS_INIT_MODPACKS 20
Expand Down
31 changes: 31 additions & 0 deletions code/_helpers/guid.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Marks a guid as unused for get_guid() to return later.
var/global/alist/_unused_guids_by_distinguisher = alist()
/proc/free_guid(_distinguisher, _guid)
var/list/unused_guids = global._unused_guids_by_distinguisher[_distinguisher]
if(islist(unused_guids))
unused_guids += _guid
else
global._unused_guids_by_distinguisher[_distinguisher] = list(_guid)

// Returns an unused guid.
var/global/alist/_guids_by_distinguisher = alist()
/proc/get_guid(_distinguisher)

// First time this has been used - set up our lists, return 1.
if(!global._guids_by_distinguisher[_distinguisher])
global._guids_by_distinguisher[_distinguisher] = 1
global._unused_guids_by_distinguisher[_distinguisher] = list()
return 1

// If we have unused guids, use one of those first. Otherwise just increment our GUID counter.
var/list/unused_guids = global._unused_guids_by_distinguisher[_distinguisher]
if(!length(unused_guids))
global._guids_by_distinguisher[_distinguisher] = global._guids_by_distinguisher[_distinguisher] + 1
return global._guids_by_distinguisher[_distinguisher]

. = unused_guids[1]
unused_guids.Cut(1, 2)

// Sets our last max guid, only really used after initial subsystem load.
/proc/set_guid(_distinguisher, _guid)
global._guids_by_distinguisher[_distinguisher] = _guid
138 changes: 138 additions & 0 deletions code/controllers/subsystems/approvals.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
SUBSYSTEM_DEF(approvals)
name = "Approvals"
flags = SS_NO_FIRE
init_order = SS_INIT_EARLY

var/const/approvals_path = "data/approvals.json" // This should definitely use a DB rather than json but heigh no
var/list/pending_approvals = list()
var/list/all_approvals = list()
var/alist/guid_to_approval = alist()

/datum/controller/subsystem/approvals/stat_entry()
..("P:[pending_approvals.len] A:[all_approvals.len]")

/datum/controller/subsystem/approvals/Initialize(start_timeofday)
..()
if(!fexists(approvals_path))
return

// Notes for future consideration
// - load approved + pending on first run, apply approvals to update whitelist or such
// - only keep pending approvals, discard everything else? or load everything so people can check their approvals?

// Should also consider integrating this more tightly with instantiate_serialized_data() but given datum handling
// this should be fine. It's not like approvals will be associated with DM refs or turfs or such I would assume.

try
var/max_guid = 0
for(var/list/approval_data in json_decode(file2text(approvals_path)))
var/create_type = approval_data[/datum::type]
var/datum/approval/approval = new create_type(approval_data)
all_approvals += approval
guid_to_approval[approval.guid] = approval
max_guid = max(max_guid, approval.guid)
if(approval.status <= /datum/approval::APPROVAL_SUBMITTED)
pending_approvals += approval
set_guid(type, max_guid)
for(var/i = 1 to max_guid)
if(!guid_to_approval[i])
free_guid(type, i)

catch(var/exception/E)
error("Exception when loading approvals file: [EXCEPTION_TEXT(E)]")

/datum/controller/subsystem/approvals/proc/store_approval(mob/_submitter, datum/approval/_approval)
_approval.submitter = _submitter.client?.ckey || "system"
_approval.guid = get_guid(type)
all_approvals += _approval
guid_to_approval[_approval.guid] = _approval.guid
if(_approval.status <= /datum/approval::APPROVAL_SUBMITTED)
pending_approvals |= _approval
_approval.on_creation(_submitter)

/datum/controller/subsystem/approvals/proc/save_approvals()

// Take a timestamped backup just in case.
if(fexists(approvals_path))
var/backup_path = "[approvals_path].[BACKUP_TIMESTAMP]"
if(!fcopy(approvals_path, backup_path))
log_error("Failed to back up approvals file [approvals_path]")
return

var/list/all_approval_data = list()
for(var/datum/approval/approval as anything in all_approvals)
all_approval_data += list(approval.Serialize())

try
var/write_data = json_encode(all_approval_data)
var/write_file = file(approvals_path)
to_file(write_file, write_data)

catch(var/exception/E)
log_error("Exception when saving approvals file: [EXCEPTION_TEXT(E)]")
return

/datum/approval
var/guid
var/name
var/submitter
var/approver
var/body
var/status = APPROVAL_CREATED

var/const/APPROVAL_CREATED = 0
var/const/APPROVAL_SUBMITTED = 1
var/const/APPROVAL_APPROVED = 2
var/const/APPROVAL_DENIED = 2

/datum/approval/proc/on_creation(mob/_submitter)
return

/datum/approval/New(list/_data)
if(islist(_data))
guid = _data[nameof(/datum/approval::guid)]
name = _data[nameof(/datum/approval::name)]
body = _data[nameof(/datum/approval::body)]
submitter = _data[nameof(/datum/approval::submitter)]
approver = _data[nameof(/datum/approval::approver)]
status = _data[nameof(/datum/approval::status)]
. = ..()

/datum/approval/Serialize()
. = ..()
.[nameof(/datum/approval::guid)] = guid
.[nameof(/datum/approval::name)] = name
.[nameof(/datum/approval::body)] = body
.[nameof(/datum/approval::submitter)] = submitter
.[nameof(/datum/approval::approver)] = approver
.[nameof(/datum/approval::status)] = status

/datum/approval/proc/on_approver_response(_approver, _approved = TRUE, _reason = "Unsupplied.")
approver = _approver
status = _approved ? APPROVAL_APPROVED : APPROVAL_DENIED
if(submitter)
for(var/client/client)
if(client.ckey == submitter)
to_chat(client, "Your submission #[guid] has been [status == APPROVAL_APPROVED ? "approved" : "denied"] by [approver] for reason: [_reason].")

// Do we need to serialize icons for these separately to uploaded icons, or should we
// immediately record it in the uploaded icon repo even if denied and delete it later?
// Approvals need to persist across restarts...
/datum/approval/player_icon
var/icon_guid
var/icon/icon

/datum/approval/player_icon/on_creation(mob/_submitter)
. = ..()
icon_guid = SSuploaded_icons.store_icon(_submitter?.ckey || "system", body, 0, icon)

/datum/approval/player_icon/Serialize()
. = ..()
.[nameof(/datum/approval/player_icon::icon_guid)] = icon_guid

/datum/approval/player_icon/on_approver_response(_approver, _approved = TRUE, _reason = "Unsupplied.")
. = ..()
if(status == APPROVAL_DENIED && icon_guid)
SSuploaded_icons.remove_icon(icon_guid)
icon = null
icon_guid = null
116 changes: 116 additions & 0 deletions code/controllers/subsystems/uploaded_icons.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
SUBSYSTEM_DEF(uploaded_icons)
name = "Uploaded Icons"
wait = 1 MINUTE
init_order = SS_INIT_VERY_EARLY

var/need_save = FALSE
var/alist/guid_to_icon = alist()
var/const/icons_path = "data/icons/"
var/const/icons_manifest = "icons.json"

/datum/controller/subsystem/uploaded_icons/stat_entry()
..("I:[guid_to_icon.len]")

/datum/controller/subsystem/uploaded_icons/fire(resumed)
save_icons()

/datum/controller/subsystem/uploaded_icons/Initialize(start_timeofday)
..()
try
var/max_guid = 0
var/manifest_path = "[icons_path][icons_manifest]"
if(fexists(manifest_path))
for(var/list/manifest_entry in json_decode(file2text(manifest_path)))
var/create_type = manifest_entry[/datum::type]
var/datum/uploaded_icon/uploaded_icon = new create_type(manifest_entry)
guid_to_icon[uploaded_icon.guid] = uploaded_icon
max_guid = max(max_guid, uploaded_icon.guid)
uploaded_icon.icon = icon(file("[icons_path][uploaded_icon.guid].dmi"))
set_guid(type, max_guid)
for(var/i = 1 to max_guid)
if(!guid_to_icon[i])
free_guid(type, i)
report_progress("Loaded [length(guid_to_icon)] uploaded icon\s.")

catch(var/exception/E)
error("Exception when loading player icons manifest: [EXCEPTION_TEXT(E)]")

/datum/controller/subsystem/uploaded_icons/proc/store_icon(_uploader, _description, _icon_flags, icon/_icon)
var/datum/uploaded_icon/new_icon = new(get_guid(type))
new_icon.uploader = _uploader
new_icon.description = _description
new_icon.icon_flags = _icon_flags
new_icon.icon = _icon
need_save = TRUE

/datum/controller/subsystem/uploaded_icons/proc/remove_icon(_guid)
var/datum/uploaded_icon/uploaded_icon = guid_to_icon[_guid]
if(!uploaded_icon)
return FALSE
guid_to_icon -= _guid
qdel(uploaded_icon)
free_guid(type, _guid)
need_save = TRUE

/datum/controller/subsystem/uploaded_icons/Shutdown()
save_icons(force = TRUE)
. = ..()

/datum/controller/subsystem/uploaded_icons/proc/save_icons(force = FALSE)

if(!need_save && !force)
return

try
// Write out our .DMI files and populate our manifest.
var/list/manifest_entries = list()
for(var/icon_guid,icon_data in guid_to_icon)
var/datum/uploaded_icon/uploaded_icon = icon_data
manifest_entries += list(uploaded_icon.Serialize())
// Should we back up the .DMI folder first? Seems much more
// likely to cause memory issues than backing up .json files.
fcopy(uploaded_icon.icon, "[icons_path][uploaded_icon.guid].dmi")
// Take a timestamped backup.
var/manifest_path = "[icons_path][icons_manifest]"
if(fexists(manifest_path))
var/backup_contents = file2text(manifest_path)
var/backup_file = file("[manifest_path].[BACKUP_TIMESTAMP]")
to_file(backup_file, backup_contents)
// Write out the manifest.
to_file(manifest_path, json_encode(manifest_entries))

catch(var/exception/E)
error("Exception when saving uploaded icons: [EXCEPTION_TEXT(E)]")

need_save = FALSE

/datum/uploaded_icon
var/guid
var/uploader
var/description
var/icon_flags = 0
// License? Artist?
var/icon/icon

var/const/ICON_FLAG_PUBLIC = BITFLAG(0)

/datum/uploaded_icon/New(list/_data)
if(islist(_data))
guid = _data[/datum/uploaded_icon::guid]
uploader = _data[/datum/uploaded_icon::uploader]
icon_flags = _data[/datum/uploaded_icon::icon_flags]
description = _data[/datum/uploaded_icon::description]
else if(isnum(_data))
guid = _data
if(isnum(guid))
// Check for collisions before doing this.
SSuploaded_icons.guid_to_icon[guid] = src
// else report an error?

/datum/uploaded_icon/Serialize()
. = ..()
// The actual icon is saved to disk and referenced by guid, not handled here.
.[/datum/uploaded_icon::guid] = guid
.[/datum/uploaded_icon::uploader] = uploader
.[/datum/uploaded_icon::icon_flags] = icon_flags
.[/datum/uploaded_icon::description] = description
2 changes: 1 addition & 1 deletion code/modules/multiz/level_persistence_handler_json.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// Do a backup (at the end to avoid overwriting then throwing an exception)
if(fexists(filepath))
var/backup_contents = file2text(filepath)
var/backup_file = file("[filepath].[time2text(REALTIMEOFDAY, "YY-MM-DD_hh-mm")].backup")
var/backup_file = file("[filepath].[BACKUP_TIMESTAMP]")
to_file(backup_file, backup_contents)
// Clear old file to avoid appending data.
// TODO: remove old backups? Leave as an exercise for the admin?
Expand Down
2 changes: 1 addition & 1 deletion code/modules/persistence/persistence_datum.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
if(length(entries) && !istext(entries[1]))
try
// Save a backup of the old file just in case we cook it.
fcopy(filename, "[filename]-legacy.[time2text(REALTIMEOFDAY, "YY-MM-DD_hh-mm")].backup")
fcopy(filename, "[filename]-legacy.[BACKUP_TIMESTAMP]")
catch(var/exception/e)
log_error("Exception during saving backup of legacy file [filename]: [EXCEPTION_TEXT(e)]")

Expand Down
3 changes: 3 additions & 0 deletions nebula.dme
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
#include "code\_helpers\game.dm"
#include "code\_helpers\gauss.dm"
#include "code\_helpers\global_lists.dm"
#include "code\_helpers\guid.dm"
#include "code\_helpers\icons.dm"
#include "code\_helpers\lists.dm"
#include "code\_helpers\logging.dm"
Expand Down Expand Up @@ -272,6 +273,7 @@
#include "code\controllers\subsystems\alarm.dm"
#include "code\controllers\subsystems\ambience.dm"
#include "code\controllers\subsystems\ao.dm"
#include "code\controllers\subsystems\approvals.dm"
#include "code\controllers\subsystems\atoms.dm"
#include "code\controllers\subsystems\configuration.dm"
#include "code\controllers\subsystems\daycycle.dm"
Expand Down Expand Up @@ -310,6 +312,7 @@
#include "code\controllers\subsystems\timer.dm"
#include "code\controllers\subsystems\trade.dm"
#include "code\controllers\subsystems\typing.dm"
#include "code\controllers\subsystems\uploaded_icons.dm"
#include "code\controllers\subsystems\vis_contents.dm"
#include "code\controllers\subsystems\vote.dm"
#include "code\controllers\subsystems\weather.dm"
Expand Down
Loading