2025-09-07 13:37:47 +03:00
|
|
|
class_name DownloadManager
|
|
|
|
|
extends Node
|
|
|
|
|
|
|
|
|
|
const DOWNLOAD_DIALOG = preload("res://Scenes/UI/DownloadDialog.tscn")
|
|
|
|
|
const DOWNLOAD_PROGRESS = preload("res://Scenes/UI/DownloadProgress.tscn")
|
|
|
|
|
const DOWNLOADS_HISTORY = preload("res://Scenes/BrowserMenus/downloads.tscn")
|
|
|
|
|
|
|
|
|
|
var active_downloads: Dictionary = {}
|
|
|
|
|
var download_progress_container: VBoxContainer = null
|
|
|
|
|
var downloads_history_ui: DownloadsStore = null
|
|
|
|
|
var main_node: Main = null
|
|
|
|
|
|
|
|
|
|
func _init(main_reference: Main):
|
|
|
|
|
main_node = main_reference
|
|
|
|
|
|
|
|
|
|
func _ensure_download_progress_container():
|
|
|
|
|
if not download_progress_container:
|
|
|
|
|
download_progress_container = VBoxContainer.new()
|
|
|
|
|
download_progress_container.name = "DownloadProgressContainer"
|
|
|
|
|
download_progress_container.size_flags_horizontal = Control.SIZE_SHRINK_END
|
|
|
|
|
download_progress_container.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
|
|
|
|
|
|
|
|
|
var anchor_container = Control.new()
|
|
|
|
|
anchor_container.name = "DownloadAnchor"
|
|
|
|
|
anchor_container.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
|
|
|
|
anchor_container.position = Vector2(0, 130)
|
|
|
|
|
anchor_container.offset_left = 381 # 376 + 5px padding
|
|
|
|
|
anchor_container.add_child(download_progress_container)
|
|
|
|
|
main_node.add_child(anchor_container)
|
|
|
|
|
|
|
|
|
|
func handle_download_request(download_data: Dictionary):
|
|
|
|
|
var active_tab = main_node.get_active_tab()
|
|
|
|
|
if active_tab and active_tab.current_url:
|
|
|
|
|
download_data["current_site"] = URLUtils.extract_domain(active_tab.current_url)
|
|
|
|
|
else:
|
|
|
|
|
download_data["current_site"] = "Unknown site"
|
|
|
|
|
|
2025-09-07 18:49:32 +03:00
|
|
|
var settings_node = Engine.get_main_loop().current_scene
|
|
|
|
|
var skip_confirmation = !settings_node.get_download_confirmation_setting()
|
2025-09-07 13:37:47 +03:00
|
|
|
|
2025-09-07 18:49:32 +03:00
|
|
|
if skip_confirmation:
|
|
|
|
|
var filename = download_data.get("filename", "download")
|
|
|
|
|
var default_path = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + "/" + filename
|
|
|
|
|
_on_download_confirmed(download_data, default_path)
|
|
|
|
|
else:
|
|
|
|
|
var dialog = DOWNLOAD_DIALOG.instantiate()
|
|
|
|
|
main_node.add_child(dialog)
|
|
|
|
|
|
|
|
|
|
dialog.download_confirmed.connect(_on_download_confirmed)
|
|
|
|
|
dialog.download_cancelled.connect(_on_download_cancelled)
|
|
|
|
|
dialog.show_download_dialog(download_data)
|
2025-09-07 13:37:47 +03:00
|
|
|
|
|
|
|
|
func _on_download_confirmed(download_data: Dictionary, save_path: String):
|
|
|
|
|
var download_id = download_data.get("id", "")
|
|
|
|
|
var url = download_data.get("url", "")
|
|
|
|
|
print(download_id, url)
|
|
|
|
|
if download_id.is_empty() or url.is_empty():
|
|
|
|
|
push_error("Invalid download data")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
_start_download(download_id, url, save_path, download_data)
|
|
|
|
|
|
|
|
|
|
func _on_download_cancelled(download_data: Dictionary):
|
|
|
|
|
print("Download cancelled: ", download_data.get("filename", "Unknown"))
|
|
|
|
|
|
|
|
|
|
func _start_download(download_id: String, url: String, save_path: String, download_data: Dictionary):
|
|
|
|
|
_ensure_download_progress_container()
|
|
|
|
|
|
|
|
|
|
var progress_ui = DOWNLOAD_PROGRESS.instantiate()
|
|
|
|
|
|
|
|
|
|
download_progress_container.add_child(progress_ui)
|
|
|
|
|
|
|
|
|
|
progress_ui.setup_download(download_id, download_data)
|
|
|
|
|
progress_ui.download_cancelled.connect(_on_download_progress_cancelled)
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
active_downloads[download_id] = {
|
|
|
|
|
"save_path": save_path,
|
|
|
|
|
"progress_ui": progress_ui,
|
|
|
|
|
"start_time": Time.get_ticks_msec() / 1000.0,
|
|
|
|
|
"total_bytes": 0,
|
|
|
|
|
"downloaded_bytes": 0,
|
|
|
|
|
"url": download_data.get("url", ""),
|
|
|
|
|
"filename": download_data.get("filename", ""),
|
|
|
|
|
"current_site": download_data.get("current_site", "")
|
|
|
|
|
}
|
2025-09-25 21:56:54 +03:00
|
|
|
|
|
|
|
|
if url.begins_with("gurt://"):
|
2025-09-26 14:49:22 +01:00
|
|
|
_start_gurt_download(download_id, url)
|
2025-09-25 21:56:54 +03:00
|
|
|
else:
|
|
|
|
|
_start_http_download(download_id, url)
|
|
|
|
|
|
|
|
|
|
func _start_http_download(download_id: String, url: String):
|
|
|
|
|
var http_request = HTTPRequest.new()
|
|
|
|
|
http_request.name = "DownloadRequest_" + download_id
|
|
|
|
|
main_node.add_child(http_request)
|
|
|
|
|
|
|
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
http_request.queue_free()
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
active_downloads[download_id]["http_request"] = http_request
|
|
|
|
|
|
|
|
|
|
var save_path = active_downloads[download_id]["save_path"]
|
2025-09-07 13:37:47 +03:00
|
|
|
http_request.set_download_file(save_path)
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
http_request.request_completed.connect(func(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
|
|
|
|
_on_download_completed(download_id, result, response_code, headers, body)
|
|
|
|
|
)
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
var headers = ["User-Agent: GURT Browser 1.0"]
|
|
|
|
|
var request_error = http_request.request(url, headers)
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
if request_error != OK:
|
|
|
|
|
var error_msg = "Failed to start download: " + str(request_error)
|
|
|
|
|
print(error_msg)
|
2025-09-25 21:56:54 +03:00
|
|
|
var progress_ui = active_downloads[download_id]["progress_ui"]
|
2025-09-07 13:37:47 +03:00
|
|
|
if progress_ui:
|
|
|
|
|
progress_ui.set_error(error_msg)
|
|
|
|
|
http_request.queue_free()
|
|
|
|
|
active_downloads.erase(download_id)
|
|
|
|
|
return
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
var timer = Timer.new()
|
|
|
|
|
timer.name = "ProgressTimer_" + download_id
|
2025-09-26 14:49:22 +01:00
|
|
|
timer.wait_time = 0.2
|
2025-09-07 13:37:47 +03:00
|
|
|
timer.timeout.connect(func(): _update_download_progress(download_id))
|
|
|
|
|
main_node.add_child(timer)
|
|
|
|
|
timer.start()
|
|
|
|
|
|
2025-09-26 14:49:22 +01:00
|
|
|
func _start_gurt_download(download_id: String, url: String):
|
2025-09-25 21:56:54 +03:00
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var progress_ui = active_downloads[download_id]["progress_ui"]
|
|
|
|
|
var save_path = active_downloads[download_id]["save_path"]
|
|
|
|
|
|
2025-09-26 14:49:22 +01:00
|
|
|
var client = GurtProtocolClient.new()
|
|
|
|
|
for ca in CertificateManager.trusted_ca_certificates:
|
|
|
|
|
client.add_ca_certificate(ca)
|
|
|
|
|
if not client.create_client_with_dns(30, GurtProtocol.DNS_SERVER_IP, GurtProtocol.DNS_SERVER_PORT):
|
|
|
|
|
if progress_ui:
|
|
|
|
|
progress_ui.set_error("Failed to create GURT client")
|
|
|
|
|
active_downloads.erase(download_id)
|
|
|
|
|
return
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-26 14:49:22 +01:00
|
|
|
active_downloads[download_id]["gurt_client"] = client
|
|
|
|
|
|
2025-09-26 15:03:39 +01:00
|
|
|
client.download_started.connect(_on_gurt_download_started)
|
|
|
|
|
client.download_progress.connect(_on_gurt_download_progress)
|
|
|
|
|
client.download_completed.connect(_on_gurt_download_completed)
|
|
|
|
|
client.download_failed.connect(_on_gurt_download_failed)
|
|
|
|
|
|
2025-09-26 14:49:22 +01:00
|
|
|
|
|
|
|
|
client.start_download(download_id, url, save_path)
|
|
|
|
|
|
|
|
|
|
var poll_timer = Timer.new()
|
|
|
|
|
poll_timer.wait_time = 0.2
|
|
|
|
|
poll_timer.one_shot = false
|
|
|
|
|
poll_timer.name = "GurtPoll_" + download_id
|
|
|
|
|
poll_timer.timeout.connect(func():
|
|
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
poll_timer.queue_free()
|
|
|
|
|
return
|
|
|
|
|
var c = active_downloads[download_id].get("gurt_client", null)
|
|
|
|
|
if c:
|
|
|
|
|
c.poll_events()
|
|
|
|
|
else:
|
|
|
|
|
poll_timer.queue_free()
|
|
|
|
|
)
|
|
|
|
|
main_node.add_child(poll_timer)
|
|
|
|
|
poll_timer.start()
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-26 14:49:22 +01:00
|
|
|
func _on_gurt_download_started(download_id: String, total_bytes: int):
|
2025-09-25 21:56:54 +03:00
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
return
|
2025-09-26 14:49:22 +01:00
|
|
|
var info = active_downloads[download_id]
|
|
|
|
|
info.total_bytes = max(total_bytes, 0)
|
|
|
|
|
info.downloaded_bytes = 0
|
|
|
|
|
var ui = info.progress_ui
|
|
|
|
|
if ui:
|
|
|
|
|
ui.update_progress(0.0, 0, info.total_bytes)
|
|
|
|
|
|
|
|
|
|
func _on_gurt_download_progress(download_id: String, downloaded_bytes: int, total_bytes: int):
|
|
|
|
|
if not active_downloads.has(download_id):
|
2025-09-25 21:56:54 +03:00
|
|
|
return
|
2025-09-26 14:49:22 +01:00
|
|
|
var info = active_downloads[download_id]
|
|
|
|
|
if total_bytes > 0:
|
|
|
|
|
info.total_bytes = total_bytes
|
|
|
|
|
info.downloaded_bytes = downloaded_bytes
|
|
|
|
|
var total = info.total_bytes
|
|
|
|
|
var p = 0.0
|
|
|
|
|
if total > 0:
|
|
|
|
|
p = float(downloaded_bytes) / float(total) * 100.0
|
|
|
|
|
var ui = info.progress_ui
|
|
|
|
|
if ui:
|
|
|
|
|
ui.update_progress(p, downloaded_bytes, total)
|
|
|
|
|
|
|
|
|
|
func _on_gurt_download_completed(download_id: String, save_path: String):
|
|
|
|
|
if not active_downloads.has(download_id):
|
2025-09-25 21:56:54 +03:00
|
|
|
return
|
2025-09-26 14:49:22 +01:00
|
|
|
var info = active_downloads[download_id]
|
|
|
|
|
var path = save_path if not save_path.is_empty() else info.save_path
|
|
|
|
|
var size = 0
|
|
|
|
|
if FileAccess.file_exists(path):
|
|
|
|
|
var f = FileAccess.open(path, FileAccess.READ)
|
|
|
|
|
if f:
|
|
|
|
|
size = f.get_length()
|
|
|
|
|
f.close()
|
|
|
|
|
info.total_bytes = size
|
|
|
|
|
info.downloaded_bytes = size
|
|
|
|
|
var ui = info.progress_ui
|
|
|
|
|
if ui:
|
|
|
|
|
ui.set_completed(path)
|
|
|
|
|
_add_to_download_history(info, size, path)
|
|
|
|
|
active_downloads.erase(download_id)
|
2025-09-25 21:56:54 +03:00
|
|
|
|
2025-09-26 14:49:22 +01:00
|
|
|
func _on_gurt_download_failed(download_id: String, message: String):
|
|
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
return
|
|
|
|
|
var info = active_downloads[download_id]
|
|
|
|
|
var ui = info.progress_ui
|
|
|
|
|
if ui:
|
|
|
|
|
ui.set_error(message)
|
|
|
|
|
var path = info.save_path
|
|
|
|
|
if FileAccess.file_exists(path):
|
|
|
|
|
DirAccess.remove_absolute(path)
|
2025-09-25 21:56:54 +03:00
|
|
|
active_downloads.erase(download_id)
|
|
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
func _update_download_progress(download_id: String):
|
|
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var download_info = active_downloads[download_id]
|
2025-09-25 21:56:54 +03:00
|
|
|
var http_request = download_info.get("http_request", null)
|
2025-09-07 13:37:47 +03:00
|
|
|
var progress_ui = download_info.progress_ui
|
|
|
|
|
|
|
|
|
|
if http_request and progress_ui:
|
|
|
|
|
var downloaded = http_request.get_downloaded_bytes()
|
|
|
|
|
var total = http_request.get_body_size()
|
|
|
|
|
|
|
|
|
|
download_info.downloaded_bytes = downloaded
|
|
|
|
|
download_info.total_bytes = total
|
|
|
|
|
|
|
|
|
|
var progress_percent = 0.0
|
|
|
|
|
if total > 0:
|
|
|
|
|
progress_percent = (float(downloaded) / float(total)) * 100.0
|
|
|
|
|
|
|
|
|
|
progress_ui.update_progress(progress_percent, downloaded, total)
|
|
|
|
|
|
|
|
|
|
func _on_download_completed(download_id: String, result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
|
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var download_info = active_downloads[download_id]
|
|
|
|
|
var progress_ui = download_info.progress_ui
|
|
|
|
|
var save_path = download_info.save_path
|
|
|
|
|
|
|
|
|
|
var timer = main_node.get_node_or_null("ProgressTimer_" + download_id)
|
|
|
|
|
if timer:
|
|
|
|
|
timer.queue_free()
|
|
|
|
|
|
|
|
|
|
if response_code >= 200 and response_code < 300 and result == HTTPRequest.RESULT_SUCCESS:
|
|
|
|
|
var file = FileAccess.open(save_path, FileAccess.READ)
|
|
|
|
|
if file:
|
|
|
|
|
var file_size = file.get_length()
|
|
|
|
|
file.close()
|
|
|
|
|
|
|
|
|
|
if progress_ui:
|
|
|
|
|
progress_ui.set_completed(save_path)
|
|
|
|
|
|
|
|
|
|
_add_to_download_history(download_info, file_size, save_path)
|
|
|
|
|
|
|
|
|
|
print("Download completed: ", save_path)
|
|
|
|
|
else:
|
|
|
|
|
if progress_ui:
|
|
|
|
|
progress_ui.set_error("Downloaded file not found")
|
|
|
|
|
print("Downloaded file not found: ", save_path)
|
|
|
|
|
else:
|
|
|
|
|
var error_msg = "HTTP " + str(response_code) if response_code >= 400 else "Request failed (" + str(result) + ")"
|
|
|
|
|
if progress_ui:
|
|
|
|
|
progress_ui.set_error(error_msg)
|
|
|
|
|
print("Download failed: ", error_msg)
|
|
|
|
|
|
|
|
|
|
if FileAccess.file_exists(save_path):
|
|
|
|
|
DirAccess.remove_absolute(save_path)
|
|
|
|
|
|
|
|
|
|
download_info.http_request.queue_free()
|
|
|
|
|
active_downloads.erase(download_id)
|
|
|
|
|
|
|
|
|
|
func _on_download_progress_cancelled(download_id: String):
|
|
|
|
|
if not active_downloads.has(download_id):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var download_info = active_downloads[download_id]
|
2025-09-26 14:49:22 +01:00
|
|
|
if download_info.has("gurt_client"):
|
|
|
|
|
var c = download_info["gurt_client"]
|
|
|
|
|
c.cancel_download(download_id)
|
|
|
|
|
return
|
2025-09-25 21:56:54 +03:00
|
|
|
|
|
|
|
|
var http_request = download_info.get("http_request", null)
|
|
|
|
|
if http_request:
|
|
|
|
|
http_request.cancel_request()
|
|
|
|
|
http_request.queue_free()
|
|
|
|
|
|
2025-09-07 13:37:47 +03:00
|
|
|
var timer = main_node.get_node_or_null("ProgressTimer_" + download_id)
|
|
|
|
|
if timer:
|
|
|
|
|
timer.queue_free()
|
|
|
|
|
|
|
|
|
|
active_downloads.erase(download_id)
|
|
|
|
|
print("Download cancelled: ", download_id)
|
|
|
|
|
|
|
|
|
|
func show_downloads_history():
|
|
|
|
|
_ensure_downloads_history_ui()
|
|
|
|
|
downloads_history_ui.popup_centered_ratio(0.8)
|
|
|
|
|
|
|
|
|
|
func _add_to_download_history(download_info: Dictionary, file_size: int, file_path: String):
|
|
|
|
|
_ensure_downloads_history_ui()
|
|
|
|
|
|
|
|
|
|
var history_data = {
|
|
|
|
|
"url": download_info.url,
|
|
|
|
|
"filename": download_info.filename,
|
|
|
|
|
"size": file_size,
|
|
|
|
|
"timestamp": Time.get_unix_time_from_system(),
|
|
|
|
|
"file_path": file_path,
|
|
|
|
|
"current_site": download_info.get("current_site", "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
downloads_history_ui.add_download_entry(history_data)
|
|
|
|
|
|
|
|
|
|
func _ensure_downloads_history_ui():
|
|
|
|
|
if not downloads_history_ui:
|
|
|
|
|
downloads_history_ui = DOWNLOADS_HISTORY.instantiate()
|
|
|
|
|
downloads_history_ui.visible = false
|
|
|
|
|
main_node.add_child(downloads_history_ui)
|