From b307f50d2ac324efd77acdd9f160fcc22517add0 Mon Sep 17 00:00:00 2001 From: Kyle Szklenski Date: Mon, 10 Jun 2024 06:26:26 -0400 Subject: [PATCH] Update storage to make StorageTask fully transparent --- addons/godot-firebase/storage/storage.gd | 71 +++++++------- .../storage/storage_reference.gd | 92 +++++++------------ addons/godot-firebase/storage/storage_task.gd | 47 +++++----- 3 files changed, 92 insertions(+), 118 deletions(-) diff --git a/addons/godot-firebase/storage/storage.gd b/addons/godot-firebase/storage/storage.gd index 82a8017..28d016e 100644 --- a/addons/godot-firebase/storage/storage.gd +++ b/addons/godot-firebase/storage/storage.gd @@ -10,11 +10,6 @@ extends Node const _API_VERSION : String = "v0" -## @arg-types int, int, PackedStringArray -## @arg-enums HTTPRequest.Result, HTTPClient.ResponseCode -## Emitted when a [StorageTask] has finished successful. -signal task_successful(result, response_code, data) - ## @arg-types int, int, PackedStringArray ## @arg-enums HTTPRequest.Result, HTTPClient.ResponseCode ## Emitted when a [StorageTask] has finished with an error. @@ -83,6 +78,7 @@ func _internal_process(_delta : float) -> void: for header in _response_headers: if "Content-Length" in header: _content_length = header.trim_prefix("Content-Length: ").to_int() + break _http_client.poll() var chunk = _http_client.read_response_body_chunk() # Get a chunk. @@ -127,13 +123,13 @@ func ref(path := "") -> StorageReference: if not _references.has(path): var ref := StorageReference.new() _references[path] = ref - ref.valid = true ref.bucket = bucket ref.full_path = path - ref.name = path.get_file() + ref.file_name = path.get_file() ref.parent = ref(path.path_join("..")) ref.root = _root_ref ref.storage = self + add_child(ref) return ref else: return _references[path] @@ -158,9 +154,9 @@ func _check_emulating() -> void : _base_url = "http://localhost:{port}/{version}/".format({ version = _API_VERSION, port = port }) -func _upload(data : PackedByteArray, headers : PackedStringArray, ref : StorageReference, meta_only : bool) -> StorageTask: +func _upload(data : PackedByteArray, headers : PackedStringArray, ref : StorageReference, meta_only : bool) -> Variant: if _is_invalid_authentication(): - return null + return 0 var task := StorageTask.new() task.ref = ref @@ -169,11 +165,11 @@ func _upload(data : PackedByteArray, headers : PackedStringArray, ref : StorageR task._headers = headers task.data = data _process_request(task) - return task + return await task.task_finished -func _download(ref : StorageReference, meta_only : bool, url_only : bool) -> StorageTask: +func _download(ref : StorageReference, meta_only : bool, url_only : bool) -> Variant: if _is_invalid_authentication(): - return null + return 0 var info_task := StorageTask.new() info_task.ref = ref @@ -182,7 +178,7 @@ func _download(ref : StorageReference, meta_only : bool, url_only : bool) -> Sto _process_request(info_task) if url_only or meta_only: - return info_task + return await info_task.task_finished var task := StorageTask.new() task.ref = ref @@ -199,33 +195,36 @@ func _download(ref : StorageReference, meta_only : bool, url_only : bool) -> Sto task.response_code = info_task.response_code task.result = info_task.result task.finished = true - task.task_finished.emit() + task.task_finished.emit(null) task_failed.emit(task.result, task.response_code, task.data) _pending_tasks.erase(task) + return null - return task + return await task.task_finished -func _list(ref : StorageReference, list_all : bool) -> StorageTask: +func _list(ref : StorageReference, list_all : bool) -> Array: if _is_invalid_authentication(): - return null + return [] var task := StorageTask.new() task.ref = ref task._url = _get_file_url(_root_ref).trim_suffix("/") task.action = StorageTask.Task.TASK_LIST_ALL if list_all else StorageTask.Task.TASK_LIST _process_request(task) - return task + return await task.task_finished -func _delete(ref : StorageReference) -> StorageTask: +func _delete(ref : StorageReference) -> bool: if _is_invalid_authentication(): - return null + return false var task := StorageTask.new() task.ref = ref task._url = _get_file_url(ref) task.action = StorageTask.Task.TASK_DELETE _process_request(task) - return task + var data = await task.task_finished + + return data == null func _process_request(task : StorageTask) -> void: if requesting: @@ -262,7 +261,10 @@ func _finish_request(result : int) -> void: StorageTask.Task.TASK_DELETE: _references.erase(task.ref.full_path) - task.ref.valid = false + for child in get_children(): + if child.full_path == task.ref.full_path: + child.queue_free() + break if typeof(task.data) == TYPE_PACKED_BYTE_ARRAY: task.data = null @@ -301,26 +303,21 @@ func _finish_request(result : int) -> void: var json = Utilities.get_json_data(_response_data) task.data = json - var next_task : StorageTask - if not _pending_tasks.is_empty(): - next_task = _pending_tasks.pop_front() - + var next_task = _get_next_pending_task() + task.finished = true task.task_finished.emit(task.data) # I believe this parameter has been missing all along, but not sure. Caused weird results at times with a yield/await returning null, but the task containing data. if typeof(task.data) == TYPE_DICTIONARY and task.data.has("error"): task_failed.emit(task.result, task.response_code, task.data) - else: - task_successful.emit(task.result, task.response_code, task.data) - - while true: - if next_task and not next_task.finished: - _process_request(next_task) - break - elif not _pending_tasks.is_empty(): - next_task = _pending_tasks.pop_front() - else: - break + if next_task and not next_task.finished: + _process_request(next_task) + +func _get_next_pending_task() -> StorageTask: + if _pending_tasks.is_empty(): + return null + + return _pending_tasks.pop_front() func _get_file_url(ref : StorageReference) -> String: var url := _extended_url.replace("[APP_ID]", ref.bucket) diff --git a/addons/godot-firebase/storage/storage_reference.gd b/addons/godot-firebase/storage/storage_reference.gd index 2ce2446..2989d12 100644 --- a/addons/godot-firebase/storage/storage_reference.gd +++ b/addons/godot-firebase/storage/storage_reference.gd @@ -4,7 +4,7 @@ ## This object is used to interact with the cloud storage. You may get data from the server, as well as upload your own back to it. @tool class_name StorageReference -extends RefCounted +extends Node ## The default MIME type to use when uploading a file. ## Data sent with this type are interpreted as plain binary data. Note that firebase will generate an MIME type based checked the file extenstion if none is provided. @@ -36,7 +36,7 @@ const MIME_TYPES = { "txt": "text/plain", "wav": "audio/wav", "webm": "video/webm", - "webp": "video/webm", + "webp": "image/webp", "xml": "text/xml", } @@ -51,7 +51,7 @@ var full_path : String = "" ## @default "" ## The name of the file/folder, including any file extension. ## Example: If the [member full_path] is [code]images/user/image.png[/code], then the [member name] would be [code]image.png[/code]. -var name : String = "" +var file_name : String = "" ## The parent [StorageReference] one level up the file hierarchy. ## If the current [StorageReference] is the root (i.e. the [member full_path] is [code]""[/code]) then the [member parent] will be [code]null[/code]. @@ -64,25 +64,16 @@ var root : StorageReference ## The Storage API that created this [StorageReference] to begin with. var storage # FirebaseStorage (Can't static type due to cyclic reference) -## @default false -## Whether this [StorageReference] is valid. None of the functions will work when in an invalid state. -## It is set to false when [method delete] is called. -var valid : bool = false - ## @args path ## @return StorageReference ## Returns a reference to another [StorageReference] relative to this one. func child(path : String) -> StorageReference: - if not valid: - return null return storage.ref(full_path.path_join(path)) ## @args data, metadata -## @return StorageTask -## Makes an attempt to upload data to the referenced file location. Status checked this task is found in the returned [StorageTask]. -func put_data(data : PackedByteArray, metadata := {}) -> StorageTask: - if not valid: - return null +## @return int +## Makes an attempt to upload data to the referenced file location. Returns Variant +func put_data(data : PackedByteArray, metadata := {}) -> Variant: if not "Content-Length" in metadata and not Utilities.is_web(): metadata["Content-Length"] = data.size() @@ -90,90 +81,75 @@ func put_data(data : PackedByteArray, metadata := {}) -> StorageTask: for key in metadata: headers.append("%s: %s" % [key, metadata[key]]) - return storage._upload(data, headers, self, false) + return await storage._upload(data, headers, self, false) + ## @args data, metadata -## @return StorageTask +## @return int ## Like [method put_data], but [code]data[/code] is a [String]. -func put_string(data : String, metadata := {}) -> StorageTask: - return put_data(data.to_utf8_buffer(), metadata) +func put_string(data : String, metadata := {}) -> Variant: + return await put_data(data.to_utf8_buffer(), metadata) ## @args file_path, metadata -## @return StorageTask +## @return int ## Like [method put_data], but the data comes from a file at [code]file_path[/code]. -func put_file(file_path : String, metadata := {}) -> StorageTask: +func put_file(file_path : String, metadata := {}) -> Variant: var file := FileAccess.open(file_path, FileAccess.READ) var data := file.get_buffer(file.get_length()) if "Content-Type" in metadata: metadata["Content-Type"] = MIME_TYPES.get(file_path.get_extension(), DEFAULT_MIME_TYPE) - return put_data(data, metadata) + return await put_data(data, metadata) -## @return StorageTask +## @return Variant ## Makes an attempt to download the files from the referenced file location. Status checked this task is found in the returned [StorageTask]. -func get_data() -> StorageTask: - if not valid: - return null - storage._download(self, false, false) - return storage._pending_tasks[-1] +func get_data() -> Variant: + var result = await storage._download(self, false, false) + return result ## @return StorageTask ## Like [method get_data], but the data in the returned [StorageTask] comes in the form of a [String]. -func get_string() -> StorageTask: - var task := get_data() - task.task_finished.connect(_on_task_finished.bind(task, "stringify")) - return task +func get_string() -> String: + var task := await get_data() + _on_task_finished(task, "stringify") + return task.data ## @return StorageTask ## Attempts to get the download url that points to the referenced file's data. Using the url directly may require an authentication header. Status checked this task is found in the returned [StorageTask]. -func get_download_url() -> StorageTask: - if not valid: - return null - return storage._download(self, false, true) +func get_download_url() -> Variant: + return await storage._download(self, false, true) ## @return StorageTask ## Attempts to get the metadata of the referenced file. Status checked this task is found in the returned [StorageTask]. -func get_metadata() -> StorageTask: - if not valid: - return null - return storage._download(self, true, false) +func get_metadata() -> Variant: + return await storage._download(self, true, false) ## @args metadata ## @return StorageTask ## Attempts to update the metadata of the referenced file. Any field with a value of [code]null[/code] will be deleted checked the server end. Status checked this task is found in the returned [StorageTask]. -func update_metadata(metadata : Dictionary) -> StorageTask: - if not valid: - return null +func update_metadata(metadata : Dictionary) -> Variant: var data := JSON.stringify(metadata).to_utf8_buffer() var headers := PackedStringArray(["Accept: application/json"]) - return storage._upload(data, headers, self, true) + return await storage._upload(data, headers, self, true) ## @return StorageTask ## Attempts to get the list of files and/or folders under the referenced folder This function is not nested unlike [method list_all]. Status checked this task is found in the returned [StorageTask]. -func list() -> StorageTask: - if not valid: - return null - return storage._list(self, false) +func list() -> Array: + return await storage._list(self, false) ## @return StorageTask ## Attempts to get the list of files and/or folders under the referenced folder This function is nested unlike [method list]. Status checked this task is found in the returned [StorageTask]. -func list_all() -> StorageTask: - if not valid: - return null - return storage._list(self, true) +func list_all() -> Array: + return await storage._list(self, true) ## @return StorageTask ## Attempts to delete the referenced file/folder. If successful, the reference will become invalid And can no longer be used. If you need to reference this location again, make a new reference with [method StorageTask.ref]. Status checked this task is found in the returned [StorageTask]. -func delete() -> StorageTask: - if not valid: - return null - return storage._delete(self) +func delete() -> bool: + return await storage._delete(self) func _to_string() -> String: var string := "gs://%s/%s" % [bucket, full_path] - if not valid: - string += " [Invalid RefCounted]" return string func _on_task_finished(task : StorageTask, action : String) -> void: diff --git a/addons/godot-firebase/storage/storage_task.gd b/addons/godot-firebase/storage/storage_task.gd index ad810c5..fa5cefd 100644 --- a/addons/godot-firebase/storage/storage_task.gd +++ b/addons/godot-firebase/storage/storage_task.gd @@ -1,4 +1,4 @@ -## @meta-authors SIsilicon +## @meta-authors SIsilicon, Kyle 'backat50ft' Szklenski ## @meta-version 2.2 ## An object that keeps track of an operation performed by [StorageReference]. @tool @@ -6,23 +6,22 @@ class_name StorageTask extends RefCounted enum Task { - TASK_UPLOAD, - TASK_UPLOAD_META, - TASK_DOWNLOAD, - TASK_DOWNLOAD_META, - TASK_DOWNLOAD_URL, - TASK_LIST, - TASK_LIST_ALL, - TASK_DELETE, - TASK_MAX ## The number of [enum Task] constants. + TASK_UPLOAD, + TASK_UPLOAD_META, + TASK_DOWNLOAD, + TASK_DOWNLOAD_META, + TASK_DOWNLOAD_URL, + TASK_LIST, + TASK_LIST_ALL, + TASK_DELETE, + TASK_MAX ## The number of [enum Task] constants. } ## Emitted when the task is finished. Returns data depending checked the success and action of the task. signal task_finished(data) -## @type StorageReference -## The [StorageReference] that created this [StorageTask]. -var ref # Storage RefCounted (Can't static type due to cyclic reference) +## Boolean to determine if this request involves metadata only +var is_meta : bool ## @enum Task ## @default -1 @@ -30,6 +29,8 @@ var ref # Storage RefCounted (Can't static type due to cyclic reference) ## The kind of operation this [StorageTask] is keeping track of. var action : int = -1 : set = set_action +var ref # Should not be needed, damnit + ## @default PackedByteArray() ## Data that the tracked task will/has returned. var data = PackedByteArray() # data can be of any type. @@ -61,13 +62,13 @@ var _url : String = "" var _headers : PackedStringArray = PackedStringArray() func set_action(value : int) -> void: - action = value - match action: - Task.TASK_UPLOAD: - _method = HTTPClient.METHOD_POST - Task.TASK_UPLOAD_META: - _method = HTTPClient.METHOD_PATCH - Task.TASK_DELETE: - _method = HTTPClient.METHOD_DELETE - _: - _method = HTTPClient.METHOD_GET + action = value + match action: + Task.TASK_UPLOAD: + _method = HTTPClient.METHOD_POST + Task.TASK_UPLOAD_META: + _method = HTTPClient.METHOD_PATCH + Task.TASK_DELETE: + _method = HTTPClient.METHOD_DELETE + _: + _method = HTTPClient.METHOD_GET