Implement early presentation loader
This commit is contained in:
parent
3db7ab1daf
commit
dda4ce2646
2 changed files with 146 additions and 3 deletions
|
@ -55,16 +55,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.""")
|
||||||
# Initialize internals
|
# Initialize internals
|
||||||
logger.info("Initializing internals")
|
logger.info("Initializing internals")
|
||||||
|
|
||||||
# Load state manager
|
# Load presentation loader
|
||||||
var presenloader: Node = Node.new()
|
var presenloader: Node = Node.new()
|
||||||
presenloader.name = "Presentation loader"
|
presenloader.name = "Presenloader"
|
||||||
presenloader.set_script(load("res://src/presenloader.gd"))
|
presenloader.set_script(load("res://src/presenloader.gd"))
|
||||||
get_tree().root.add_child(presenloader)
|
get_tree().root.add_child(presenloader)
|
||||||
|
|
||||||
# Decide whenether to load a presentation or the UI
|
# Decide whenether to load a presentation or the UI
|
||||||
if args["load_presentation"]:
|
if args["load_presentation"]:
|
||||||
logger.verb("Loading 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:
|
else:
|
||||||
logger.verb("Loading user interface")
|
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()
|
var userinterface: NinePatchRect = load("res://scenesrc/UserInterface.tscn").instantiate()
|
||||||
userinterface.name = "UserInterface"
|
userinterface.name = "UserInterface"
|
||||||
userinterface.core_config = core_config.duplicate()
|
userinterface.core_config = core_config.duplicate()
|
||||||
|
userinterface.presenloader = presenloader
|
||||||
get_tree().root.add_child.call_deferred(userinterface)
|
get_tree().root.add_child.call_deferred(userinterface)
|
||||||
|
|
||||||
# Perform cleanup
|
# Perform cleanup
|
||||||
|
|
|
@ -1,10 +1,148 @@
|
||||||
extends Node
|
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 core: Core = get_node("/root/CORE")
|
||||||
@onready var logger: CoreLoggerInstance = core.logger.get_instance("src/presenloader.gd", self)
|
@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:
|
func _ready() -> void:
|
||||||
# Register cleanup hook
|
# Register cleanup hook
|
||||||
core.register_cleanup_hook(func() -> void: queue_free())
|
core.register_cleanup_hook(func() -> void: queue_free())
|
||||||
|
|
||||||
logger.info("Presentation loader is ready")
|
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()
|
||||||
|
|
Loading…
Reference in a new issue