chore(client): vendor GUT 9.7.1 + headless test harness
This commit is contained in:
346
client/addons/gut/update_detector.gd
Normal file
346
client/addons/gut/update_detector.gd
Normal file
@@ -0,0 +1,346 @@
|
||||
extends Node
|
||||
## -----------------------------------------------------------------------------
|
||||
## Checks to see if there is a better version of GUT available for a version of
|
||||
## Godot. The data used to validate the GUT version includes lower and upper
|
||||
## Godot versions
|
||||
##
|
||||
## There are 3 places where version information is located.
|
||||
## * LOCAL_FILE_PATH: This is the version information that is packed with this
|
||||
## release of GUT. It serves as a fallback if remote data is not available.
|
||||
## This is most useful in detecting that GUT is being used with an earlier
|
||||
## release of Godot and remote data is not available.
|
||||
## * REMOTE_FILE_URL: This is where the the version information exists outside
|
||||
## of the local copy of GUT. This data will be downlaoded to REMOTE_FILE_PATH
|
||||
## whenever remote data is fetched.
|
||||
## * REMOTE_FILE_PATH: This is where REMOTE_FILE_URL is downloaded to. When
|
||||
## the file is downloaded, timetamp information is added to the data so that
|
||||
## we can avoid downloading the file too often. This file is used whenever
|
||||
## it exists. If it does not exist and we don't fetch it, LOCAL_FILE_PATH
|
||||
## will be used.
|
||||
##
|
||||
## REMOTE_FILE_URL Location
|
||||
## Currently this file points to a file on the main branch. This is the same
|
||||
## file that will be shipped with each version of GUT. This allows the file to
|
||||
## be updated and still be shipped with each version of GUT.
|
||||
## -----------------------------------------------------------------------------
|
||||
var Vnt = load("res://addons/gut/version_numbers.gd").VerNumTools
|
||||
|
||||
|
||||
const REMOTE_FILE_URL = "https://api.github.com/repos/bitwes/gut/contents/addons/gut/versions.json"
|
||||
const LOCAL_FILE_PATH = "res://addons/gut/versions.json"
|
||||
const REMOTE_FILE_PATH = "user://gut_temp_directory/versions.json"
|
||||
const VERSION_ZERO = "0.0.0"
|
||||
|
||||
class VersionData:
|
||||
var _data := {}
|
||||
var data_issues = []
|
||||
const VERSION_ZERO = "0.0.0"
|
||||
|
||||
var Vnt = load("res://addons/gut/version_numbers.gd").VerNumTools
|
||||
|
||||
|
||||
func is_gut_version_valid(gut_v, godot_v):
|
||||
if(_data.releases.has(gut_v)):
|
||||
var entry = _data.releases[gut_v]
|
||||
return Vnt.is_version_gte(godot_v, entry.godot_min) and Vnt.is_version_lte(godot_v, entry.godot_max)
|
||||
else:
|
||||
return false
|
||||
|
||||
|
||||
func parse_data(new_data):
|
||||
data_issues.clear()
|
||||
_data = {}
|
||||
if(typeof(new_data) == TYPE_STRING):
|
||||
_data = JSON.parse_string(new_data)
|
||||
elif(typeof(new_data) == TYPE_DICTIONARY):
|
||||
_data = new_data
|
||||
|
||||
if(!_data.has('asset_library')):
|
||||
data_issues.append("asset_library entry missing")
|
||||
|
||||
if(_data.has('releases')):
|
||||
for key in _data.releases:
|
||||
var entry = _data.releases[key]
|
||||
if(!entry.has('godot_min')):
|
||||
data_issues.append(str(key, ' missing godot_min'))
|
||||
if(!entry.has('godot_max')):
|
||||
data_issues.append(str(key, ' missing godot_max'))
|
||||
else:
|
||||
data_issues.append('missing releases entry')
|
||||
|
||||
if(_data.has('branches')):
|
||||
for key in _data.branches:
|
||||
var entry = _data.branches[key]
|
||||
if(!entry.has('godot_min')):
|
||||
data_issues.append(str(key, ' missing godot_min'))
|
||||
if(!entry.has('godot_max')):
|
||||
data_issues.append(str(key, ' missing godot_max'))
|
||||
else:
|
||||
data_issues.append('missing branches entry')
|
||||
|
||||
|
||||
func parse_file(path):
|
||||
if(FileAccess.file_exists(path)):
|
||||
var text = GutUtils.get_file_as_text(path)
|
||||
parse_data(text)
|
||||
else:
|
||||
_data = {}
|
||||
|
||||
|
||||
func get_gut_version_for_godot_version(godot_v=null):
|
||||
var to_return = VERSION_ZERO
|
||||
if(is_empty()):
|
||||
return to_return
|
||||
|
||||
if(godot_v == null):
|
||||
godot_v = GutUtils.version_numbers.make_godot_version_string()
|
||||
|
||||
for key in _data.releases:
|
||||
var entry = _data.releases[key]
|
||||
if(Vnt.is_version_gte(godot_v, entry.godot_min) and Vnt.is_version_lte(godot_v, entry.godot_max)):
|
||||
if(Vnt.is_version_gte(key, to_return)):
|
||||
to_return = key
|
||||
|
||||
if(to_return == VERSION_ZERO and _data.has('branches')):
|
||||
for key in _data.branches:
|
||||
var entry = _data.branches[key]
|
||||
if(Vnt.is_version_gte(godot_v, entry.godot_min) and Vnt.is_version_lte(godot_v, entry.godot_max)):
|
||||
to_return = key
|
||||
|
||||
return to_return
|
||||
|
||||
func is_empty():
|
||||
return _data.is_empty()
|
||||
|
||||
func clear():
|
||||
_data.clear()
|
||||
data_issues.clear()
|
||||
|
||||
## See comment in get_update_string
|
||||
# func is_in_asset_library(gut_v):
|
||||
# if(_data.has('asset_library')):
|
||||
# return _data.asset_library == gut_v
|
||||
# else:
|
||||
# return false
|
||||
|
||||
func get_data():
|
||||
return _data.duplicate(true)
|
||||
|
||||
|
||||
var _http_request : HTTPRequest
|
||||
var local_data : VersionData = VersionData.new()
|
||||
var remote_data : VersionData = VersionData.new()
|
||||
|
||||
var min_fetch_wait = 60 * 60 # 1 hour
|
||||
|
||||
signal download_completed()
|
||||
signal updated()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_setup_http_request(HTTPRequest.new())
|
||||
|
||||
|
||||
func _setup_http_request(request):
|
||||
_http_request = request
|
||||
add_child(_http_request)
|
||||
_http_request.timeout = 3.0
|
||||
_http_request.request_completed.connect(self._http_request_completed)
|
||||
|
||||
|
||||
func _write_remote_file(data):
|
||||
data.fetch_timestamp = Time.get_unix_time_from_system()
|
||||
GutUtils.write_file(REMOTE_FILE_PATH, JSON.stringify(data))
|
||||
|
||||
|
||||
func _url_formatter(url, link_text=null):
|
||||
if(link_text == null):
|
||||
return url
|
||||
else:
|
||||
return str(link_text, ': ', url)
|
||||
|
||||
|
||||
#------------
|
||||
# Events
|
||||
#------------
|
||||
# Called when the HTTP request is completed.
|
||||
func _http_request_completed(result, response_code, headers, body):
|
||||
var body_text = body.get_string_from_utf8()
|
||||
|
||||
if(response_code == 200):
|
||||
var json = JSON.new()
|
||||
var err = json.parse(body_text)
|
||||
if(err != OK):
|
||||
push_error("[GUT] Invalid JSON: ", json.get_error_message(), '. ', body_text)
|
||||
download_completed.emit()
|
||||
return
|
||||
var response = json.get_data()
|
||||
|
||||
remote_data.parse_data(response)
|
||||
if(remote_data.data_issues.size() == 0):
|
||||
_write_remote_file(response.duplicate(true))
|
||||
else:
|
||||
push_error("[GUT] Invalid version data: ", remote_data.data_issues)
|
||||
remote_data.clear()
|
||||
else:
|
||||
var json = JSON.new()
|
||||
var err = json.parse(body_text)
|
||||
var response = {}
|
||||
if(err == OK):
|
||||
response = json.get_data()
|
||||
|
||||
var msg = ''
|
||||
if(response != null and response.has('message')):
|
||||
msg = str(" (", response.message, ")")
|
||||
push_error("[GUT] Could not get version info, response code: ", response_code, msg)
|
||||
|
||||
download_completed.emit()
|
||||
|
||||
|
||||
func _is_branch_version(v : String):
|
||||
return v.find(".") == -1
|
||||
#------------
|
||||
# Public
|
||||
#------------
|
||||
func fetch_remote_file():
|
||||
var headers : PackedStringArray = [
|
||||
"Accept: application/vnd.github.raw",
|
||||
"X-GitHub-Api-Version: 2022-11-28"
|
||||
]
|
||||
var error = _http_request.request(REMOTE_FILE_URL, headers)
|
||||
if error != OK:
|
||||
var errtxt = str("[GUT] An error occurred requesting version data: ", error, ".")
|
||||
remote_data.data_issues.append(errtxt)
|
||||
push_error(errtxt)
|
||||
return error
|
||||
|
||||
|
||||
func get_gut_version_for_godot_version(godot_v=null):
|
||||
var to_return = VERSION_ZERO
|
||||
if(godot_v == null):
|
||||
godot_v = GutUtils.version_numbers.make_godot_version_string()
|
||||
|
||||
var remote_latest = remote_data.get_gut_version_for_godot_version(godot_v)
|
||||
var local_latest = local_data.get_gut_version_for_godot_version(godot_v)
|
||||
|
||||
if(remote_latest == VERSION_ZERO and local_latest != VERSION_ZERO):
|
||||
to_return = local_latest
|
||||
elif(remote_latest != VERSION_ZERO and local_latest == VERSION_ZERO):
|
||||
to_return = remote_latest
|
||||
elif(_is_branch_version(remote_latest) and !_is_branch_version(local_latest)):
|
||||
to_return = local_latest
|
||||
elif(Vnt.is_version_gte(remote_latest, local_latest)):
|
||||
to_return = remote_latest
|
||||
else:
|
||||
to_return = local_latest
|
||||
|
||||
return to_return
|
||||
|
||||
|
||||
func is_gut_version_valid(gut_v =null, godot_v=null):
|
||||
if(gut_v == null):
|
||||
gut_v = GutUtils.version_numbers.gut_version
|
||||
godot_v = GutUtils.version_numbers.make_godot_version_string()
|
||||
|
||||
if(!local_data.is_empty() and !remote_data.is_empty()):
|
||||
return local_data.is_gut_version_valid(gut_v, godot_v) or \
|
||||
remote_data.is_gut_version_valid(gut_v, godot_v)
|
||||
else:
|
||||
return true
|
||||
|
||||
## See comment in get_update_string
|
||||
# func is_in_asset_library(gut_v):
|
||||
# if(remote_data._data.has('asset_library')):
|
||||
# return remote_data._data.asset_library == gut_v
|
||||
# else:
|
||||
# return false
|
||||
|
||||
|
||||
func check_for_update():
|
||||
local_data.parse_file(LOCAL_FILE_PATH)
|
||||
if(FileAccess.file_exists(REMOTE_FILE_PATH)):
|
||||
remote_data.parse_file(REMOTE_FILE_PATH)
|
||||
|
||||
updated.emit.call_deferred()
|
||||
|
||||
|
||||
func check_for_update_with_fetch(force=false):
|
||||
remote_data.parse_file(REMOTE_FILE_PATH)
|
||||
var time_since_last_fetch = 60 * 60 * 24 * 10_000 # ten thousand days
|
||||
|
||||
if(remote_data._data.has("fetch_timestamp")):
|
||||
time_since_last_fetch = Time.get_unix_time_from_system() - remote_data._data.fetch_timestamp
|
||||
|
||||
if(force or time_since_last_fetch > min_fetch_wait):
|
||||
fetch_remote_file()
|
||||
await download_completed
|
||||
|
||||
check_for_update()
|
||||
|
||||
|
||||
func get_update_string(url_formatter:Callable=_url_formatter):
|
||||
var gut_v = GutUtils.version_numbers.gut_version
|
||||
var godot_v = GutUtils.godot_version_string()
|
||||
var version_info = str("GUT ", gut_v, " is the lastest version for Godot ", godot_v)
|
||||
|
||||
var rec_ver = get_gut_version_for_godot_version(godot_v)
|
||||
var rec_ver_link = url_formatter.call(str("https://github.com/bitwes/Gut/releases/tag/v", rec_ver), str("GUT ",rec_ver))
|
||||
|
||||
if(is_gut_version_valid(gut_v, godot_v)):
|
||||
if(rec_ver != gut_v):
|
||||
version_info = str(rec_ver_link, ' is now available!')
|
||||
else:
|
||||
if(rec_ver.find(".") == -1):
|
||||
version_info = str("GUT does not have a release for this version of Godot yet, but it does have ",
|
||||
"the branch '", rec_ver, "'.\n",
|
||||
"Check the readme for install links/instructions: ", url_formatter.call('https://github.com/bitwes/Gut'))
|
||||
else:
|
||||
version_info = str('This version of GUT may not be compatible with Godot ', godot_v, '. ')
|
||||
if(rec_ver == '0.0.0'):
|
||||
version_info += str("No release or branch exists for this version of Godot yet. Check back soon.")
|
||||
else:
|
||||
version_info += str('Consider changing to ', rec_ver_link)
|
||||
## Commented this out for now, this needs to be reassessed for the new store
|
||||
## For now I'm leaving it out as I don't know what the urls look like or have
|
||||
## anything out there yet.
|
||||
# if(rec_ver != gut_v and is_in_asset_library(rec_ver)):
|
||||
# version_info += str("\nYou can update to ", rec_ver, " through the Asset Library.")
|
||||
return version_info
|
||||
|
||||
|
||||
func get_summary_string():
|
||||
var gut_v = GutUtils.version_numbers.gut_version
|
||||
var godot_v = GutUtils.godot_version_string()
|
||||
|
||||
return str("GUT: ", gut_v, "\n",
|
||||
"Godot: ", godot_v, "\n",
|
||||
"Valid: ", is_gut_version_valid(gut_v, godot_v), "\n",
|
||||
"Latest: ", get_gut_version_for_godot_version(godot_v))
|
||||
|
||||
|
||||
func fetch_limit_wait_time():
|
||||
var remaining = -1
|
||||
if(remote_data.get_data().has("fetch_timestamp")):
|
||||
var time_since_last_fetch = Time.get_unix_time_from_system() - remote_data._data.fetch_timestamp
|
||||
return max(min_fetch_wait - time_since_last_fetch, 0.0)
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
func get_days_since_last_fetch():
|
||||
var to_return = 99
|
||||
if(remote_data.get_data().has("fetch_timestamp")):
|
||||
var time_since_last_fetch = Time.get_unix_time_from_system() - remote_data._data.fetch_timestamp
|
||||
to_return = time_since_last_fetch / (60.0 * 60.0 * 24.0)
|
||||
return to_return
|
||||
|
||||
|
||||
func is_empty():
|
||||
return local_data.is_empty() and remote_data.is_empty()
|
||||
|
||||
|
||||
func get_all_data():
|
||||
return {
|
||||
local_data:local_data.get_data(),
|
||||
remote_data:remote_data.get_data()
|
||||
}
|
||||
Reference in New Issue
Block a user