Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update storage to make StorageTask fully transparent #403

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 34 additions & 37 deletions addons/godot-firebase/storage/storage.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
92 changes: 34 additions & 58 deletions addons/godot-firebase/storage/storage_reference.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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",
}

Expand All @@ -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].
Expand All @@ -64,116 +64,92 @@ 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()

var headers := []
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:
Expand Down
Loading
Loading