Implement early presentation loader

This commit is contained in:
JeremyStar™ 2024-05-11 12:07:04 +02:00
parent 3db7ab1daf
commit dda4ce2646
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
2 changed files with 146 additions and 3 deletions

View file

@ -55,16 +55,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.""")
# Initialize internals
logger.info("Initializing internals")
# Load state manager
# Load presentation loader
var presenloader: Node = Node.new()
presenloader.name = "Presentation loader"
presenloader.name = "Presenloader"
presenloader.set_script(load("res://src/presenloader.gd"))
get_tree().root.add_child(presenloader)
# Decide whenether to load a presentation or the UI
if args["load_presentation"]:
logger.verb("Loading presentation")
logger.crash("Not implemented.")
var error: String = presenloader.load_presentation(args["presentation_path"])
if error != "":
logger.error("Presencode is unable to load the presentation you tried to open.\nError thrown by presenloader.gd:\n" + error)
else:
logger.verb("Loading user interface")
@ -72,6 +76,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.""")
var userinterface: NinePatchRect = load("res://scenesrc/UserInterface.tscn").instantiate()
userinterface.name = "UserInterface"
userinterface.core_config = core_config.duplicate()
userinterface.presenloader = presenloader
get_tree().root.add_child.call_deferred(userinterface)
# Perform cleanup

View file

@ -1,10 +1,148 @@
extends Node
# Presencode version
const version: int = 1
const version_float: float = float(version)
# CORE
@onready var core: Core = get_node("/root/CORE")
@onready var logger: CoreLoggerInstance = core.logger.get_instance("src/presenloader.gd", self)
# Presentation zip/dir info
var path: String = ""
var is_file: bool = true
var manifest: Dictionary = {}
# Access methods
var reader: ZIPReader = ZIPReader.new()
var diraccess: DirAccess = null
func _ready() -> void:
# Register cleanup hook
core.register_cleanup_hook(func() -> void: queue_free())
logger.info("Presentation loader is ready")
func load_presentation(load_path: String) -> String:
logger.info("Validating presentation located at " + load_path)
path = load_path
is_file = FileAccess.file_exists(path)
# Check if exists
logger.verb("Checking if presentation exists")
if !is_file and !DirAccess.dir_exists_absolute(path):
return "Could not locate file or directory at " + path
if is_file:
# Refuse loading legacy presentations
if path.ends_with(".pcpa"):
return "Legacy Presencode presentation (.pcpa files) cannot be opened in newer Presencode versions. Please\nuse Presencode commit 19d3e8a0d00849c6668b31cb7ffda542826ba533 to view legacy Presencode presentations"
# This check simply exists to ensure that Presencode presentation can be easily recognized by the file extension.
if !path.ends_with(".pcar"): return "The file does not end in .pcar (Presencode Archive). Please rename the file."
# Open archive
logger.verb("Opening archive")
var error: Error = reader.open(path)
if error != Error.OK:
return core.misc.stringify_variables("Can't open ZIP archive: %error_string% (%error%)", { "error": error, "error_string": error_string(error) }, true)
else:
# Open directory
logger.verb("Opening directory")
diraccess = DirAccess.open(path)
if diraccess == null:
return core.misc.stringify_variables("Can't open directory: %error_string% (%error%)", { "error": DirAccess.get_open_error(), "error_string": error_string(DirAccess.get_open_error()) }, true)
var output: String = check_required_files()
if output != "": return output
output = parse_manifest()
if output != "": return output
var output_array: Array[String] = check_manifest()
if output_array != []: return core.misc.format_stringarray(output_array, "- ", "", "\n", "\n")
return ""
func check_required_files() -> String:
logger.verb("Checking for required files")
# Define variables
var files_present: Dictionary = { "manifest": false, "slides": false }
var files: PackedStringArray = PackedStringArray([])
# Update 'files' appropriately
if is_file:
files = reader.get_files()
else:
files = diraccess.get_files()
for directory in diraccess.get_directories():
files.append(directory + "/")
# Iterate through files and directories
for file in files:
if file == "manifest.json": files_present["manifest"] = true
elif file.begins_with("slides/"): files_present["slides"] = true
elif file.begins_with("src/"): pass
elif file.begins_with("assets/"): pass
else: logger.warn(core.misc.stringify_variables("Unknown file/directory %file% inside presentation directory, ignoring", { "file": file }))
# Check if required files exist
if !files_present["manifest"]:
return "The presentation manifest is missing. Make sure it is named 'manifest.json'"
if !files_present["slides"]: return "The 'slides' directory is missing. You can't have a presentation without slides, dingus!"
return ""
func parse_manifest() -> String:
logger.verb("Parsing manifest")
var manifest_raw: String = ""
if is_file: manifest_raw = reader.read_file("manifest.json").get_string_from_utf8()
else:
var file: FileAccess = FileAccess.open(path + "/manifest.json", FileAccess.READ)
if file == null:
return core.misc.stringify_variables("Can't read manifest: %error_string% (%error%)", { "error": FileAccess.get_open_error(), "error_string": error_string(FileAccess.get_open_error()) })
manifest_raw = file.get_as_text()
file.close()
var json: JSON = JSON.new()
var error: Error = json.parse(manifest_raw)
if error != OK:
return core.misc.stringify_variables("Can't parse manifest: %error% (line %line%)", { "error": json.get_error_message(), "line": json.get_error_line() })
if typeof(json.data) != Variant.Type.TYPE_DICTIONARY:
return core.misc.stringify_variables("Can't parse manifest: Parsed manifest is not of type Dictionary but instead %type%", { "type": type_string(typeof(json.data)) })
manifest = json.data
return ""
func check_manifest() -> Array[String]:
logger.verb("Checking manifest")
var schema: CoreValidationSchema = core.validation.get_schema({
"version": {
"minimum": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_FLOAT ]).is_not_empty().has_minimum_float(version_float).has_maximum_float(version_float),
"target": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_FLOAT ]).is_not_empty().has_minimum_float(version_float)
},
"resolution": {
"x": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_FLOAT ]).is_not_empty().has_minimum_float(500.0),
"y": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_FLOAT ]).is_not_empty().has_minimum_float(500.0)
},
"title": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_STRING ]).is_not_empty(),
"authors": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_ARRAY ]).is_not_empty(),
"contributors": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_ARRAY ]),
"license": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_STRING ]).has_values(Callable(func() -> Array:
# Retrieve SPDX license identifiers from res://assets/licenses.json (symlinked to res://dist/submodules/spdx-license-identifiers/json/licenses.json)
var licenses: Array = []
var file: FileAccess = FileAccess.open("res://assets/licenses.json", FileAccess.READ)
var spdx_licenses: Dictionary = JSON.parse_string(file.get_as_text())
file.close()
for license in spdx_licenses["licenses"]:
licenses.append(license["licenseId"])
return licenses
).call()),
"entrypoint": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_STRING ]).is_not_empty().contains([ ".gd" ], 1),
"slides": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_FLOAT ]).has_minimum_float(1.0),
"animations": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_BOOL ]),
"display_end_text_after_last_slide": core.validation.get_single_schema(self).matches_type([ Variant.Type.TYPE_BOOL ])
}, manifest, self)
return schema.evaluate()