mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-25 09:26:40 +01:00
243 lines
6.6 KiB
Plaintext
243 lines
6.6 KiB
Plaintext
/**
|
|
* manages pictures / photos
|
|
*
|
|
* for full functionality, the following must be enabled and working:
|
|
* - SQL DB for storage
|
|
* - Round ID system
|
|
* - picture directory that's accessible from the internet for delivery
|
|
*
|
|
* failure to have all of these will result in the system automatically falling back to
|
|
* non-persistent mode and delivering pictures with browse_rsc() instead of links.
|
|
*/
|
|
SUBSYSTEM_DEF(photography)
|
|
name = "Photography"
|
|
init_order = INIT_ORDER_PHOTOGRAPHY
|
|
subsystem_flags = SS_NO_FIRE | SS_NO_INIT
|
|
/// pictures loaded by hash as text
|
|
var/list/datum/picture/picture_cache = list()
|
|
/// photos loaded by id as text
|
|
var/list/datum/photograph/photograph_cache = list()
|
|
|
|
// todo: our config system is awful and needs a proper cache..
|
|
// todo: cache eviction
|
|
|
|
/datum/controller/subsystem/photography/proc/is_persistent()
|
|
return CONFIG_GET(flag/sql_enabled) && CONFIG_GET(flag/picture_persistent)
|
|
|
|
/**
|
|
* url image path for a photograph id. fails if we're not persistent-enabled and the image isn't loaded.
|
|
*
|
|
* this is the preferred method of fetching a picture that should be stored
|
|
* as we don't even need to grab (and cache) the datum itself.
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/url_for_photograph(id, list/client/clients)
|
|
var/datum/photograph/loaded = load_photograph(id)
|
|
return loaded?.img_src(clients)
|
|
|
|
/**
|
|
* url image path for a picture hash. fails if we're not persistent-enabled and the image isn't loaded.
|
|
*
|
|
* this is the preferred method of fetching a picture that should be stored
|
|
* as we don't even need to grab (and cache) the datum itself.
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/url_for_picture(hash, list/client/clients)
|
|
if(!is_persistent())
|
|
var/datum/picture/loaded = picture_cache[hash]
|
|
if(isnull(loaded))
|
|
return
|
|
return loaded.rsc_src(clients)
|
|
var/root = CONFIG_GET(string/picture_webroot)
|
|
return "[root]/[copytext(hash, 1, 3)]/[hash].png"
|
|
|
|
/**
|
|
* returns path to store picture. null if we're not persistent.
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/path_for_picture(hash)
|
|
if(!is_persistent())
|
|
return
|
|
var/root = CONFIG_GET(string/picture_storage)
|
|
return "[root]/[copytext(hash, 1, 3)]/[hash].png"
|
|
|
|
/**
|
|
* creates a picture from an icon, immediately flushing it to disk.
|
|
*
|
|
* @return /datum/picture instance
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/create_picture(icon/I)
|
|
var/datum/picture/picture = new
|
|
// init
|
|
picture.full = I
|
|
var/hash = sha1asfile(I)
|
|
ASSERT(length(hash))
|
|
picture.image_hash = hash
|
|
// cache - load existing if exists to de-dupe
|
|
__sql_load_picture(picture.image_hash)
|
|
if(picture_cache[picture.image_hash])
|
|
// return existing copy if already exists
|
|
return picture_cache[picture.image_hash]
|
|
picture.width = I.Width()
|
|
picture.height = I.Height()
|
|
picture_cache[picture.image_hash] = picture
|
|
// sql + storage enabled?
|
|
if(!is_persistent())
|
|
// nope; bail
|
|
return picture
|
|
// store
|
|
// todo: did we really need to fcopy twice to hash and then copy? probably, but that seems wild.
|
|
fcopy(picture.full, path_for_picture(hash))
|
|
__sql_save_picture(picture)
|
|
picture.image_saved = TRUE
|
|
// unload to conserve memory
|
|
picture.unload()
|
|
// return instance
|
|
return picture
|
|
|
|
/**
|
|
* resolves a /datum/picture from a hash
|
|
*
|
|
* @return /datum/picture instance, or null
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/resolve_picture(hash)
|
|
. = picture_cache[hash]
|
|
if(. || !is_persistent())
|
|
return
|
|
var/datum/picture/loaded = __sql_load_picture(hash)
|
|
if(isnull(loaded))
|
|
return
|
|
picture_cache[hash] = loaded
|
|
return loaded
|
|
|
|
/datum/controller/subsystem/photography/proc/__sql_save_picture(datum/picture/pic)
|
|
// pause admin proccall guard
|
|
var/__oldusr = usr
|
|
usr = null
|
|
// section below can never be allowed to runtime
|
|
|
|
var/datum/db_query/query = SSdbcore.NewQuery(
|
|
{"
|
|
INSERT INTO [DB_PREFIX_TABLE_NAME("pictures")]
|
|
(`hash`, `width`, `height`) VALUES
|
|
(:hash, :width, :height)
|
|
"},
|
|
list(
|
|
"hash" = pic.image_hash,
|
|
"width" = pic.width,
|
|
"height" = pic.height,
|
|
)
|
|
)
|
|
query.Execute()
|
|
|
|
// resume admin proccall guard
|
|
usr = __oldusr
|
|
|
|
|
|
/datum/controller/subsystem/photography/proc/__sql_load_picture(hash)
|
|
// pause admin proccall guard
|
|
var/__oldusr = usr
|
|
usr = null
|
|
// section below can never be allowed to runtime
|
|
|
|
var/datum/db_query/query = SSdbcore.NewQuery(
|
|
{"
|
|
SELECT `width`, `height`
|
|
FROM [DB_PREFIX_TABLE_NAME("pictures")]
|
|
WHERE `hash` = :hash
|
|
"},
|
|
list(
|
|
"hash" = hash,
|
|
)
|
|
)
|
|
query.Execute()
|
|
|
|
if(query.NextRow())
|
|
var/datum/picture/pic = new
|
|
pic.image_hash = hash
|
|
pic.width = text2num(query.item[1])
|
|
pic.height = text2num(query.item[2])
|
|
. = pic
|
|
|
|
// resume admin proccall guard
|
|
usr = __oldusr
|
|
|
|
/**
|
|
* saves a photograph to disk.
|
|
*
|
|
* photograph is immutable after.
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/save_photograph(datum/photograph/photograph)
|
|
if(!is_persistent())
|
|
// just assign it a temporary id; if it collides we don't really care we did our best
|
|
var/static/temporary_id_next = 0
|
|
photograph.id = "[GLOB.round_number]-[num2text(temporary_id_next++, 99999999)]"
|
|
else
|
|
__sql_save_photograph(photograph)
|
|
photograph_cache[photograph.id] = photograph
|
|
return photograph
|
|
|
|
/**
|
|
* loads a photograph datum based on id
|
|
*/
|
|
/datum/controller/subsystem/photography/proc/load_photograph(id)
|
|
. = photograph_cache[id]
|
|
if(. || !is_persistent())
|
|
return
|
|
var/datum/photograph/loaded = __sql_load_photograph(id)
|
|
if(isnull(loaded))
|
|
return
|
|
photograph_cache[id] = loaded
|
|
return loaded
|
|
|
|
/datum/controller/subsystem/photography/proc/__sql_save_photograph(datum/photograph/photo)
|
|
// pause admin proccall guard
|
|
var/__oldusr = usr
|
|
usr = null
|
|
// section below can never be allowed to runtime
|
|
|
|
var/datum/db_query/query = SSdbcore.NewQuery(
|
|
{"
|
|
INSERT INTO [DB_PREFIX_TABLE_NAME("photographs")]
|
|
(`picture`, `scene`, `desc`) VALUES
|
|
(:hash, :scene, :desc)
|
|
"},
|
|
list(
|
|
"hash" = photo.picture_hash,
|
|
"scene" = photo.scene,
|
|
"desc" = photo.desc,
|
|
)
|
|
)
|
|
query.Execute()
|
|
photo.id = "[query.last_insert_id]"
|
|
|
|
// resume admin proccall guard
|
|
usr = __oldusr
|
|
|
|
/datum/controller/subsystem/photography/proc/__sql_load_photograph(id)
|
|
// pause admin proccall guard
|
|
var/__oldusr = usr
|
|
usr = null
|
|
// section below can never be allowed to runtime
|
|
|
|
var/datum/db_query/query = SSdbcore.NewQuery(
|
|
{"
|
|
SELECT `picture`, `scene`, `desc`
|
|
FROM [DB_PREFIX_TABLE_NAME("photographs")]
|
|
WHERE `id` = :id
|
|
"},
|
|
list(
|
|
"id" = id,
|
|
)
|
|
)
|
|
query.Execute()
|
|
|
|
if(query.NextRow())
|
|
var/datum/photograph/photo = new
|
|
photo.id = id
|
|
photo.picture_hash = query.item[1]
|
|
photo.scene = query.item[2]
|
|
photo.desc = query.item[3]
|
|
. = photo
|
|
|
|
// resume admin proccall guard
|
|
usr = __oldusr
|
|
|