Files
fps_project_1/addons/godot-firebase/firestore/firestore_task.gd

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