189 lines
6.6 KiB
GDScript
189 lines
6.6 KiB
GDScript
## @meta-authors Nicolò 'fenix' Santilio, Kyle 'backat50ft' Szklenski
|
|
## @meta-version 1.4
|
|
##
|
|
## A [code]FirestoreTask[/code] is an independent node inheriting [code]HTTPRequest[/code] that processes a [code]Firestore[/code] request.
|
|
## Once the Task is completed (both if successfully or not) it will emit the relative signal (or a general purpose signal [code]task_finished()[/code]) and will destroy automatically.
|
|
##
|
|
## Being a [code]Node[/code] it can be stored in a variable to yield checked it, and receive its result as a callback.
|
|
## All signals emitted by a [code]FirestoreTask[/code] represent a direct level of signal communication, which can be high ([code]get_document(document), result_query(result)[/code]) or low ([code]task_finished(result)[/code]).
|
|
## An indirect level of communication with Tasks is also provided, redirecting signals to the [class FirebaseFirestore] module.
|
|
##
|
|
## ex.
|
|
## [code]var task : FirestoreTask = Firebase.Firestore.query(query)[/code]
|
|
## [code]var result : Array = await task.task_finished[/code]
|
|
## [code]var result : Array = await task.result_query[/code]
|
|
## [code]var result : Array = await Firebase.Firestore.task_finished[/code]
|
|
## [code]var result : Array = await Firebase.Firestore.result_query[/code]
|
|
##
|
|
## @tutorial https://github.com/GodotNuts/GodotFirebase/wiki/Firestore#FirestoreTask
|
|
|
|
@tool
|
|
class_name FirestoreTask
|
|
extends RefCounted
|
|
|
|
## Emitted when a request is completed. The request can be successful or not successful: if not, an [code]error[/code] Dictionary will be passed as a result.
|
|
## @arg-types Variant
|
|
signal task_finished()
|
|
|
|
enum Task {
|
|
TASK_GET, ## A GET Request Task, processing a get() request
|
|
TASK_POST, ## A POST Request Task, processing add() request
|
|
TASK_PATCH, ## A PATCH Request Task, processing a update() request
|
|
TASK_DELETE, ## A DELETE Request Task, processing a delete() request
|
|
TASK_QUERY, ## A POST Request Task, processing a query() request
|
|
TASK_AGG_QUERY, ## A POST Request Task, processing an aggregation_query() request
|
|
TASK_LIST, ## A POST Request Task, processing a list() request
|
|
TASK_COMMIT ## A POST Request Task that hits the write api
|
|
}
|
|
|
|
## Mapping of Task enum values to descriptions for use in printing user-friendly error codes.
|
|
const TASK_MAP = {
|
|
Task.TASK_GET: "GET DOCUMENT",
|
|
Task.TASK_POST: "ADD DOCUMENT",
|
|
Task.TASK_PATCH: "UPDATE DOCUMENT",
|
|
Task.TASK_DELETE: "DELETE DOCUMENT",
|
|
Task.TASK_QUERY: "QUERY COLLECTION",
|
|
Task.TASK_LIST: "LIST DOCUMENTS",
|
|
Task.TASK_COMMIT: "COMMIT DOCUMENT",
|
|
Task.TASK_AGG_QUERY: "AGG QUERY COLLECTION"
|
|
}
|
|
|
|
## The code indicating the request Firestore is processing.
|
|
## See @[enum FirebaseFirestore.Requests] to get a full list of codes identifiers.
|
|
## @setter set_action
|
|
var action : int = -1 : set = set_action
|
|
|
|
## A variable, temporary holding the result of the request.
|
|
var data
|
|
var error: Dictionary
|
|
var document: FirestoreDocument
|
|
|
|
var _response_headers: PackedStringArray = PackedStringArray()
|
|
var _response_code: int = 0
|
|
|
|
var _method: int = -1
|
|
var _url: String = ""
|
|
var _fields: String = ""
|
|
var _headers: PackedStringArray = []
|
|
|
|
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
|
|
var bod = body.get_string_from_utf8()
|
|
if bod != "":
|
|
bod = Utilities.get_json_data(bod)
|
|
|
|
var failed: bool = bod is Dictionary and bod.has("error") and response_code != HTTPClient.RESPONSE_OK
|
|
# Probably going to regret this...
|
|
if response_code == HTTPClient.RESPONSE_OK:
|
|
match action:
|
|
Task.TASK_POST, Task.TASK_GET, Task.TASK_PATCH:
|
|
document = FirestoreDocument.new(bod)
|
|
data = document
|
|
Task.TASK_DELETE:
|
|
data = true
|
|
Task.TASK_QUERY:
|
|
data = []
|
|
for doc in bod:
|
|
if doc.has('document'):
|
|
data.append(FirestoreDocument.new(doc.document))
|
|
Task.TASK_AGG_QUERY:
|
|
var agg_results = []
|
|
for agg_result in bod:
|
|
var idx = 0
|
|
var query_results = {}
|
|
for field_value in agg_result.result.aggregateFields.keys():
|
|
var agg = data.aggregations[idx]
|
|
var field = agg_result.result.aggregateFields[field_value]
|
|
query_results[agg.keys()[0]] = Utilities.from_firebase_type(field)
|
|
idx += 1
|
|
agg_results.push_back(query_results)
|
|
data = agg_results
|
|
Task.TASK_LIST:
|
|
data = []
|
|
if bod.has('documents'):
|
|
for doc in bod.documents:
|
|
data.append(FirestoreDocument.new(doc))
|
|
if bod.has("nextPageToken"):
|
|
data.append(bod.nextPageToken)
|
|
Task.TASK_COMMIT:
|
|
data = bod # Commit's response is not a full document, so don't treat it as such
|
|
else:
|
|
var description = ""
|
|
if TASK_MAP.has(action):
|
|
description = "(" + TASK_MAP[action] + ")"
|
|
|
|
Firebase._printerr("Action in error was: " + str(action) + " " + description)
|
|
build_error(bod, action, description)
|
|
|
|
task_finished.emit()
|
|
|
|
func build_error(_error, action, description) -> void:
|
|
if _error:
|
|
if _error is Array and _error.size() > 0 and _error[0].has("error"):
|
|
_error = _error[0].error
|
|
elif _error is Dictionary and _error.keys().size() > 0 and _error.has("error"):
|
|
_error = _error.error
|
|
|
|
error = _error
|
|
else:
|
|
#error.code, error.status, error.message
|
|
error = { "error": {
|
|
"code": 0,
|
|
"status": "Unknown Error",
|
|
"message": "Error: %s - %s" % [action, description]
|
|
}
|
|
}
|
|
|
|
data = null
|
|
|
|
func set_action(value : int) -> void:
|
|
action = value
|
|
match action:
|
|
Task.TASK_GET, Task.TASK_LIST:
|
|
_method = HTTPClient.METHOD_GET
|
|
Task.TASK_POST, Task.TASK_QUERY, Task.TASK_AGG_QUERY:
|
|
_method = HTTPClient.METHOD_POST
|
|
Task.TASK_PATCH:
|
|
_method = HTTPClient.METHOD_PATCH
|
|
Task.TASK_DELETE:
|
|
_method = HTTPClient.METHOD_DELETE
|
|
Task.TASK_COMMIT:
|
|
_method = HTTPClient.METHOD_POST
|
|
_:
|
|
assert(false)
|
|
|
|
|
|
func _merge_dict(dic_a : Dictionary, dic_b : Dictionary, nullify := false) -> Dictionary:
|
|
var ret := dic_a.duplicate(true)
|
|
for key in dic_b:
|
|
var val = dic_b[key]
|
|
|
|
if val == null and nullify:
|
|
ret.erase(key)
|
|
elif val is Array:
|
|
ret[key] = _merge_array(ret.get(key) if ret.get(key) else [], val)
|
|
elif val is Dictionary:
|
|
ret[key] = _merge_dict(ret.get(key) if ret.get(key) else {}, val)
|
|
else:
|
|
ret[key] = val
|
|
return ret
|
|
|
|
|
|
func _merge_array(arr_a : Array, arr_b : Array, nullify := false) -> Array:
|
|
var ret := arr_a.duplicate(true)
|
|
ret.resize(len(arr_b))
|
|
|
|
var deletions := 0
|
|
for i in len(arr_b):
|
|
var index : int = i - deletions
|
|
var val = arr_b[index]
|
|
if val == null and nullify:
|
|
ret.remove_at(index)
|
|
deletions += i
|
|
elif val is Array:
|
|
ret[index] = _merge_array(ret[index] if ret[index] else [], val)
|
|
elif val is Dictionary:
|
|
ret[index] = _merge_dict(ret[index] if ret[index] else {}, val)
|
|
else:
|
|
ret[index] = val
|
|
return ret
|