2017-04-28 20:04:09 +02:00
/**************************************************************************/
/* export_template_manager.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2017-03-20 23:31:41 -03:00
# include "export_template_manager.h"
2017-04-28 20:04:09 +02:00
2026-03-03 22:35:49 +01:00
# include "core/config/engine.h"
# include "core/error/error_list.h"
2021-06-11 14:51:48 +02:00
# include "core/io/dir_access.h"
2018-09-11 18:13:45 +02:00
# include "core/io/json.h"
2026-03-25 21:48:28 +01:00
# include "core/io/marshalls.h"
2018-09-11 18:13:45 +02:00
# include "core/io/zip_io.h"
2026-03-04 14:28:13 +01:00
# include "core/object/callable_mp.h"
2026-03-03 17:32:44 +01:00
# include "core/os/os.h"
2018-09-11 18:13:45 +02:00
# include "core/version.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_node.h"
2023-08-13 02:33:39 +02:00
# include "editor/editor_string_names.h"
2026-03-03 22:35:49 +01:00
# include "editor/export/editor_export.h"
2025-06-10 16:47:26 +02:00
# include "editor/file_system/editor_file_system.h"
# include "editor/file_system/editor_paths.h"
2026-03-21 17:49:09 +01:00
# include "editor/gui/editor_bottom_panel.h"
2025-06-10 16:47:26 +02:00
# include "editor/gui/progress_dialog.h"
# include "editor/settings/editor_settings.h"
2024-01-15 13:14:55 +01:00
# include "editor/themes/editor_scale.h"
2026-03-03 22:35:49 +01:00
# include "scene/gui/box_container.h"
# include "scene/gui/button.h"
# include "scene/gui/item_list.h"
# include "scene/gui/label.h"
2025-02-24 03:37:03 +07:00
# include "scene/gui/link_button.h"
2025-04-22 00:23:16 +02:00
# include "scene/gui/option_button.h"
2026-03-03 22:35:49 +01:00
# include "scene/gui/split_container.h"
2022-07-20 17:45:01 -05:00
# include "scene/gui/tree.h"
2026-03-03 22:35:49 +01:00
# include "scene/resources/style_box.h"
# include "scene/resources/texture.h"
2026-02-26 17:18:17 +01:00
# include "servers/display/display_server.h"
2017-11-17 21:48:24 +01:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _request_mirrors ( ) {
mirrors_list - > clear ( ) ;
mirrors_empty = true ;
_update_install_button ( ) ;
2024-08-30 22:14:31 +08:00
// Downloadable export templates are only available for stable and official alpha/beta/RC builds
// (which always have a number following their status, e.g. "alpha1").
// Therefore, don't display download-related features when using a development version
// (whose builds aren't numbered).
2026-03-03 22:35:49 +01:00
if ( ! strcmp ( GODOT_VERSION_STATUS , " dev " ) | | ! strcmp ( GODOT_VERSION_STATUS , " beta " ) | | ! strcmp ( GODOT_VERSION_STATUS , " rc " ) ) {
_set_empty_mirror_list ( ) ;
mirrors_list - > set_tooltip_text ( TTRC ( " Official export templates aren't available for development builds. " ) ) ;
2024-11-23 18:40:16 +01:00
# ifdef REAL_T_IS_DOUBLE
2026-03-03 22:35:49 +01:00
} else if ( true ) {
_set_empty_mirror_list ( ) ;
mirrors_list - > set_tooltip_text ( TTRC ( " Official export templates aren't available for double-precision builds. " ) ) ;
# endif
} else if ( ! _is_online ( ) ) {
mirrors_list - > set_tooltip_text ( TTRC ( " Template downloading is disabled in offline mode. " ) ) ;
} else {
mirrors_list - > set_tooltip_text ( String ( ) ) ;
}
2026-03-25 21:48:28 +01:00
if ( mirrors_list - > get_tooltip_text ( ) . is_empty ( ) ) {
const String mirrors_metadata_url = vformat ( " https://godotengine.org/mirrorlist/%s.json " , GODOT_VERSION_FULL_CONFIG ) ;
mirrors_requester - > request ( mirrors_metadata_url ) ;
}
2026-03-03 22:35:49 +01:00
}
2025-09-29 02:02:06 +02:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _mirrors_request_completed ( int p_result , int p_response_code , const PackedStringArray & p_headers , const PackedByteArray & p_body ) {
mirrors_list - > clear ( ) ;
2024-11-23 18:40:16 +01:00
2026-03-03 22:35:49 +01:00
if ( p_result ! = HTTPRequest : : RESULT_SUCCESS | | p_response_code ! = HTTPClient : : RESPONSE_OK ) {
String error = TTR ( " Error getting the list of mirrors. " ) + " \n " ;
if ( p_result = = HTTPRequest : : RESULT_SUCCESS & & p_response_code = = HTTPClient : : RESPONSE_NOT_FOUND ) {
// Response successful, but wrong address.
error + = TTR ( " No mirrors found for this version. Template download is only available for official releases. " ) ;
} else {
error + = vformat ( TTR ( " Result: %d \n Response code: %d " ) , p_result , p_response_code ) ;
}
EditorNode : : get_singleton ( ) - > show_warning ( error ) ;
_set_empty_mirror_list ( ) ;
return ;
2025-02-24 03:37:03 +07:00
}
2026-03-03 22:35:49 +01:00
String response_json = String : : utf8 ( ( const char * ) p_body . ptr ( ) , p_body . size ( ) ) ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
JSON json ;
Error err = json . parse ( response_json ) ;
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Error parsing JSON with the list of mirrors. Please report this issue! " ) ) ;
_set_empty_mirror_list ( ) ;
return ;
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
bool mirrors_available = false ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
Dictionary mirror_data = json . get_data ( ) ;
if ( mirror_data . has ( " mirrors " ) ) {
Array mirrors = mirror_data [ " mirrors " ] ;
for ( const Variant & mirror : mirrors ) {
Dictionary m = mirror ;
ERR_CONTINUE ( ! m . has ( " url " ) | | ! m . has ( " name " ) ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
mirrors_list - > add_item ( m [ " name " ] ) ;
mirrors_list - > set_item_metadata ( - 1 , m [ " url " ] ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
mirrors_available = true ;
}
// Hard-coded for translation. Should match the up-to-date list of mirrors.
// TTR("Official Releases mirror")
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
if ( ! mirrors_available ) {
_set_empty_mirror_list ( ) ;
2017-03-20 23:31:41 -03:00
} else {
2026-03-03 22:35:49 +01:00
mirrors_list - > set_disabled ( false ) ;
open_mirror - > set_disabled ( false ) ;
mirrors_empty = false ;
_update_install_button ( ) ;
if ( ! is_downloading ( ) ) {
// Some tree buttons won't show until mirrors are loaded.
_update_template_tree ( ) ;
2019-08-12 17:05:25 +02:00
}
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _set_empty_mirror_list ( ) {
mirrors_list - > add_item ( TTRC ( " No mirrors " ) ) ;
mirrors_list - > set_disabled ( true ) ;
open_mirror - > set_disabled ( true ) ;
mirrors_empty = true ;
_update_install_button ( ) ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
String ExportTemplateManager : : _get_current_mirror_url ( ) const {
return mirrors_list - > get_item_metadata ( mirrors_list - > get_selected ( ) ) ;
}
2021-03-12 16:32:53 -07:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _update_online_mode ( ) {
offline_container - > set_visible ( ( int ) EDITOR_GET ( " network/connection/network_mode " ) = = EditorSettings : : NETWORK_OFFLINE ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( _is_online ( ) ) {
_update_install_button ( ) ;
} else {
mirrors_list - > clear ( ) ;
_set_empty_mirror_list ( ) ;
2021-05-20 23:15:49 +03:00
}
}
2026-03-03 22:35:49 +01:00
bool ExportTemplateManager : : _is_online ( ) const {
return ! offline_container - > is_visible ( ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _force_online_mode ( ) {
EditorSettings : : get_singleton ( ) - > set_setting ( " network/connection/network_mode " , EditorSettings : : NETWORK_ONLINE ) ;
EditorSettings : : get_singleton ( ) - > notify_changes ( ) ;
EditorSettings : : get_singleton ( ) - > save ( ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
_update_online_mode ( ) ;
_request_mirrors ( ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _open_mirror ( ) {
OS : : get_singleton ( ) - > shell_open ( _get_current_mirror_url ( ) ) ;
2017-03-20 23:31:41 -03:00
}
2026-03-25 21:48:28 +01:00
void ExportTemplateManager : : _delete_confirmed ( ) {
const String selected_version = version_list - > get_item_text ( version_list - > get_current ( ) ) ;
const String template_directory = _get_template_folder_path ( selected_version ) ;
if ( _item_is_file ( item_to_delete ) ) {
OS : : get_singleton ( ) - > move_to_trash ( template_directory . path_join ( item_to_delete - > get_text ( 0 ) ) ) ;
file_metadata . erase ( item_to_delete - > get_text ( 0 ) ) ;
} else {
for ( TreeItem * child = item_to_delete - > get_first_child ( ) ; child ; child = child - > get_next ( ) ) {
if ( ! _get_file_metadata ( child ) - > is_missing ) {
OS : : get_singleton ( ) - > move_to_trash ( template_directory . path_join ( child - > get_text ( 0 ) ) ) ;
}
file_metadata . erase ( child - > get_text ( 0 ) ) ;
}
}
_update_template_tree ( ) ;
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _initialize_template_data ( ) {
// Base templates.
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Windows x86_32 " ;
2026-03-03 22:35:49 +01:00
info . description = TTRC ( " 32-bit build for Microsoft Windows, including console wrapper. " ) ;
info . file_list = { " windows_debug_x86_32.exe " , " windows_debug_x86_32_console.exe " , " windows_release_x86_32.exe " , " windows_release_x86_32_console.exe " } ;
template_data [ TemplateID : : WINDOWS_X86_32 ] = info ;
}
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Windows x86_64 " ;
2026-03-03 22:35:49 +01:00
info . description = TTRC ( " 64-bit build for Microsoft Windows, including console wrapper. " ) ;
info . file_list = { " windows_debug_x86_64.exe " , " windows_debug_x86_64_console.exe " , " windows_release_x86_64.exe " , " windows_release_x86_64_console.exe " } ;
template_data [ TemplateID : : WINDOWS_X86_64 ] = info ;
}
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Windows arm64 " ;
info . description = TTRC ( " 64-bit build for Microsoft Windows on ARM architecture, including console wrapper. " ) ;
2026-03-03 22:35:49 +01:00
info . file_list = { " windows_debug_arm64.exe " , " windows_debug_arm64_console.exe " , " windows_release_arm64.exe " , " windows_release_arm64_console.exe " } ;
template_data [ TemplateID : : WINDOWS_ARM64 ] = info ;
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Linux x86_32 " ;
2026-03-03 22:35:49 +01:00
info . description = TTRC ( " 32-bit build for Linux systems. " ) ;
info . file_list = { " linux_debug.x86_32 " , " linux_release.x86_32 " } ;
template_data [ TemplateID : : LINUX_X86_32 ] = info ;
}
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Linux x86_64 " ;
2026-03-03 22:35:49 +01:00
info . description = TTRC ( " 64-bit build for Linux systems. " ) ;
info . file_list = { " linux_debug.x86_64 " , " linux_release.x86_64 " } ;
template_data [ TemplateID : : LINUX_X86_64 ] = info ;
}
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Linux arm32 " ;
2026-03-03 22:35:49 +01:00
info . description = TTRC ( " 32-bit build for Linux systems on ARM architecture. " ) ;
info . file_list = { " linux_debug.arm32 " , " linux_release.arm32 " } ;
template_data [ TemplateID : : LINUX_ARM32 ] = info ;
}
{
TemplateInfo info ;
2026-03-25 21:48:28 +01:00
info . name = " Linux arm64 " ;
2026-03-03 22:35:49 +01:00
info . description = TTRC ( " 64-bit build for Linux systems on ARM architecture. " ) ;
info . file_list = { " linux_debug.arm64 " , " linux_release.arm64 " } ;
template_data [ TemplateID : : LINUX_ARM64 ] = info ;
}
2024-02-07 21:16:52 -03:00
2026-03-03 22:35:49 +01:00
{
TemplateInfo info ;
info . name = " macOS " ;
info . description = TTRC ( " Universal build for macOS. " ) ;
info . file_list = { " macos.zip " } ;
template_data [ TemplateID : : MACOS ] = info ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
{
TemplateInfo info ;
info . name = " Web " ;
info . description = TTRC ( " Regular web build with threading support. Threads improve performance, but require \" cross-origin isolated \" website to run. " ) ;
info . file_list = { " web_debug.zip " , " web_release.zip " } ;
template_data [ TemplateID : : WEB ] = info ;
}
{
TemplateInfo info ;
info . name = TTR ( " Web with Extensions " ) ;
info . description = TTRC ( " Web build with support for GDExtextensions. Only useful if you use GDExtensions, otherwise it only increases build size. " ) ;
info . file_list = { " web_dlink_debug.zip " , " web_dlink_release.zip " } ;
template_data [ TemplateID : : WEB_EXTENSIONS ] = info ;
}
{
TemplateInfo info ;
info . name = TTR ( " Web Single-Threaded " ) ;
info . description = TTRC ( " Web build without threading support. " ) ;
info . file_list = { " web_nothreads_debug.zip " , " web_nothreads_release.zip " } ;
template_data [ TemplateID : : WEB_NOTHREADS ] = info ;
}
{
TemplateInfo info ;
info . name = TTR ( " Web with Extensions Single-Threaded " ) ;
info . description = TTRC ( " Web build with GDExtension support and no threading support. " ) ;
info . file_list = { " web_dlink_nothreads_debug.zip " , " web_dlink_nothreads_release.zip " } ;
template_data [ TemplateID : : WEB_EXTENSIONS_NOTHREADS ] = info ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
{
TemplateInfo info ;
info . name = " Android " ;
info . description = TTRC ( " Basic Android APK template. " ) ;
info . file_list = { " android_debug.apk " , " android_release.apk " } ;
template_data [ TemplateID : : ANDROID ] = info ;
}
{
TemplateInfo info ;
info . name = TTR ( " Android Source " ) ;
info . description = TTRC ( " Template for Gradle builds for Android. " ) ;
info . file_list = { " android_source.zip " } ;
template_data [ TemplateID : : ANDROID_SOURCE ] = info ;
}
2021-12-09 11:34:32 +08:00
2026-03-03 22:35:49 +01:00
{
TemplateInfo info ;
info . name = " iOS " ;
info . description = TTRC ( " Build for Apple's iOS. " ) ;
info . file_list = { " ios.zip " } ;
template_data [ TemplateID : : IOS ] = info ;
}
{
TemplateInfo info ;
info . name = TTR ( " ICU Data " ) ;
info . description = TTRC ( " Line breaking dictionaries for TextServer, used by certain languages. " ) ;
info . file_list = { " icudt_godot.dat " } ;
template_data [ TemplateID : : ICU_DATA ] = info ;
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
// Platforms.
{
PlatformInfo info ;
info . name = " Windows " ;
info . icon = _get_platform_icon ( " Windows Desktop " ) ;
info . templates = { TemplateID : : WINDOWS_X86_32 , TemplateID : : WINDOWS_X86_64 , TemplateID : : WINDOWS_ARM64 } ;
info . group = TTR ( " Desktop " , " Platform Group " ) ;
platform_map [ PlatformID : : WINDOWS ] = info ;
}
{
PlatformInfo info ;
info . name = " Linux " ;
info . icon = _get_platform_icon ( " Linux " ) ;
info . templates = { TemplateID : : LINUX_X86_32 , TemplateID : : LINUX_X86_64 , TemplateID : : LINUX_ARM32 , TemplateID : : LINUX_ARM64 } ;
info . group = TTR ( " Desktop " , " Platform Group " ) ;
platform_map [ PlatformID : : LINUX ] = info ;
}
{
PlatformInfo info ;
info . name = " macOS " ;
info . icon = _get_platform_icon ( " macOS " ) ;
info . templates = { TemplateID : : MACOS } ;
info . group = TTR ( " Desktop " , " Platform Group " ) ;
platform_map [ PlatformID : : MACOS ] = info ;
}
{
PlatformInfo info ;
info . name = " Android " ;
info . icon = _get_platform_icon ( " Android " ) ;
info . templates = { TemplateID : : ANDROID , TemplateID : : ANDROID_SOURCE } ;
info . group = TTR ( " Mobile " , " Platform Group " ) ;
platform_map [ PlatformID : : ANDROID ] = info ;
}
{
PlatformInfo info ;
info . name = " iOS " ;
info . icon = _get_platform_icon ( " iOS " ) ;
info . templates = { TemplateID : : IOS } ;
info . group = TTR ( " Mobile " , " Platform Group " ) ;
platform_map [ PlatformID : : IOS ] = info ;
}
{
PlatformInfo info ;
info . name = " Web " ;
info . icon = _get_platform_icon ( " Web " ) ;
info . templates = { TemplateID : : WEB , TemplateID : : WEB_EXTENSIONS , TemplateID : : WEB_NOTHREADS , TemplateID : : WEB_EXTENSIONS_NOTHREADS } ;
info . group = TTR ( " Web " , " Platform Group " ) ;
platform_map [ PlatformID : : WEB ] = info ;
}
{
PlatformInfo info ;
info . name = TTR ( " Common " ) ;
info . templates = { TemplateID : : ICU_DATA } ;
platform_map [ PlatformID : : COMMON ] = info ;
}
2026-03-21 17:49:09 +01:00
2026-03-03 22:35:49 +01:00
// Template directory status.
DirAccess : : make_dir_recursive_absolute ( _get_template_folder_path ( VERSION_FULL_CONFIG ) ) ;
Ref < DirAccess > templates_dir = DirAccess : : open ( EditorPaths : : get_singleton ( ) - > get_export_templates_dir ( ) ) ;
ERR_FAIL_COND ( templates_dir . is_null ( ) ) ;
for ( const String & dir : templates_dir - > get_directories ( ) ) {
if ( dir = = GODOT_VERSION_FULL_CONFIG ) {
version_list - > add_item ( dir ) ;
version_list - > set_item_custom_fg_color ( - 1 , theme_cache . current_version_color ) ;
version_list - > select ( version_list - > get_item_count ( ) - 1 ) ;
} else {
version_list - > add_item ( dir ) ;
}
version_list - > set_item_metadata ( - 1 , dir ) ;
}
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _update_template_tree ( ) {
downloading_items . clear ( ) ;
const String selected_version = version_list - > get_item_text ( version_list - > get_current ( ) ) ;
Ref < DirAccess > template_directory = DirAccess : : open ( _get_template_folder_path ( selected_version ) ) ;
ERR_FAIL_COND ( template_directory . is_null ( ) ) ;
bool is_current_version = ( selected_version = = GODOT_VERSION_FULL_CONFIG ) ;
HashMap < TemplateID , LocalVector < String > > installed_template_files ;
for ( const KeyValue < PlatformID , PlatformInfo > & KV : platform_map ) {
for ( TemplateID id : KV . value . templates ) {
for ( const String & file : template_data [ id ] . file_list ) {
if ( template_directory - > file_exists ( file ) ) {
installed_template_files [ id ] . push_back ( file ) ;
2021-05-20 23:15:49 +03:00
}
}
2026-03-03 22:35:49 +01:00
}
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
_fill_template_tree ( available_templates_tree , installed_template_files , is_current_version ) ;
_fill_template_tree ( installed_templates_tree , installed_template_files , is_current_version ) ;
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _update_template_tree_theme ( Tree * p_tree ) {
if ( is_downloading ( ) ) {
// Prevents hiding progress bar.
Ref < StyleBoxEmpty > empty_style ;
empty_style . instantiate ( ) ;
p_tree - > add_theme_style_override ( SNAME ( " hovered " ) , empty_style ) ;
p_tree - > add_theme_style_override ( SNAME ( " hovered_dimmed " ) , empty_style ) ;
p_tree - > add_theme_style_override ( SNAME ( " selected " ) , empty_style ) ;
p_tree - > add_theme_style_override ( SNAME ( " selected_focus " ) , empty_style ) ;
p_tree - > add_theme_style_override ( SNAME ( " hovered_selected " ) , empty_style ) ;
p_tree - > add_theme_style_override ( SNAME ( " hovered_selected_focus " ) , empty_style ) ;
} else {
p_tree - > remove_theme_style_override ( SNAME ( " hovered " ) ) ;
p_tree - > remove_theme_style_override ( SNAME ( " hovered_dimmed " ) ) ;
p_tree - > remove_theme_style_override ( SNAME ( " selected " ) ) ;
p_tree - > remove_theme_style_override ( SNAME ( " selected_focus " ) ) ;
p_tree - > remove_theme_style_override ( SNAME ( " hovered_selected " ) ) ;
p_tree - > remove_theme_style_override ( SNAME ( " hovered_selected_focus " ) ) ;
2021-05-20 23:15:49 +03:00
}
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _fill_template_tree ( Tree * p_tree , const HashMap < TemplateID , LocalVector < String > > & p_installed_template_files , bool p_is_current_version ) {
bool is_installed_tree = ( p_tree = = installed_templates_tree ) ;
bool is_available_tree = ! is_installed_tree ; // For readability.
const LocalVector < String > empty_vector ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( p_tree - > get_root ( ) ) {
_update_folding_cache ( p_tree - > get_root ( ) ) ;
p_tree - > clear ( ) ;
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
TreeItem * platform_parent = p_tree - > create_item ( ) ;
_setup_item_text ( platform_parent , String ( ) ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( is_available_tree & & ! p_is_current_version ) {
TreeItem * nodownloadsforyou = platform_parent - > create_child ( ) ;
nodownloadsforyou - > set_text ( 0 , TTR ( " Downloads are only available for the current Godot version. " ) ) ;
nodownloadsforyou - > set_custom_color ( 0 , get_theme_color ( SNAME ( " font_disabled_color " ) , EditorStringName ( Editor ) ) ) ;
2021-05-20 23:15:49 +03:00
return ;
}
2026-03-03 22:35:49 +01:00
String current_group ;
for ( const KeyValue < PlatformID , PlatformInfo > & KV : platform_map ) {
const PlatformInfo & template_platform = KV . value ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
bool all_installed = true ;
bool any_installed = false ;
for ( TemplateID id : template_platform . templates ) {
if ( p_installed_template_files . has ( id ) & & ! queued_templates . has ( template_data [ id ] . name ) ) {
any_installed = true ;
} else {
all_installed = false ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( any_installed & & ! all_installed ) {
// Not going to change anymore.
break ;
}
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( ( is_available_tree & & all_installed ) | | ( is_installed_tree & & ! any_installed ) ) {
continue ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( is_available_tree & & template_platform . group ! = current_group ) {
// Use platform groups only for available templates.
_apply_item_folding ( platform_parent ) ;
current_group = template_platform . group ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( current_group . is_empty ( ) ) {
platform_parent = p_tree - > get_root ( ) ;
} else {
platform_parent = p_tree - > create_item ( ) ;
if ( ! is_downloading ( ) ) {
_set_item_type ( platform_parent , TreeItem : : CELL_MODE_CHECK ) ;
}
_setup_item_text ( platform_parent , current_group ) ;
}
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
TreeItem * platform_item = platform_parent - > create_child ( ) ;
if ( is_available_tree & & ! is_downloading ( ) ) {
_set_item_type ( platform_item , TreeItem : : CELL_MODE_CHECK ) ;
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
_setup_item_text ( platform_item , template_platform . name ) ;
platform_item - > set_icon ( 0 , template_platform . icon ) ;
platform_item - > set_icon_max_width ( 0 , theme_cache . icon_width ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
for ( TemplateID id : template_platform . templates ) {
TemplateInfo & template_info = template_data [ id ] ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
bool is_template_installed = p_installed_template_files . has ( id ) ;
if ( ! queued_templates . has ( template_info . name ) ) {
if ( is_template_installed = = is_available_tree ) {
continue ;
}
} else if ( is_installed_tree ) {
continue ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
const LocalVector < String > & installed_files = is_template_installed ? p_installed_template_files [ id ] : empty_vector ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
TreeItem * template_item ;
if ( template_platform . templates . size ( ) = = 1 & & template_info . name = = template_platform . name ) {
// Single template with the same name as platform, so it can be skipped.
template_item = platform_item ;
} else {
template_item = platform_item - > create_child ( ) ;
}
2025-02-24 03:37:03 +07:00
2026-03-03 22:35:49 +01:00
if ( is_available_tree ) {
if ( queued_templates . has ( template_info . name ) ) {
_set_item_type ( template_item , TreeItem : : CELL_MODE_CUSTOM ) ;
template_item - > add_button ( 0 , theme_cache . cancel_icon , ( int ) ButtonID : : CANCEL ) ;
template_item - > set_button_tooltip_text ( 0 , - 1 , TTR ( " Cancel downloading this template. " ) ) ;
} else if ( ! is_downloading ( ) ) {
_set_item_type ( template_item , TreeItem : : CELL_MODE_CHECK ) ;
}
}
_setup_item_text ( template_item , template_info . name ) ;
template_item - > set_tooltip_text ( 0 , TTR ( template_info . description ) ) ;
2025-02-24 03:37:03 +07:00
2026-03-03 22:35:49 +01:00
bool any_missing = false ;
bool any_failed = false ;
for ( const String & file : template_info . file_list ) {
FileMetadata * meta = _get_file_metadata ( file ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
TreeItem * file_item = template_item - > create_child ( ) ;
file_item - > set_meta ( FILE_META , true ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
if ( meta - > download_status = = DownloadStatus : : FAILED ) {
_add_fail_reason_button ( file_item , file ) ;
any_failed = true ;
}
if ( is_available_tree & & ! is_downloading ( ) ) {
_set_item_type ( file_item , TreeItem : : CELL_MODE_CHECK ) ;
} else if ( meta - > download_status ! = DownloadStatus : : NONE | | queued_files . has ( file ) ) {
if ( ! _status_is_finished ( meta - > download_status ) ) {
_set_item_type ( file_item , TreeItem : : CELL_MODE_CUSTOM ) ;
file_item - > add_button ( 0 , theme_cache . cancel_icon , ( int ) ButtonID : : CANCEL ) ;
file_item - > set_button_tooltip_text ( 0 , - 1 , TTRC ( " Cancel downloading this file. " ) ) ;
downloading_items . push_back ( file_item ) ;
if ( meta - > download_status = = DownloadStatus : : NONE ) {
meta - > download_status = DownloadStatus : : PENDING ;
}
}
}
_setup_item_text ( file_item , file ) ;
if ( is_installed_tree ) {
if ( installed_files . has ( file ) ) {
file_item - > add_button ( 0 , theme_cache . remove_icon , ( int ) ButtonID : : REMOVE ) ;
file_item - > set_button_tooltip_text ( 0 , - 1 , TTR ( " Remove this file. " ) ) ;
} else {
file_item - > set_custom_color ( 0 , theme_cache . missing_file_color ) ;
if ( p_is_current_version & & ! is_downloading ( ) & & _can_download_templates ( ) ) {
file_item - > add_button ( 0 , theme_cache . install_icon , ( int ) ButtonID : : DOWNLOAD ) ;
file_item - > set_button_tooltip_text ( 0 , - 1 , TTR ( " Download this missing file. " ) ) ;
}
meta - > is_missing = true ;
any_missing = true ;
}
}
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
if ( any_failed | | any_missing ) {
template_item - > set_custom_color ( 0 , theme_cache . incomplete_template_color ) ;
if ( any_failed ) {
template_item - > add_button ( 0 , theme_cache . failure_icon , ( int ) ButtonID : : NONE ) ;
template_item - > set_button_tooltip_text ( 0 , - 1 , TTR ( " Some files have failed to download. " ) ) ;
}
if ( any_missing & & p_is_current_version & & ! is_downloading ( ) & & _can_download_templates ( ) ) {
template_item - > add_button ( 0 , theme_cache . repair_icon , ( int ) ButtonID : : REPAIR ) ;
template_item - > set_button_tooltip_text ( 0 , - 1 , TTR ( " Download missing template files. " ) ) ;
}
}
if ( is_installed_tree ) {
template_item - > add_button ( 0 , theme_cache . remove_icon , ( int ) ButtonID : : REMOVE ) ;
template_item - > set_button_tooltip_text ( 0 , - 1 , TTR ( " Remove this template. " ) ) ;
}
_apply_item_folding ( template_item , true ) ;
}
_apply_item_folding ( platform_item ) ;
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
if ( p_tree - > get_root ( ) - > get_child_count ( ) = = 0 ) {
TreeItem * empty = p_tree - > create_item ( ) ;
empty - > set_text ( 0 , is_available_tree ? TTR ( " All templates installed. " ) : TTR ( " No templates installed. " ) ) ;
empty - > set_custom_color ( 0 , get_theme_color ( SNAME ( " font_disabled_color " ) , EditorStringName ( Editor ) ) ) ;
}
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _update_install_button ( ) {
if ( is_downloading ( ) ) {
install_button - > set_text ( TTRC ( " Downloading templates... " ) ) ;
install_button - > set_disabled ( true ) ;
install_button - > set_tooltip_text ( String ( ) ) ;
return ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
download_all_enabled = true ;
for ( TreeItem * item = available_templates_tree - > get_root ( ) ; item ; item = item - > get_next_in_tree ( ) ) {
if ( item - > is_checked ( 0 ) ) {
download_all_enabled = false ;
break ;
}
}
if ( download_all_enabled ) {
install_button - > set_text ( TTRC ( " Install All Templates " ) ) ;
2021-05-20 23:15:49 +03:00
} else {
2026-03-03 22:35:49 +01:00
install_button - > set_text ( TTRC ( " Install Selected Templates " ) ) ;
2021-05-20 23:15:49 +03:00
}
2026-03-03 22:35:49 +01:00
install_button - > set_disabled ( ! _can_download_templates ( ) ) ;
if ( install_button - > is_disabled ( ) ) {
if ( mirrors_empty ) {
install_button - > set_tooltip_text ( TTRC ( " No mirrors available for download. " ) ) ;
} else if ( ! _is_online ( ) ) {
install_button - > set_tooltip_text ( TTRC ( " Download not available in offline mode. " ) ) ;
} else {
install_button - > set_tooltip_text ( TTRC ( " Downloads are only available for the current Godot version. " ) ) ;
}
} else {
install_button - > set_tooltip_text ( String ( ) ) ;
2026-03-21 17:49:09 +01:00
}
2021-05-20 23:15:49 +03:00
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
bool ExportTemplateManager : : _can_download_templates ( ) {
const String selected_version = version_list - > get_item_text ( version_list - > get_current ( ) ) ;
return ! mirrors_empty & & _is_online ( ) & & selected_version = = GODOT_VERSION_FULL_CONFIG ;
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _update_folding_cache ( TreeItem * p_item ) {
folding_cache [ _get_item_path ( p_item ) ] = p_item - > is_collapsed ( ) ;
if ( p_item - > get_cell_mode ( 0 ) = = TreeItem : : CELL_MODE_CHECK ) {
if ( p_item - > is_indeterminate ( 0 ) ) {
checked_cache [ _get_item_path ( p_item ) ] = 1 ;
} else {
checked_cache [ _get_item_path ( p_item ) ] = p_item - > is_checked ( 0 ) ? 2 : 0 ;
}
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
for ( TreeItem * child = p_item - > get_first_child ( ) ; child ; child = child - > get_next ( ) ) {
_update_folding_cache ( child ) ;
}
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
String ExportTemplateManager : : _get_template_folder_path ( const String & p_version ) const {
return EditorPaths : : get_singleton ( ) - > get_export_templates_dir ( ) . path_join ( p_version ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
Ref < Texture2D > ExportTemplateManager : : _get_platform_icon ( const String & p_platform_name ) {
for ( int i = 0 ; i < EditorExport : : get_singleton ( ) - > get_export_platform_count ( ) ; i + + ) {
Ref < EditorExportPlatform > platform = EditorExport : : get_singleton ( ) - > get_export_platform ( i ) ;
if ( platform - > get_name ( ) = = p_platform_name ) {
return platform - > get_logo ( ) ;
2022-04-05 13:40:26 +03:00
}
2026-03-03 22:35:49 +01:00
}
return Ref < Texture2D > ( ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _version_selected ( ) {
if ( ! is_downloading ( ) ) {
file_metadata . clear ( ) ;
_update_template_tree ( ) ;
}
_update_install_button ( ) ;
}
2024-12-02 17:50:20 -08:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _tree_button_clicked ( TreeItem * p_item , int p_column , int p_id , MouseButton p_button ) {
switch ( ( ButtonID ) p_id ) {
case ButtonID : : DOWNLOAD : {
_install_templates ( p_item ) ;
} break ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
case ButtonID : : REPAIR : {
p_item - > set_collapsed ( false ) ;
_install_templates ( p_item ) ;
} break ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
case ButtonID : : REMOVE : {
2026-03-25 21:48:28 +01:00
item_to_delete = p_item ;
confirm_delete - > popup_centered ( ) ;
2026-03-03 22:35:49 +01:00
} break ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
case ButtonID : : CANCEL : {
if ( _item_is_file ( p_item ) ) {
_cancel_item_download ( p_item ) ;
if ( _is_template_download_finished ( p_item - > get_parent ( ) ) ) {
queued_templates . erase ( p_item - > get_parent ( ) - > get_text ( 0 ) ) ;
}
} else {
queued_templates . erase ( p_item - > get_text ( 0 ) ) ;
for ( TreeItem * child = p_item - > get_first_child ( ) ; child ; child = child - > get_next ( ) ) {
if ( _get_file_metadata ( child ) - > download_status ! = DownloadStatus : : NONE ) {
_cancel_item_download ( child ) ;
}
}
}
_process_download_queue ( ) ;
_update_template_tree ( ) ;
} break ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
case ButtonID : : FAIL : {
FileMetadata * meta = _get_file_metadata ( p_item ) ;
EditorNode : : get_singleton ( ) - > show_warning ( meta - > fail_reason + " . " , TTR ( " Download Failed " ) ) ;
} break ;
2018-07-16 13:05:45 +06:00
2026-03-03 22:35:49 +01:00
case ButtonID : : NONE : {
} break ;
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _tree_item_edited ( ) {
TreeItem * edited = available_templates_tree - > get_edited ( ) ;
ERR_FAIL_NULL ( edited ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
edited - > propagate_check ( 0 , false ) ;
_update_install_button ( ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _install_templates ( TreeItem * p_files ) {
_queue_download_tree_item ( p_files ? p_files : available_templates_tree - > get_root ( ) ) ;
download_count = queued_files . size ( ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
file_metadata . clear ( ) ;
_update_template_tree ( ) ;
_process_download_queue ( ) ;
_update_install_button ( ) ;
_update_template_tree_theme ( installed_templates_tree ) ;
_update_template_tree_theme ( available_templates_tree ) ;
ProgressIndicator * indicator = EditorNode : : get_bottom_panel ( ) - > get_progress_indicator ( ) ;
indicator - > set_tooltip_text ( TTRC ( " Downloading export templates... " ) ) ;
indicator - > set_value ( 0 ) ;
indicator - > show ( ) ;
}
void ExportTemplateManager : : _open_template_directory ( ) {
const String selected_version = version_list - > get_item_text ( version_list - > get_current ( ) ) ;
OS : : get_singleton ( ) - > shell_show_in_file_manager ( _get_template_folder_path ( selected_version ) , true ) ;
}
void ExportTemplateManager : : _queue_download_tree_item ( TreeItem * p_item ) {
if ( _item_is_file ( p_item ) ) {
bool valid ;
bool is_installed_tree = p_item - > get_tree ( ) = = installed_templates_tree ;
if ( is_installed_tree ) {
FileMetadata * meta = _get_file_metadata ( p_item ) ;
valid = meta - > is_missing ;
} else {
valid = download_all_enabled | | p_item - > is_checked ( 0 ) ;
2022-04-05 13:40:26 +03:00
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
if ( valid ) {
queued_files . insert ( p_item - > get_text ( 0 ) ) ;
if ( ! is_installed_tree ) {
queued_templates . insert ( p_item - > get_parent ( ) - > get_text ( 0 ) ) ;
}
2023-07-12 14:36:12 +02:00
}
2026-03-03 22:35:49 +01:00
} else {
for ( TreeItem * child = p_item - > get_first_child ( ) ; child ; child = child - > get_next ( ) ) {
_queue_download_tree_item ( child ) ;
}
}
}
2023-07-12 14:36:12 +02:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _process_download_queue ( ) {
queue_update_pending = false ;
2019-02-25 19:37:51 +01:00
2026-03-03 22:35:49 +01:00
int downloader_index = 0 ;
bool is_finished = true ;
for ( TreeItem * item : downloading_items ) {
FileMetadata * meta = _get_file_metadata ( item ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
is_finished = is_finished & & _status_is_finished ( meta - > download_status ) ;
if ( meta - > download_status ! = DownloadStatus : : PENDING ) {
2018-07-16 13:05:45 +06:00
continue ;
}
2026-03-25 21:48:28 +01:00
TemplateDownloader * downloader = _get_available_downloader ( & downloader_index ) ;
2026-03-03 22:35:49 +01:00
if ( ! downloader ) {
break ;
}
downloader_index + + ;
2017-03-20 23:31:41 -03:00
2026-03-25 21:48:28 +01:00
Error err = downloader - > download_template ( item - > get_text ( 0 ) , _get_current_mirror_url ( ) ) ;
2026-03-03 22:35:49 +01:00
if ( err = = OK ) {
meta - > download_status = DownloadStatus : : IN_PROGRESS ;
meta - > downloader = downloader ;
} else {
2026-03-25 21:48:28 +01:00
_item_download_failed ( item , vformat ( TTR ( " Download request failed: %s. " ) , TTR ( error_names [ err ] ) ) ) ;
2026-03-03 22:35:49 +01:00
}
}
2019-02-25 19:37:51 +01:00
2026-03-03 22:35:49 +01:00
if ( is_finished ) {
// Exit "downloading mode".
queued_templates . clear ( ) ;
downloading_items . clear ( ) ;
set_process_internal ( false ) ;
_update_template_tree_theme ( installed_templates_tree ) ;
_update_template_tree_theme ( available_templates_tree ) ;
_update_install_button ( ) ;
EditorNode : : get_bottom_panel ( ) - > get_progress_indicator ( ) - > hide ( ) ;
} else {
set_process_internal ( true ) ;
}
}
2019-02-25 19:37:51 +01:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _queue_process_download_queue ( ) {
if ( queue_update_pending ) {
return ;
}
callable_mp ( this , & ExportTemplateManager : : _process_download_queue ) . call_deferred ( ) ;
queue_update_pending = true ;
}
2019-02-25 19:37:51 +01:00
2026-03-25 21:48:28 +01:00
TemplateDownloader * ExportTemplateManager : : _get_available_downloader ( int * r_from_index ) {
2026-03-03 22:35:49 +01:00
int counter = - 1 ;
2026-03-25 21:48:28 +01:00
for ( TemplateDownloader * downloader : downloaders ) {
2026-03-03 22:35:49 +01:00
counter + + ;
if ( counter < * r_from_index ) {
continue ;
2019-02-25 19:37:51 +01:00
}
2026-03-25 21:48:28 +01:00
if ( ! downloader - > is_downloading ( ) ) {
2026-03-03 22:35:49 +01:00
* r_from_index = counter ;
return downloader ;
}
}
return nullptr ;
}
2019-02-25 19:37:51 +01:00
2026-03-25 21:48:28 +01:00
void ExportTemplateManager : : _download_request_completed ( const String & p_filename ) {
2026-03-03 22:35:49 +01:00
bool found = false ;
bool template_finished = false ;
2026-03-25 21:48:28 +01:00
queued_files . erase ( p_filename ) ;
2026-03-03 22:35:49 +01:00
for ( TreeItem * item : downloading_items ) {
2026-03-25 21:48:28 +01:00
if ( item - > get_text ( 0 ) ! = p_filename ) {
2026-03-03 22:35:49 +01:00
continue ;
2017-12-17 15:47:50 -03:00
}
2026-03-25 21:48:28 +01:00
item - > clear_buttons ( ) ;
2017-03-20 23:31:41 -03:00
2026-03-25 21:48:28 +01:00
FileMetadata * meta = _get_file_metadata ( p_filename ) ;
2026-03-03 22:35:49 +01:00
meta - > downloader = nullptr ;
2026-03-25 21:48:28 +01:00
meta - > download_status = DownloadStatus : : COMPLETED ;
meta - > is_missing = false ;
found = true ;
template_finished = _is_template_download_finished ( item - > get_parent ( ) ) ;
if ( template_finished ) {
queued_templates . erase ( item - > get_parent ( ) - > get_text ( 0 ) ) ;
}
break ;
}
if ( ! found ) {
ERR_FAIL_COND ( ! found ) ;
}
_queue_process_download_queue ( ) ;
2026-03-03 22:35:49 +01:00
2026-03-25 21:48:28 +01:00
if ( template_finished ) {
_update_template_tree ( ) ;
}
}
2017-03-20 23:31:41 -03:00
2026-03-25 21:48:28 +01:00
void ExportTemplateManager : : _download_request_failed ( const String & p_filename , const String & p_reason ) {
bool found = false ;
bool template_finished = false ;
queued_files . erase ( p_filename ) ;
for ( TreeItem * item : downloading_items ) {
if ( item - > get_text ( 0 ) ! = p_filename ) {
continue ;
2017-12-18 20:44:19 +01:00
}
2026-03-25 21:48:28 +01:00
FileMetadata * meta = _get_file_metadata ( p_filename ) ;
meta - > downloader = nullptr ;
_item_download_failed ( item , p_reason ) ;
2026-03-03 22:35:49 +01:00
found = true ;
template_finished = _is_template_download_finished ( item - > get_parent ( ) ) ;
if ( template_finished ) {
queued_templates . erase ( item - > get_parent ( ) - > get_text ( 0 ) ) ;
}
break ;
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
if ( ! found ) {
ERR_FAIL_COND ( ! found ) ;
2017-12-17 15:47:50 -03:00
}
2026-03-03 22:35:49 +01:00
_queue_process_download_queue ( ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
if ( template_finished ) {
_update_template_tree ( ) ;
}
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
bool ExportTemplateManager : : _is_template_download_finished ( TreeItem * p_template ) {
for ( TreeItem * child = p_template - > get_first_child ( ) ; child ; child = child - > get_next ( ) ) {
if ( ! downloading_items . has ( child ) ) {
continue ;
}
FileMetadata * meta = _get_file_metadata ( child ) ;
if ( ! _status_is_finished ( meta - > download_status ) ) {
return false ;
}
}
return true ;
2017-03-20 23:31:41 -03:00
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _apply_item_folding ( TreeItem * p_item , bool p_default ) {
if ( folding_cache . is_empty ( ) ) {
if ( p_default ) {
p_item - > set_collapsed ( true ) ;
}
} else {
bool * cached = folding_cache . getptr ( _get_item_path ( p_item ) ) ;
if ( cached ) {
p_item - > set_collapsed ( * cached ) ;
} else if ( p_default ) {
p_item - > set_collapsed ( true ) ;
}
2017-11-01 23:12:28 -03:00
}
2026-03-03 22:35:49 +01:00
}
2017-11-01 23:12:28 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _cancel_item_download ( TreeItem * p_item ) {
_item_download_failed ( p_item , TTR ( " Canceled by the user " ) ) ;
queued_files . erase ( p_item - > get_text ( 0 ) ) ;
FileMetadata * meta = _get_file_metadata ( p_item ) ;
if ( meta - > downloader ) {
2026-03-25 21:48:28 +01:00
meta - > downloader - > cancel_download ( ) ;
2026-03-03 22:35:49 +01:00
meta - > downloader = nullptr ;
2017-11-01 23:12:28 -03:00
}
2026-03-03 22:35:49 +01:00
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _item_download_failed ( TreeItem * p_item , const String & p_reason ) {
FileMetadata * meta = _get_file_metadata ( p_item ) ;
meta - > fail_reason = p_reason ;
meta - > download_status = DownloadStatus : : FAILED ;
p_item - > clear_buttons ( ) ;
_add_fail_reason_button ( p_item ) ;
2017-11-01 23:12:28 -03:00
}
2020-05-14 14:29:06 +02:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _add_fail_reason_button ( TreeItem * p_item , const String & p_filename ) {
FileMetadata * meta = _get_file_metadata ( p_filename . is_empty ( ) ? p_item - > get_text ( 0 ) : p_filename ) ;
p_item - > add_button ( 0 , theme_cache . failure_icon , ( int ) ButtonID : : FAIL ) ;
p_item - > set_button_tooltip_text ( 0 , - 1 , vformat ( TTR ( " Download failed. \n Reason: %s. " ) , meta - > fail_reason ) ) ;
}
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _set_item_type ( TreeItem * p_item , int p_type ) {
switch ( p_type ) {
case TreeItem : : CELL_MODE_CHECK : {
p_item - > set_cell_mode ( 0 , TreeItem : : CELL_MODE_CHECK ) ;
p_item - > set_editable ( 0 , true ) ;
2017-11-01 23:12:28 -03:00
} break ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
case TreeItem : : CELL_MODE_CUSTOM : {
p_item - > set_cell_mode ( 0 , TreeItem : : CELL_MODE_CUSTOM ) ;
p_item - > set_custom_draw_callback ( 0 , callable_mp ( this , & ExportTemplateManager : : _draw_item_progress ) ) ;
2017-11-01 23:12:28 -03:00
} break ;
}
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _setup_item_text ( TreeItem * p_item , const String & p_text ) {
if ( p_item = = p_item - > get_tree ( ) - > get_root ( ) ) {
if ( p_item - > get_tree ( ) = = installed_templates_tree ) {
p_item - > set_meta ( PATH_META , " installed/ " ) ;
} else {
p_item - > set_meta ( PATH_META , " available/ " ) ;
}
} else {
p_item - > set_text ( 0 , p_text ) ;
const String path = p_item - > get_parent ( ) - > get_meta ( PATH_META ) . operator String ( ) + p_text ;
p_item - > set_meta ( PATH_META , path ) ;
if ( p_item - > get_cell_mode ( 0 ) = = TreeItem : : CELL_MODE_CHECK ) {
int * checked = checked_cache . getptr ( path ) ;
if ( checked ) {
if ( * checked = = 1 ) {
p_item - > set_indeterminate ( 0 , true ) ;
} else {
p_item - > set_checked ( 0 , * checked = = 2 ) ;
}
}
}
2018-01-27 02:38:08 +07:00
}
2026-03-03 22:35:49 +01:00
}
2018-01-27 02:38:08 +07:00
2026-03-03 22:35:49 +01:00
ExportTemplateManager : : FileMetadata * ExportTemplateManager : : _get_file_metadata ( const String & p_text ) const {
FileMetadata * meta = file_metadata . getptr ( p_text ) ;
if ( likely ( meta ) ) {
return meta ;
2017-11-01 23:12:28 -03:00
}
2026-03-03 22:35:49 +01:00
HashMap < String , FileMetadata > : : Iterator it = file_metadata . insert ( p_text , FileMetadata ( ) ) ;
return & it - > value ;
2021-05-20 23:15:49 +03:00
}
2017-11-01 23:12:28 -03:00
2026-03-03 22:35:49 +01:00
ExportTemplateManager : : FileMetadata * ExportTemplateManager : : _get_file_metadata ( const TreeItem * p_item ) const {
return _get_file_metadata ( p_item - > get_text ( 0 ) ) ;
2017-11-01 23:12:28 -03:00
}
2026-03-03 22:35:49 +01:00
String ExportTemplateManager : : _get_item_path ( TreeItem * p_item ) const {
return p_item - > get_meta ( PATH_META , String ( ) ) ;
}
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
bool ExportTemplateManager : : _item_is_file ( TreeItem * p_item ) const {
return p_item - > get_meta ( FILE_META , false ) . operator bool ( ) ;
}
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
float ExportTemplateManager : : _get_download_progress ( const TreeItem * p_item ) const {
FileMetadata * meta = _get_file_metadata ( p_item ) ;
switch ( meta - > download_status ) {
case DownloadStatus : : NONE :
case DownloadStatus : : PENDING : {
return 0.0 ;
}
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : IN_PROGRESS : {
if ( ! meta - > downloader ) {
return 0.0 ;
2024-08-30 22:14:31 +08:00
}
2026-03-25 21:48:28 +01:00
return meta - > downloader - > get_download_progress ( ) ;
2026-03-03 22:35:49 +01:00
}
2025-02-24 03:37:03 +07:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : COMPLETED : {
return 1.0 ;
}
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : FAILED : {
return meta - > progress_cache ;
}
}
return 0.0 ;
}
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _draw_item_progress ( TreeItem * p_item , const Rect2 & p_rect ) {
Tree * owning_tree = p_item - > get_tree ( ) ;
owning_tree - > draw_rect ( p_rect , Color ( 0 , 0 , 0 , 0.5 ) ) ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
if ( ! _item_is_file ( p_item ) ) {
float progress = 0.0 ;
int item_count = 0 ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
bool has_fail = false ;
for ( TreeItem * child = p_item - > get_first_child ( ) ; child ; child = child - > get_next ( ) ) {
if ( ! downloading_items . has ( child ) ) {
continue ;
}
item_count + + ;
progress + = _get_download_progress ( child ) ;
2025-02-24 03:37:03 +07:00
2026-03-03 22:35:49 +01:00
FileMetadata * meta = _get_file_metadata ( child ) ;
has_fail = has_fail | | meta - > download_status = = DownloadStatus : : FAILED ;
}
progress / = item_count ;
owning_tree - > draw_rect ( Rect2 ( p_rect . position , Vector2 ( p_rect . size . x * progress , p_rect . size . y ) ) , has_fail ? theme_cache . download_failed_color : theme_cache . download_progress_color ) ;
return ;
}
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
FileMetadata * meta = _get_file_metadata ( p_item ) ;
switch ( meta - > download_status ) {
case DownloadStatus : : NONE : {
} break ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : PENDING : {
uint64_t frame = Engine : : get_singleton ( ) - > get_frames_drawn ( ) ;
const Ref < Texture2D > progress_texture = theme_cache . progress_icons [ frame / 4 % 8 ] ;
owning_tree - > draw_texture ( progress_texture , Vector2 ( p_rect . get_end ( ) . x - progress_texture - > get_width ( ) , p_rect . position . y + p_rect . size . y * 0.5 - progress_texture - > get_height ( ) * 0.5 ) ) ;
} break ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : IN_PROGRESS : {
float progress = _get_download_progress ( p_item ) ;
meta - > progress_cache = progress ;
owning_tree - > draw_rect ( Rect2 ( p_rect . position , Vector2 ( p_rect . size . x * progress , p_rect . size . y ) ) , theme_cache . download_progress_color ) ;
} break ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : COMPLETED : {
owning_tree - > draw_rect ( p_rect , theme_cache . download_progress_color ) ;
} break ;
2025-02-24 03:37:03 +07:00
2026-03-03 22:35:49 +01:00
case DownloadStatus : : FAILED : {
owning_tree - > draw_rect ( Rect2 ( p_rect . position , Vector2 ( p_rect . size . x * _get_download_progress ( p_item ) , p_rect . size . y ) ) , theme_cache . download_failed_color ) ;
2024-08-30 22:14:31 +08:00
} break ;
2026-03-03 22:35:49 +01:00
}
}
2024-11-23 18:40:16 +01:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_READY : {
EditorNode : : get_bottom_panel ( ) - > get_progress_indicator ( ) - > connect ( " clicked " , callable_mp ( this , & ExportTemplateManager : : popup_manager ) ) ;
} break ;
2024-11-23 18:40:16 +01:00
2026-03-03 22:35:49 +01:00
case NOTIFICATION_TRANSLATION_CHANGED : {
if ( template_data . is_empty ( ) ) {
break ;
}
platform_map [ PlatformID : : WINDOWS ] . group = TTR ( " Desktop " , " Platform Group " ) ;
platform_map [ PlatformID : : LINUX ] . group = TTR ( " Desktop " , " Platform Group " ) ;
platform_map [ PlatformID : : MACOS ] . group = TTR ( " Desktop " , " Platform Group " ) ;
platform_map [ PlatformID : : WEB ] . group = TTR ( " Web " , " Platform Group " ) ;
platform_map [ PlatformID : : ANDROID ] . group = TTR ( " Mobile " , " Platform Group " ) ;
platform_map [ PlatformID : : IOS ] . group = TTR ( " Mobile " , " Platform Group " ) ;
platform_map [ PlatformID : : COMMON ] . name = TTR ( " Common " ) ;
template_data [ TemplateID : : WEB_EXTENSIONS ] . name = TTR ( " Web with Extensions " ) ;
template_data [ TemplateID : : WEB_NOTHREADS ] . name = TTR ( " Web Single-Threaded " ) ;
template_data [ TemplateID : : WEB_EXTENSIONS_NOTHREADS ] . name = TTR ( " Web with Extensions Single-Threaded " ) ;
template_data [ TemplateID : : ANDROID_SOURCE ] . name = TTR ( " Android Source " ) ;
template_data [ TemplateID : : ANDROID_SOURCE ] . name = TTR ( " ICU Data " ) ;
} break ;
2024-11-23 18:40:16 +01:00
2026-03-03 22:35:49 +01:00
case NOTIFICATION_THEME_CHANGED : {
open_folder_button - > set_button_icon ( get_editor_theme_icon ( " Folder " ) ) ;
2026-03-25 21:48:28 +01:00
install_button - > set_button_icon ( get_editor_theme_icon ( " AssetStore " ) ) ;
2026-03-03 22:35:49 +01:00
open_mirror - > set_button_icon ( get_editor_theme_icon ( " ExternalLink " ) ) ;
2026-03-25 21:48:28 +01:00
theme_cache . install_icon = get_editor_theme_icon ( " AssetStore " ) ;
2026-03-03 22:35:49 +01:00
theme_cache . remove_icon = get_editor_theme_icon ( " Remove " ) ;
theme_cache . repair_icon = get_editor_theme_icon ( " Tools " ) ;
theme_cache . failure_icon = get_editor_theme_icon ( " NodeWarning " ) ;
theme_cache . cancel_icon = get_editor_theme_icon ( " Close " ) ;
for ( int i = 0 ; i < 8 ; i + + ) {
theme_cache . progress_icons [ i ] = get_editor_theme_icon ( " Progress " + itos ( i + 1 ) ) ;
}
2024-11-23 18:40:16 +01:00
2026-03-03 22:35:49 +01:00
theme_cache . current_version_color = get_theme_color ( " accent_color " , EditorStringName ( Editor ) ) ;
theme_cache . incomplete_template_color = get_theme_color ( " warning_color " , EditorStringName ( Editor ) ) ;
theme_cache . missing_file_color = get_theme_color ( " error_color " , EditorStringName ( Editor ) ) ;
theme_cache . download_progress_color = Color ( get_theme_color ( " success_color " , EditorStringName ( Editor ) ) , 0.5 ) ;
theme_cache . download_failed_color = Color ( theme_cache . missing_file_color , 0.5 ) ;
2024-08-30 22:14:31 +08:00
2026-03-03 22:35:49 +01:00
theme_cache . icon_width = get_theme_constant ( " class_icon_size " , EditorStringName ( Editor ) ) ;
} break ;
2018-01-31 18:15:06 -07:00
2026-03-03 22:35:49 +01:00
case NOTIFICATION_INTERNAL_PROCESS : {
available_templates_tree - > queue_redraw ( ) ;
installed_templates_tree - > queue_redraw ( ) ;
2026-03-20 21:03:46 +01:00
2026-03-03 22:35:49 +01:00
float progress = 0.0 ;
int indeterminate_count = download_count ;
for ( const TreeItem * item : downloading_items ) {
progress + = _get_download_progress ( item ) ;
indeterminate_count - - ;
}
progress + = indeterminate_count ;
EditorNode : : get_bottom_panel ( ) - > get_progress_indicator ( ) - > set_value ( progress / download_count ) ;
}
2020-03-06 14:00:16 -03:00
}
2017-11-01 23:12:28 -03:00
}
2024-02-13 14:20:23 -06:00
String ExportTemplateManager : : get_android_build_directory ( const Ref < EditorExportPreset > & p_preset ) {
if ( p_preset . is_valid ( ) ) {
String gradle_build_dir = p_preset - > get ( " gradle_build/gradle_build_directory " ) ;
if ( ! gradle_build_dir . is_empty ( ) ) {
return gradle_build_dir . path_join ( " build " ) ;
}
}
return " res://android/build " ;
}
String ExportTemplateManager : : get_android_source_zip ( const Ref < EditorExportPreset > & p_preset ) {
if ( p_preset . is_valid ( ) ) {
String android_source_zip = p_preset - > get ( " gradle_build/android_source_template " ) ;
if ( ! android_source_zip . is_empty ( ) ) {
return android_source_zip ;
}
}
2025-03-03 22:27:29 -08:00
const String templates_dir = EditorPaths : : get_singleton ( ) - > get_export_templates_dir ( ) . path_join ( GODOT_VERSION_FULL_CONFIG ) ;
2024-02-13 14:20:23 -06:00
return templates_dir . path_join ( " android_source.zip " ) ;
2019-04-07 15:46:52 -03:00
}
2024-02-13 14:20:23 -06:00
String ExportTemplateManager : : get_android_template_identifier ( const Ref < EditorExportPreset > & p_preset ) {
// The template identifier is the Godot version for the default template, and the full path plus md5 hash for custom templates.
if ( p_preset . is_valid ( ) ) {
String android_source_zip = p_preset - > get ( " gradle_build/android_source_template " ) ;
if ( ! android_source_zip . is_empty ( ) ) {
return android_source_zip + String ( " [ " ) + FileAccess : : get_md5 ( android_source_zip ) + String ( " ] " ) ;
}
}
2025-03-03 22:27:29 -08:00
return GODOT_VERSION_FULL_CONFIG ;
2024-02-13 14:20:23 -06:00
}
bool ExportTemplateManager : : is_android_template_installed ( const Ref < EditorExportPreset > & p_preset ) {
return DirAccess : : exists ( get_android_build_directory ( p_preset ) ) ;
}
bool ExportTemplateManager : : can_install_android_template ( const Ref < EditorExportPreset > & p_preset ) {
return FileAccess : : exists ( get_android_source_zip ( p_preset ) ) ;
}
Error ExportTemplateManager : : install_android_template ( const Ref < EditorExportPreset > & p_preset ) {
const String source_zip = get_android_source_zip ( p_preset ) ;
2021-07-15 10:12:56 -03:00
ERR_FAIL_COND_V ( ! FileAccess : : exists ( source_zip ) , ERR_CANT_OPEN ) ;
2024-02-13 14:20:23 -06:00
return install_android_template_from_file ( source_zip , p_preset ) ;
2021-07-15 10:12:56 -03:00
}
2024-02-13 14:20:23 -06:00
Error ExportTemplateManager : : install_android_template_from_file ( const String & p_file , const Ref < EditorExportPreset > & p_preset ) {
2019-09-02 17:31:51 -07:00
// To support custom Android builds, we install the Java source code and buildsystem
// from android_source.zip to the project's res://android folder.
2019-08-27 16:42:15 +02:00
2022-03-23 11:08:58 +02:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_RESOURCES ) ;
ERR_FAIL_COND_V ( da . is_null ( ) , ERR_CANT_CREATE ) ;
2019-04-07 15:46:52 -03:00
2024-02-13 14:20:23 -06:00
String build_dir = get_android_build_directory ( p_preset ) ;
String parent_dir = build_dir . get_base_dir ( ) ;
// Make parent of the build dir (if it does not exist).
da - > make_dir_recursive ( parent_dir ) ;
2019-04-07 15:46:52 -03:00
{
2024-02-13 14:20:23 -06:00
// Add identifier, to ensure building won't work if the current template doesn't match.
Ref < FileAccess > f = FileAccess : : open ( parent_dir . path_join ( " .build_version " ) , FileAccess : : WRITE ) ;
2022-03-23 11:08:58 +02:00
ERR_FAIL_COND_V ( f . is_null ( ) , ERR_CANT_CREATE ) ;
2024-02-13 14:20:23 -06:00
f - > store_line ( get_android_template_identifier ( p_preset ) ) ;
2019-04-07 15:46:52 -03:00
}
2023-07-02 11:14:29 -07:00
// Create the android build directory.
2024-02-13 14:20:23 -06:00
Error err = da - > make_dir_recursive ( build_dir ) ;
2019-04-07 15:46:52 -03:00
ERR_FAIL_COND_V ( err ! = OK , err ) ;
2019-10-18 09:59:04 -07:00
{
// Add an empty .gdignore file to avoid scan.
2024-02-13 14:20:23 -06:00
Ref < FileAccess > f = FileAccess : : open ( build_dir . path_join ( " .gdignore " ) , FileAccess : : WRITE ) ;
2022-03-23 11:08:58 +02:00
ERR_FAIL_COND_V ( f . is_null ( ) , ERR_CANT_CREATE ) ;
2019-10-18 09:59:04 -07:00
f - > store_line ( " " ) ;
}
2019-04-07 15:46:52 -03:00
2019-08-27 16:42:15 +02:00
// Uncompress source template.
2022-05-11 15:15:58 +03:00
Ref < FileAccess > io_fa ;
zlib_filefunc_def io = zipio_create_io ( & io_fa ) ;
2019-04-07 15:46:52 -03:00
2021-07-15 10:12:56 -03:00
unzFile pkg = unzOpen2 ( p_file . utf8 ( ) . get_data ( ) , & io ) ;
2023-09-28 11:40:18 +02:00
ERR_FAIL_NULL_V_MSG ( pkg , ERR_CANT_OPEN , " Android sources not in ZIP format. " ) ;
2019-04-07 15:46:52 -03:00
int ret = unzGoToFirstFile ( pkg ) ;
int total_files = 0 ;
2019-08-27 16:42:15 +02:00
// Count files to unzip.
2019-04-07 15:46:52 -03:00
while ( ret = = UNZ_OK ) {
total_files + + ;
ret = unzGoToNextFile ( pkg ) ;
}
ret = unzGoToFirstFile ( pkg ) ;
2019-08-27 16:42:15 +02:00
ProgressDialog : : get_singleton ( ) - > add_task ( " uncompress_src " , TTR ( " Uncompressing Android Build Sources " ) , total_files ) ;
2019-04-07 15:46:52 -03:00
2022-05-19 17:00:06 +02:00
HashSet < String > dirs_tested ;
2019-04-07 15:46:52 -03:00
int idx = 0 ;
while ( ret = = UNZ_OK ) {
2019-08-27 16:42:15 +02:00
// Get file path.
2019-04-07 15:46:52 -03:00
unz_file_info info ;
2019-08-27 16:42:15 +02:00
char fpath [ 16384 ] ;
2020-04-02 01:20:12 +02:00
ret = unzGetCurrentFileInfo ( pkg , & info , fpath , 16384 , nullptr , 0 , nullptr , 0 ) ;
2022-04-05 13:40:26 +03:00
if ( ret ! = UNZ_OK ) {
break ;
}
2019-04-07 15:46:52 -03:00
2022-01-05 14:27:11 +02:00
String path = String : : utf8 ( fpath ) ;
2019-08-27 16:42:15 +02:00
String base_dir = path . get_base_dir ( ) ;
2019-04-07 15:46:52 -03:00
2019-08-27 16:42:15 +02:00
if ( ! path . ends_with ( " / " ) ) {
2022-09-29 12:53:28 +03:00
Vector < uint8_t > uncomp_data ;
uncomp_data . resize ( info . uncompressed_size ) ;
2019-04-07 15:46:52 -03:00
2019-08-27 16:42:15 +02:00
// Read.
2019-04-07 15:46:52 -03:00
unzOpenCurrentFile ( pkg ) ;
2022-09-29 12:53:28 +03:00
unzReadCurrentFile ( pkg , uncomp_data . ptrw ( ) , uncomp_data . size ( ) ) ;
2019-04-07 15:46:52 -03:00
unzCloseCurrentFile ( pkg ) ;
if ( ! dirs_tested . has ( base_dir ) ) {
2024-02-13 14:20:23 -06:00
da - > make_dir_recursive ( build_dir . path_join ( base_dir ) ) ;
2019-04-07 15:46:52 -03:00
dirs_tested . insert ( base_dir ) ;
}
2024-02-13 14:20:23 -06:00
String to_write = build_dir . path_join ( path ) ;
2022-03-23 11:08:58 +02:00
Ref < FileAccess > f = FileAccess : : open ( to_write , FileAccess : : WRITE ) ;
if ( f . is_valid ( ) ) {
2022-09-29 12:53:28 +03:00
f - > store_buffer ( uncomp_data . ptr ( ) , uncomp_data . size ( ) ) ;
2022-04-12 10:12:40 +03:00
f . unref ( ) ; // close file.
2019-04-07 15:46:52 -03:00
# ifndef WINDOWS_ENABLED
FileAccess : : set_unix_permissions ( to_write , ( info . external_fa > > 16 ) & 0x01FF ) ;
# endif
} else {
2019-11-06 17:03:04 +01:00
ERR_PRINT ( " Can't uncompress file: " + to_write ) ;
2019-04-07 15:46:52 -03:00
}
}
2019-08-27 16:42:15 +02:00
ProgressDialog : : get_singleton ( ) - > task_step ( " uncompress_src " , path , idx ) ;
idx + + ;
ret = unzGoToNextFile ( pkg ) ;
}
ProgressDialog : : get_singleton ( ) - > end_task ( " uncompress_src " ) ;
unzClose ( pkg ) ;
2024-10-07 22:03:41 +05:30
EditorFileSystem : : get_singleton ( ) - > scan_changes ( ) ;
2019-04-07 15:46:52 -03:00
return OK ;
}
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : popup_manager ( ) {
if ( template_data . is_empty ( ) ) {
_initialize_template_data ( ) ;
}
_update_online_mode ( ) ;
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
if ( ! is_downloading ( ) ) {
_update_template_tree ( ) ;
_request_mirrors ( ) ;
}
popup_centered_clamped ( Vector2i ( 640 , 700 ) * EDSCALE ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
bool ExportTemplateManager : : is_downloading ( ) const {
return ! queued_files . is_empty ( ) ;
}
2017-03-20 23:31:41 -03:00
2026-03-03 22:35:49 +01:00
void ExportTemplateManager : : stop_download ( ) {
for ( TreeItem * item : downloading_items ) {
FileMetadata * meta = _get_file_metadata ( item ) ;
if ( meta & & ! _status_is_finished ( meta - > download_status ) ) {
_cancel_item_download ( item ) ;
}
2021-05-20 23:15:49 +03:00
}
}
2017-03-20 23:31:41 -03:00
2021-05-20 23:15:49 +03:00
ExportTemplateManager : : ExportTemplateManager ( ) {
2026-03-03 22:35:49 +01:00
set_title ( TTRC ( " Export Template Manager " ) ) ;
set_ok_button_text ( TTRC ( " Close " ) ) ;
2021-05-20 23:15:49 +03:00
VBoxContainer * main_vb = memnew ( VBoxContainer ) ;
add_child ( main_vb ) ;
2026-03-03 22:35:49 +01:00
HBoxContainer * download_header = memnew ( HBoxContainer ) ;
download_header - > set_alignment ( BoxContainer : : ALIGNMENT_BEGIN ) ;
main_vb - > add_child ( download_header ) ;
2021-05-20 23:15:49 +03:00
2026-03-03 22:35:49 +01:00
download_header - > add_child ( memnew ( Label ( TTRC ( " Download from: " ) ) ) ) ;
2021-05-20 23:15:49 +03:00
mirrors_list = memnew ( OptionButton ) ;
2025-03-21 09:55:22 +02:00
mirrors_list - > set_accessibility_name ( TTRC ( " Mirror " ) ) ;
2026-03-03 22:35:49 +01:00
download_header - > add_child ( mirrors_list ) ;
open_mirror = memnew ( Button ) ;
open_mirror - > set_tooltip_text ( TTRC ( " Open in Web Browser " ) ) ;
download_header - > add_child ( open_mirror ) ;
open_mirror - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & ExportTemplateManager : : _open_mirror ) ) ;
install_button = memnew ( Button ) ;
install_button - > set_h_size_flags ( Control : : SIZE_SHRINK_END | Control : : SIZE_EXPAND ) ;
download_header - > add_child ( install_button ) ;
install_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & ExportTemplateManager : : _install_templates ) . bind ( ( TreeItem * ) nullptr ) ) ;
HSplitContainer * main_split = memnew ( HSplitContainer ) ;
main_split - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
main_vb - > add_child ( main_split ) ;
VBoxContainer * side_vb = memnew ( VBoxContainer ) ;
main_split - > add_child ( side_vb ) ;
Label * version_header = memnew ( Label ( TTRC ( " Godot Version " ) ) ) ;
version_header - > set_theme_type_variation ( " HeaderSmall " ) ;
side_vb - > add_child ( version_header ) ;
version_list = memnew ( ItemList ) ;
version_list - > set_accessibility_name ( TTRC ( " Godot Version List " ) ) ;
version_list - > set_theme_type_variation ( " ItemListSecondary " ) ;
version_list - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
side_vb - > add_child ( version_list ) ;
version_list - > connect ( SceneStringName ( item_selected ) , callable_mp ( this , & ExportTemplateManager : : _version_selected ) . unbind ( 1 ) ) ;
open_folder_button = memnew ( Button ) ;
open_folder_button - > set_tooltip_text ( TTRC ( " Open templates directory. " ) ) ;
open_folder_button - > set_h_size_flags ( Control : : SIZE_SHRINK_BEGIN ) ;
side_vb - > add_child ( open_folder_button ) ;
open_folder_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & ExportTemplateManager : : _open_template_directory ) ) ;
VSplitContainer * center_split = memnew ( VSplitContainer ) ;
center_split - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
main_split - > add_child ( center_split ) ;
VBoxContainer * available_templates_container = memnew ( VBoxContainer ) ;
available_templates_container - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
center_split - > add_child ( available_templates_container ) ;
Label * template_header2 = memnew ( Label ( TTRC ( " Available Templates " ) ) ) ;
template_header2 - > set_theme_type_variation ( " HeaderSmall " ) ;
template_header2 - > set_h_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
available_templates_container - > add_child ( template_header2 ) ;
available_templates_tree = memnew ( Tree ) ;
available_templates_tree - > set_accessibility_name ( TTRC ( " Available Templates " ) ) ;
available_templates_tree - > set_hide_root ( true ) ;
available_templates_tree - > set_theme_type_variation ( " TreeSecondary " ) ;
available_templates_tree - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
available_templates_tree - > set_auto_translate_mode ( AUTO_TRANSLATE_MODE_DISABLED ) ;
available_templates_container - > add_child ( available_templates_tree ) ;
available_templates_tree - > connect ( " button_clicked " , callable_mp ( this , & ExportTemplateManager : : _tree_button_clicked ) ) ;
available_templates_tree - > connect ( " item_edited " , callable_mp ( this , & ExportTemplateManager : : _tree_item_edited ) ) ;
VBoxContainer * installed_templates_container = memnew ( VBoxContainer ) ;
installed_templates_container - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
center_split - > add_child ( installed_templates_container ) ;
Label * template_header = memnew ( Label ( TTRC ( " Installed Templates " ) ) ) ;
template_header - > set_theme_type_variation ( " HeaderSmall " ) ;
installed_templates_container - > add_child ( template_header ) ;
installed_templates_tree = memnew ( Tree ) ;
installed_templates_tree - > set_accessibility_name ( TTRC ( " Installed Templates " ) ) ;
installed_templates_tree - > set_hide_root ( true ) ;
installed_templates_tree - > set_theme_type_variation ( " TreeSecondary " ) ;
installed_templates_tree - > set_v_size_flags ( Control : : SIZE_EXPAND_FILL ) ;
installed_templates_tree - > set_auto_translate_mode ( AUTO_TRANSLATE_MODE_DISABLED ) ;
installed_templates_container - > add_child ( installed_templates_tree ) ;
installed_templates_tree - > connect ( " button_clicked " , callable_mp ( this , & ExportTemplateManager : : _tree_button_clicked ) ) ;
offline_container = memnew ( HBoxContainer ) ;
offline_container - > set_alignment ( BoxContainer : : ALIGNMENT_CENTER ) ;
offline_container - > hide ( ) ;
main_vb - > add_child ( offline_container ) ;
Label * offline_mode_label = memnew ( Label ( TTRC ( " Offline mode, some functionality is not available. " ) ) ) ;
offline_container - > add_child ( offline_mode_label ) ;
2025-02-24 03:37:03 +07:00
LinkButton * enable_online_button = memnew ( LinkButton ) ;
2026-03-03 22:35:49 +01:00
enable_online_button - > set_text ( TTRC ( " Go Online " ) ) ;
offline_container - > add_child ( enable_online_button ) ;
2025-02-24 03:37:03 +07:00
enable_online_button - > connect ( SceneStringName ( pressed ) , callable_mp ( this , & ExportTemplateManager : : _force_online_mode ) ) ;
2026-03-25 21:48:28 +01:00
confirm_delete = memnew ( ConfirmationDialog ) ;
confirm_delete - > set_text ( TTRC ( " Remove the selected template files? (Cannot be undone.) \n Depending on your filesystem configuration, the files will either be moved to the system trash or deleted permanently. " ) ) ;
add_child ( confirm_delete ) ;
confirm_delete - > connect ( SceneStringName ( confirmed ) , callable_mp ( this , & ExportTemplateManager : : _delete_confirmed ) ) ;
2026-03-03 22:35:49 +01:00
mirrors_requester = memnew ( HTTPRequest ) ;
mirrors_requester - > connect ( " request_completed " , callable_mp ( this , & ExportTemplateManager : : _mirrors_request_completed ) ) ;
add_child ( mirrors_requester ) ;
2026-03-25 21:48:28 +01:00
const String template_directory = _get_template_folder_path ( GODOT_VERSION_FULL_CONFIG ) ;
2026-03-03 22:35:49 +01:00
for ( int i = 0 ; i < 5 ; i + + ) {
2026-03-25 21:48:28 +01:00
TemplateDownloader * downloader = memnew ( TemplateDownloader ( template_directory ) ) ;
2026-03-03 22:35:49 +01:00
downloader - > set_use_threads ( true ) ;
add_child ( downloader ) ;
downloaders . push_back ( downloader ) ;
2026-03-25 21:48:28 +01:00
downloader - > connect ( " download_completed " , callable_mp ( this , & ExportTemplateManager : : _download_request_completed ) ) ;
downloader - > connect ( " download_failed " , callable_mp ( this , & ExportTemplateManager : : _download_request_failed ) ) ;
}
}
int TemplateDownloader : : _find_sequence_backwards ( const PackedByteArray & p_source , const PackedByteArray & p_target ) const {
const int64_t source_size = p_source . size ( ) ;
const int64_t target_size = p_target . size ( ) ;
if ( target_size = = 0 ) {
return - 1 ;
}
if ( target_size > source_size ) {
return - 1 ;
}
const uint8_t * src_ptr = p_source . ptr ( ) ;
const uint8_t * tgt_ptr = p_target . ptr ( ) ;
for ( int64_t i = source_size - target_size ; i > = 0 ; i - - ) {
if ( memcmp ( & src_ptr [ i ] , tgt_ptr , target_size ) = = 0 ) {
return ( int ) i ;
}
}
return - 1 ;
}
String TemplateDownloader : : _get_download_error ( int p_result , int p_response_code ) const {
switch ( p_result ) {
case HTTPRequest : : RESULT_CANT_RESOLVE :
return TTR ( " Can't resolve the requested address " ) ;
case HTTPRequest : : RESULT_BODY_SIZE_LIMIT_EXCEEDED :
case HTTPRequest : : RESULT_CONNECTION_ERROR :
case HTTPRequest : : RESULT_CHUNKED_BODY_SIZE_MISMATCH :
case HTTPRequest : : RESULT_TLS_HANDSHAKE_ERROR :
case HTTPRequest : : RESULT_CANT_CONNECT :
return TTR ( " Can't connect to the mirror " ) ;
case HTTPRequest : : RESULT_NO_RESPONSE :
return TTR ( " No response from the mirror " ) ;
case HTTPRequest : : RESULT_REQUEST_FAILED :
return TTR ( " Request failed " ) ;
case HTTPRequest : : RESULT_REDIRECT_LIMIT_REACHED :
return TTR ( " Request ended up in a redirect loop " ) ;
}
switch ( p_response_code ) {
case HTTPClient : : RESPONSE_FORBIDDEN :
return TTR ( " Forbidden " ) ;
case HTTPClient : : RESPONSE_NOT_FOUND :
return TTR ( " Not found " ) ;
default : // Handle only common errors.
return vformat ( TTR ( " Response code: %d " ) , p_response_code ) ;
}
}
void TemplateDownloader : : _request_completed ( int p_result , int p_response_code , const PackedStringArray & p_headers , const PackedByteArray & p_body ) {
switch ( current_step ) {
case Step : : WAITING : {
_download_failed ( String ( ) ) ; // Not really possible to happen, so just fail with empty message.
ERR_FAIL_MSG ( " Request completed on wrong step. " ) ;
} break ;
case Step : : QUERYING : {
if ( p_result ! = HTTPRequest : : RESULT_SUCCESS | | p_response_code ! = HTTPClient : : RESPONSE_OK ) {
_download_failed ( _get_download_error ( p_result , p_response_code ) ) ;
return ;
}
for ( const String & header : p_headers ) {
if ( header . to_lower ( ) . begins_with ( " content-length: " ) ) {
file_size = header . split ( " : " ) [ 1 ] . to_int ( ) ;
}
}
current_step = Step : : SCANNING ;
// Request the last 64 KB of the file to read the Central Directory.
const String tail_range = vformat ( " Range: bytes=%d-%d " , MAX ( 0 , file_size - 0x10000 ) , file_size - 1 ) ;
Error err = request ( url , PackedStringArray { tail_range } , HTTPClient : : METHOD_GET ) ;
if ( err ! = OK ) {
_download_failed ( vformat ( TTR ( " Download request failed: %s. " ) , TTR ( error_names [ err ] ) ) ) ;
}
} break ;
case Step : : SCANNING : {
if ( p_result ! = HTTPRequest : : RESULT_SUCCESS | | p_response_code ! = HTTPClient : : RESPONSE_PARTIAL_CONTENT ) {
_download_failed ( _get_download_error ( p_result , p_response_code ) ) ;
return ;
}
PackedByteArray eocd_sig = { 0x50 , 0x4b , 0x05 , 0x06 } ;
int eocd_pos = _find_sequence_backwards ( p_body , eocd_sig ) ;
if ( eocd_pos = = - 1 ) {
_download_failed ( TTR ( " Invalid template archive header. " ) ) ;
return ;
}
const uint8_t * tail_data = p_body . ptr ( ) ;
int total_entries = decode_uint16 ( tail_data + eocd_pos + 10 ) ;
int cd_start_offset = decode_uint32 ( tail_data + eocd_pos + 16 ) ;
int buffer_start_abs = file_size - p_body . size ( ) ;
int current_pos = cd_start_offset - buffer_start_abs ;
const String target_path = " templates/ " + filename ;
for ( int i = 0 ; i < total_entries ; i + + ) {
if ( decode_uint32 ( tail_data + current_pos ) ! = 0x02014b50 ) {
break ;
}
int comp_method = decode_uint16 ( tail_data + current_pos + 10 ) ; // 0 = Stored, 8 = Deflated
int comp_size = decode_uint32 ( tail_data + current_pos + 20 ) ;
int uncomp_size = decode_uint32 ( tail_data + current_pos + 24 ) ;
int name_len = decode_uint16 ( tail_data + current_pos + 28 ) ;
int extra_len = decode_uint16 ( tail_data + current_pos + 30 ) ;
int comm_len = decode_uint16 ( tail_data + current_pos + 32 ) ;
int local_offset = decode_uint32 ( tail_data + current_pos + 42 ) ;
int full_record_len = 46 + name_len + extra_len + comm_len ;
const PackedByteArray raw_record = p_body . slice ( current_pos , current_pos + full_record_len ) ;
const String file_name = String : : utf8 ( ( const char * ) p_body . slice ( current_pos + 46 , current_pos + 46 + name_len ) . ptr ( ) , name_len ) ;
if ( file_name = = target_path ) {
file_info . offset = local_offset ;
file_info . compressed_size = comp_size ;
file_info . uncompressed_size = uncomp_size ;
file_info . raw_record = raw_record ;
file_info . method = comp_method ;
file_info . name = file_name ;
break ;
}
current_pos + = full_record_len ;
}
if ( file_info . name . is_empty ( ) ) {
_download_failed ( TTR ( " Requested template not found in the archive. " ) ) ;
return ;
}
int start_byte = file_info . offset ;
int end_byte = file_info . offset + file_info . compressed_size + file_info . raw_record . size ( ) ;
current_step = Step : : DOWNLOADING ;
const String data_range = vformat ( " Range: bytes=%d-%d " , start_byte , end_byte ) ;
Error err = request ( url , PackedStringArray { data_range } , HTTPClient : : METHOD_GET ) ;
if ( err ! = OK ) {
_download_failed ( vformat ( TTR ( " Download request failed: %s. " ) , TTR ( error_names [ err ] ) ) ) ;
}
} break ;
case Step : : DOWNLOADING : {
if ( p_result ! = HTTPRequest : : RESULT_SUCCESS | | p_response_code ! = HTTPClient : : RESPONSE_PARTIAL_CONTENT ) {
_download_failed ( _get_download_error ( p_result , p_response_code ) ) ;
return ;
}
const String mini_zip_path = EditorPaths : : get_singleton ( ) - > get_temp_dir ( ) . path_join ( filename + " .zip " ) ;
const uint8_t * fragment = p_body . ptr ( ) ;
int local_name_len = decode_uint16 ( fragment + 26 ) ;
int local_extra_len = decode_uint16 ( fragment + 28 ) ;
int full_file_chunk_size = 30 + local_name_len + local_extra_len + file_info . compressed_size ;
if ( p_body . size ( ) < full_file_chunk_size ) {
_download_failed ( vformat ( TTR ( " Archive fragment too small. Loaded: %d, required: %d. " ) , p_body . size ( ) , full_file_chunk_size ) ) ;
return ;
}
const PackedByteArray clean_fragment = p_body . slice ( 0 , full_file_chunk_size ) ;
PackedByteArray cd_record = file_info . raw_record . duplicate ( ) ;
uint8_t * record_write = cd_record . ptrw ( ) ;
encode_uint32 ( 0 , record_write + 42 ) ; // IMPORTANT: Set the offset to 0, as the file is at the very beginning of the mini-ZIP.
// EOCD (End of Central Directory)
PackedByteArray eocd ;
eocd . resize_initialized ( 22 ) ;
uint8_t * eocd_write = eocd . ptrw ( ) ;
encode_uint32 ( 0x06054b50 , eocd_write ) ; // Signature (4 bytes).
// Offsets 4-7 remain 0 (disk numbers).
encode_uint16 ( 1 , eocd_write + 8 ) ; // Number of entries on this disk (2 bytes).
encode_uint16 ( 1 , eocd_write + 10 ) ; // Total number of entries (2 bytes).
encode_uint32 ( cd_record . size ( ) , eocd_write + 12 ) ; // Central Directory size (4 bytes).
encode_uint32 ( clean_fragment . size ( ) , eocd_write + 16 ) ; // CD start offset (after file data) (4 bytes).
// Write Mini-Zip to a file.
Ref < FileAccess > f = FileAccess : : open ( mini_zip_path , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
_download_failed ( TTR ( " Failed to open mini-ZIP for writing. " ) ) ;
return ;
}
f - > store_buffer ( clean_fragment ) ;
f - > store_buffer ( cd_record ) ;
f - > store_buffer ( eocd ) ;
f . unref ( ) ;
PackedByteArray extracted_data ;
{
// Read back the mini-ZIP.
Ref < FileAccess > zip_access ;
zlib_filefunc_def io = zipio_create_io ( & zip_access ) ;
unzFile uzf = unzOpen2 ( mini_zip_path . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! uzf ) {
_download_failed ( TTR ( " ZIP reader could not open mini-ZIP. " ) ) ;
return ;
}
// IMPORTANT: The path in the ZIP reader must exactly match the one in the CD.
int err = UNZ_OK ;
// Locate and open the file.
err = godot_unzip_locate_file ( uzf , file_info . name , true ) ;
if ( err ! = UNZ_OK ) {
_download_failed ( TTR ( " File does not exist in zip archive. " ) ) ;
return ;
}
err = unzOpenCurrentFile ( uzf ) ;
if ( err ! = UNZ_OK ) {
_download_failed ( TTR ( " Could not open file within zip archive. " ) ) ;
return ;
}
// Read the file info.
unz_file_info info ;
err = unzGetCurrentFileInfo ( uzf , & info , nullptr , 0 , nullptr , 0 , nullptr , 0 ) ;
if ( err ! = UNZ_OK ) {
_download_failed ( TTR ( " Unable to read file information from zip archive. " ) ) ;
return ;
}
// Read the file data.
extracted_data . resize ( info . uncompressed_size ) ;
uint8_t * buffer = extracted_data . ptrw ( ) ;
int to_read = extracted_data . size ( ) ;
while ( to_read > 0 ) {
int bytes_read = unzReadCurrentFile ( uzf , buffer , to_read ) ;
if ( bytes_read < 0 | | ( bytes_read = = UNZ_EOF & & to_read ! = 0 ) ) {
_download_failed ( TTR ( " IO/zlib error reading file from zip archive. " ) ) ;
return ;
}
buffer + = bytes_read ;
to_read - = bytes_read ;
}
// Verify the data and return.
err = unzCloseCurrentFile ( uzf ) ;
if ( err ! = UNZ_OK ) {
_download_failed ( TTR ( " CRC error reading file from zip archive. " ) ) ;
return ;
}
}
if ( extracted_data . is_empty ( ) ) {
_download_failed ( TTR ( " Mini-ZIP data was empty. " ) ) ;
// The mini-ZIP is not deleted for inspection.
return ;
} else {
DirAccess : : remove_absolute ( mini_zip_path ) ;
f = FileAccess : : open ( target_directory . path_join ( filename ) , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
_download_failed ( TTR ( " Failed to template file for writing. " ) ) ;
return ;
}
f - > store_buffer ( extracted_data ) ;
f . unref ( ) ;
current_step = Step : : WAITING ;
emit_signal ( SNAME ( " download_completed " ) , filename ) ;
}
} break ;
}
}
void TemplateDownloader : : _download_failed ( const String & p_reason ) {
const String failed_file = filename ;
cancel_download ( ) ;
emit_signal ( SNAME ( " download_failed " ) , failed_file , p_reason ) ;
}
void TemplateDownloader : : _notification ( int p_what ) {
switch ( p_what ) {
case NOTIFICATION_POSTINITIALIZE : {
connect ( SNAME ( " request_completed " ) , callable_mp ( this , & TemplateDownloader : : _request_completed ) , CONNECT_DEFERRED ) ;
} break ;
}
}
void TemplateDownloader : : _bind_methods ( ) {
ADD_SIGNAL ( MethodInfo ( " download_completed " ) ) ;
ADD_SIGNAL ( MethodInfo ( " download_failed " ) ) ;
}
Error TemplateDownloader : : download_template ( const String & p_file_name , const String & p_source ) {
url = p_source ;
filename = p_file_name ;
current_step = Step : : QUERYING ;
return request ( p_source , PackedStringArray ( ) , HTTPClient : : METHOD_HEAD ) ;
}
void TemplateDownloader : : cancel_download ( ) {
cancel_request ( ) ;
current_step = Step : : WAITING ;
filename = String ( ) ;
url = String ( ) ;
file_size = 0 ;
file_info = FileInfo ( ) ;
}
float TemplateDownloader : : get_download_progress ( ) const {
if ( current_step = = Step : : DOWNLOADING ) {
return ( float ) get_downloaded_bytes ( ) / get_body_size ( ) ;
2026-03-03 22:35:49 +01:00
}
2026-03-25 21:48:28 +01:00
return 0.0f ;
2017-03-20 23:31:41 -03:00
}