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
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
@@ -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)
92 changes: 34 additions & 58 deletions addons/godot-firebase/storage/storage_reference.gd
Original file line number Diff line number Diff line change
@@ -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,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:
47 changes: 24 additions & 23 deletions addons/godot-firebase/storage/storage_task.gd
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
## @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
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
## @setter set_action
## 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