OverviewFilesCommitsIssuesDocsReleasesPeers

godot-witchy_world_demo / hypergodot / hyper_gateway / hyper_gateway.gd

242 lines · gd ref:
extends Node

const URLParser = preload("../utils/urlparser.gd")

class_name HyperGateway

signal starting_gateway()
signal started_gateway(pid)

signal stopping_gateway()
signal stopped_gateway()

signal started_download(version)
signal finished_download(version)

export var writable: bool = true
export var persist: bool = true

export var autoStart: bool = false
export var autoDownload: bool = true
export var autoUpdate: bool = false

const DEFAULT_PORT = 4973

export var port: int = DEFAULT_PORT
export var storageDirectory: String = "user://gateway-data/"
export var repositoryName: String = "rangermauve/hyper-gateway"
export var tagStart: String = "v2"

## TODO: Use this tag if the `latest` release doesn't match the tag?
# export var defaultTag = "v2.1.3"

var processPID : int = 0
var latestInfoRequest = HTTPRequest.new()
var downloadFileRequest = HTTPRequest.new()
var downloadingVersion = null
var downloadThread = null

func _exit_tree():
	cleanupGateway()

func _notification(what):
	if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
		cleanupGateway()

# Called when the node enters the scene tree for the first time.
func _ready():
	ensureStorageLocation()
	add_child(latestInfoRequest)
	latestInfoRequest.connect("request_completed", self, "_on_got_latest")
	
	add_child(downloadFileRequest)
	downloadFileRequest.connect("request_completed", self, "_on_finished_download")
	
	if autoStart:
		start()

func start():
	autoStart = true
	if autoDownload:
		ensureDownloaded()
	else: ensureSetup()
	
func stop():
	cleanupGateway()

func cleanupGateway() -> int:
	if processPID == 0:
		print('Gateway already closed ' + str(processPID)) 
		return OK
	
	print('Cleaning up gateway ' + str(processPID))
	emit_signal("stopping_gateway")
	var err = OS.kill(processPID)
	OS.delay_msec(2000)
	if err == OK:
		processPID = 0
		emit_signal("stopped_gateway")
	return err

func setupGateway():
	emit_signal("starting_gateway")
	var executiblePath = getExecutiblePath()
	var path = ProjectSettings.globalize_path(executiblePath)

	var args = ["run"]
	if !persist: args.append('--no-persist')
	if writable: args.append('--writable')
	
	args.append("--silent")
	
	if port != DEFAULT_PORT:
		args.append('--port')
		args.append(String(port))
	
	var resolvedStorageDirectory = ProjectSettings.globalize_path(storageDirectory)
	args.append('--storage-location')
	args.append(resolvedStorageDirectory)

	print("Starting gateway from ",path, " with args ", args)

	# Start the hyper-gateway in a process
	processPID = OS.execute(path, args, false)

	# Sleep for a bit while the gateway loads
	# TODO: Find a better indicator like polling the HTTP server socket
	OS.delay_msec(2000)

	print("Started gateway at " + str(processPID))	
	emit_signal("started_gateway", processPID)

func ensureSetup():
	if getIsGatewayRunning(): return
	setupGateway()
	
func getIsGatewayRunning() -> bool:
	if processPID != 0:
		return true;
	else:
		return false;
		
func getGatewayPort() -> int:
	if( getIsGatewayRunning() ):
		return port
	else:
		return -1;
		
func getGatewayPID() -> int:
	if( getIsGatewayRunning() ):
		return processPID;
	else:
		return 0

func getExecutibleName():
	var name = OS.get_name()
	if name == 'X11' or name == 'Linux': return "hyper-gateway-linux"
	if name == 'OSX': return "hyper-gateway-macos"
	if name == 'Windows': return "hyper-gateway-windows.exe"
	push_error("Can not start gateway, invalid operating system " + name)

func getCurrentVersion():
	var file = File.new()
	var err = file.open(getVersionFilePath(), File.READ)
	if err != OK:
		printerr('No existing binary version found', err)
		return ""
	var version = file.get_as_text()
	file.close()
	return version

func saveCurrentVersion(version):
	var file = File.new()
	file.open(getVersionFilePath(), File.WRITE)
	file.store_string(version)
	file.close()

func getVersionFilePath():
	return storageDirectory + 'version.txt'

func getExecutiblePath():
	var executibleName = getExecutibleName()
	return storageDirectory  + executibleName

func ensureStorageLocation():
	var dir = Directory.new()
	var err = dir.make_dir_recursive(storageDirectory)
	if err != OK:
		printerr("Unable to create storage directory ", err)

func hasBinary():
	var version = getCurrentVersion()
	return version && version.length()

func ensureDownloaded():
	if autoUpdate || !hasBinary():
		checkLatestVersion()
	elif autoStart:
		ensureSetup()

func checkLatestVersion():
	var url = latestURL()
	print('Getting latest version from ' + url)
	latestInfoRequest.request(url, [], true, HTTPClient.METHOD_GET)
	
func _on_got_latest(_result, response_code, _headers, body):
	print('Latest info response', response_code)
	var parsed = JSON.parse(body.get_string_from_utf8())
	if parsed.error != OK:
		printerr("Unable to parse latest version from GitHub:", parsed.error_string)
		return
	var info = parsed.result
	var newVersion = info.tag_name
	
	print('Latest version:', newVersion)

	var existing = getCurrentVersion()
	
	if newVersion != existing: downloadLatest(info.assets, newVersion)
	elif autoStart: ensureSetup()

func downloadLatest(assets, version):
	var toDownload = null
	var executibleName = getExecutibleName()
	for asset in assets:
		if asset.name != executibleName: continue
		toDownload = asset
	if !toDownload:
		printerr("Unable to find valid executible in latest release")
		return

	var fromURL = toDownload.browser_download_url
	var toPath = getExecutiblePath()

	print('Downloading latest binary:',fromURL)
	
	downloadFileRequest.set_download_file(toPath)
	
	downloadingVersion = version
	emit_signal("started_download", version)
	
	downloadFileRequest.request(fromURL, [], true,HTTPClient.METHOD_GET)

func _on_finished_download(_result, response_code, _headers, _body):
	var executiblePath = getExecutiblePath()
	var path = ProjectSettings.globalize_path(executiblePath)
	var os = OS.get_name()

	# Mark file as executible if not on Windows
	if os != "Windows":
		OS.execute("chmod", ["+x", path])

	print("Finished download ", response_code)
	saveCurrentVersion(downloadingVersion)
	emit_signal("finished_download", downloadingVersion)
	if autoStart: ensureSetup()

func repoURL():
	return "https://api.github.com/repos/" + repositoryName + "/"

func latestURL():
	return repoURL() + "releases/latest"