diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 7f46bfb4201..86900774b7a 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -103,6 +103,7 @@ #include "editor/export/editor_export.h" #include "editor/export/export_template_manager.h" #include "editor/export/project_export.h" +#include "editor/export/project_zip_packer.h" #include "editor/fbx_importer_manager.h" #include "editor/filesystem_dock.h" #include "editor/gui/editor_bottom_panel.h" @@ -2204,6 +2205,15 @@ void EditorNode::_dialog_action(String p_file) { } break; + case PROJECT_PACK_AS_ZIP: { + ProjectZIPPacker::pack_project_zip(p_file); + { + Ref f = FileAccess::open(p_file, FileAccess::READ); + ERR_FAIL_COND_MSG(f.is_null(), vformat("Unable to create ZIP file at: %s. Check for write permissions and whether you have enough disk space left.", p_file)); + } + + } break; + case RESOURCE_SAVE: case RESOURCE_SAVE_AS: { ERR_FAIL_COND(saving_resource.is_null()); @@ -2869,6 +2879,20 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { project_export->popup_export(); } break; + case PROJECT_PACK_AS_ZIP: { + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + const String base_path = resource_path.substr(0, resource_path.rfind_char('/')) + "/"; + + file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file->set_access(EditorFileDialog::ACCESS_FILESYSTEM); + file->clear_filters(); + file->set_current_path(base_path); + file->set_current_file(ProjectZIPPacker::get_project_zip_safe_name()); + file->add_filter("*.zip", "ZIP Archive"); + file->set_title(TTR("Pack Project as ZIP...")); + file->popup_file_dialog(); + } break; + case FILE_UNDO: { if ((int)Input::get_singleton()->get_mouse_button_mask() & 0x7) { log->add_message(TTR("Can't undo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR); @@ -7367,6 +7391,7 @@ EditorNode::EditorNode() { project_menu->add_separator(); project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTRC("Export..."), Key::NONE, TTRC("Export")), PROJECT_EXPORT); + project_menu->add_item(TTR("Pack Project as ZIP..."), PROJECT_PACK_AS_ZIP); #ifndef ANDROID_ENABLED project_menu->add_item(TTR("Install Android Build Template..."), PROJECT_INSTALL_ANDROID_SOURCE); project_menu->add_item(TTR("Open User Data Folder"), PROJECT_OPEN_USER_DATA_FOLDER); diff --git a/editor/editor_node.h b/editor/editor_node.h index a3b3f0620b1..4866809d919 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -157,6 +157,7 @@ public: PROJECT_OPEN_SETTINGS, PROJECT_VERSION_CONTROL, PROJECT_EXPORT, + PROJECT_PACK_AS_ZIP, PROJECT_INSTALL_ANDROID_SOURCE, PROJECT_OPEN_USER_DATA_FOLDER, PROJECT_RELOAD_CURRENT_PROJECT, diff --git a/editor/export/project_zip_packer.cpp b/editor/export/project_zip_packer.cpp new file mode 100644 index 00000000000..2595bb016ba --- /dev/null +++ b/editor/export/project_zip_packer.cpp @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* project_zip_packer.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. */ +/**************************************************************************/ + +#include "project_zip_packer.h" + +#include "core/config/project_settings.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/os/os.h" +#include "core/os/time.h" + +String ProjectZIPPacker::get_project_zip_safe_name() { + // Name the downloaded ZIP file to contain the project name and download date for easier organization. + // Replace characters not allowed (or risky) in Windows file names with safe characters. + // In the project name, all invalid characters become an empty string so that a name + // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". + const String project_name = GLOBAL_GET("application/config/name"); + const String project_name_safe = project_name.to_lower().replace(" ", "_"); + const String datetime_safe = + Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); + const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip", project_name_safe, datetime_safe)); + return output_name; +} + +void ProjectZIPPacker::pack_project_zip(const String &p_path) { + Ref io_fa; + zlib_filefunc_def io = zipio_create_io(&io_fa); + + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + const String base_path = resource_path.substr(0, resource_path.rfind_char('/')) + "/"; + + zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); + _zip_recursive(resource_path, base_path, zip); + zipClose(zip, nullptr); +} + +void ProjectZIPPacker::_zip_file(const String &p_path, const String &p_base_path, zipFile p_zip) { + Ref f = FileAccess::open(p_path, FileAccess::READ); + if (f.is_null()) { + WARN_PRINT("Unable to open file for zipping: " + p_path); + return; + } + Vector data; + uint64_t len = f->get_length(); + data.resize(len); + f->get_buffer(data.ptrw(), len); + + String path = p_path.replace_first(p_base_path, ""); + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + nullptr, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipWriteInFileInZip(p_zip, data.ptr(), data.size()); + zipCloseFileInZip(p_zip); +} + +void ProjectZIPPacker::_zip_recursive(const String &p_path, const String &p_base_path, zipFile p_zip) { + Ref dir = DirAccess::open(p_path); + if (dir.is_null()) { + WARN_PRINT("Unable to open directory for zipping: " + p_path); + return; + } + dir->list_dir_begin(); + String cur = dir->get_next(); + String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); + while (!cur.is_empty()) { + String cs = p_path.path_join(cur); + if (cur == "." || cur == ".." || cur == project_data_dir_name) { + // Skip + } else if (dir->current_is_dir()) { + String path = cs.replace_first(p_base_path, "") + "/"; + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + nullptr, + nullptr, + 0, + nullptr, + 0, + nullptr, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipCloseFileInZip(p_zip); + _zip_recursive(cs, p_base_path, p_zip); + } else { + _zip_file(cs, p_base_path, p_zip); + } + cur = dir->get_next(); + } +} diff --git a/editor/export/project_zip_packer.h b/editor/export/project_zip_packer.h new file mode 100644 index 00000000000..d09c2ec2438 --- /dev/null +++ b/editor/export/project_zip_packer.h @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* project_zip_packer.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef PROJECT_ZIP_PACKER_H +#define PROJECT_ZIP_PACKER_H + +#include "core/io/zip_io.h" +#include "core/variant/variant.h" + +class ProjectZIPPacker { + static void _zip_file(const String &p_path, const String &p_base_path, zipFile p_zip); + static void _zip_recursive(const String &p_path, const String &p_base_path, zipFile p_zip); + +public: + static String get_project_zip_safe_name(); + static void pack_project_zip(const String &p_path); +}; + +#endif // PROJECT_ZIP_PACKER_H diff --git a/platform/web/editor/web_tools_editor_plugin.cpp b/platform/web/editor/web_tools_editor_plugin.cpp index a98d45a49a6..1154ef2d3da 100644 --- a/platform/web/editor/web_tools_editor_plugin.cpp +++ b/platform/web/editor/web_tools_editor_plugin.cpp @@ -36,6 +36,7 @@ #include "core/io/file_access.h" #include "core/os/time.h" #include "editor/editor_node.h" +#include "editor/export/project_zip_packer.h" #include @@ -61,26 +62,10 @@ void WebToolsEditorPlugin::_download_zip() { ERR_PRINT("Downloading the project as a ZIP archive is only available in Editor mode."); return; } - String resource_path = ProjectSettings::get_singleton()->get_resource_path(); - - Ref io_fa; - zlib_filefunc_def io = zipio_create_io(&io_fa); - - // Name the downloaded ZIP file to contain the project name and download date for easier organization. - // Replace characters not allowed (or risky) in Windows file names with safe characters. - // In the project name, all invalid characters become an empty string so that a name - // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". - const String project_name = GLOBAL_GET("application/config/name"); - const String project_name_safe = project_name.to_lower().replace(" ", "_"); - const String datetime_safe = - Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); - const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip", project_name_safe, datetime_safe)); + const String output_name = ProjectZIPPacker::get_project_zip_safe_name(); const String output_path = String("/tmp").path_join(output_name); + ProjectZIPPacker::pack_project_zip(output_path); - zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); - const String base_path = resource_path.substr(0, resource_path.rfind_char('/')) + "/"; - _zip_recursive(resource_path, base_path, zip); - zipClose(zip, nullptr); { Ref f = FileAccess::open(output_path, FileAccess::READ); ERR_FAIL_COND_MSG(f.is_null(), "Unable to create ZIP file."); @@ -93,63 +78,3 @@ void WebToolsEditorPlugin::_download_zip() { // Remove the temporary file since it was sent to the user's native filesystem as a download. DirAccess::remove_file_or_error(output_path); } - -void WebToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { - Ref f = FileAccess::open(p_path, FileAccess::READ); - if (f.is_null()) { - WARN_PRINT("Unable to open file for zipping: " + p_path); - return; - } - Vector data; - uint64_t len = f->get_length(); - data.resize(len); - f->get_buffer(data.ptrw(), len); - - String path = p_path.replace_first(p_base_path, ""); - zipOpenNewFileInZip(p_zip, - path.utf8().get_data(), - nullptr, - nullptr, - 0, - nullptr, - 0, - nullptr, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - zipWriteInFileInZip(p_zip, data.ptr(), data.size()); - zipCloseFileInZip(p_zip); -} - -void WebToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { - Ref dir = DirAccess::open(p_path); - if (dir.is_null()) { - WARN_PRINT("Unable to open directory for zipping: " + p_path); - return; - } - dir->list_dir_begin(); - String cur = dir->get_next(); - String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); - while (!cur.is_empty()) { - String cs = p_path.path_join(cur); - if (cur == "." || cur == ".." || cur == project_data_dir_name) { - // Skip - } else if (dir->current_is_dir()) { - String path = cs.replace_first(p_base_path, "") + "/"; - zipOpenNewFileInZip(p_zip, - path.utf8().get_data(), - nullptr, - nullptr, - 0, - nullptr, - 0, - nullptr, - Z_DEFLATED, - Z_DEFAULT_COMPRESSION); - zipCloseFileInZip(p_zip); - _zip_recursive(cs, p_base_path, p_zip); - } else { - _zip_file(cs, p_base_path, p_zip); - } - cur = dir->get_next(); - } -} diff --git a/platform/web/editor/web_tools_editor_plugin.h b/platform/web/editor/web_tools_editor_plugin.h index 70b47ab49cb..856e944f985 100644 --- a/platform/web/editor/web_tools_editor_plugin.h +++ b/platform/web/editor/web_tools_editor_plugin.h @@ -38,8 +38,6 @@ class WebToolsEditorPlugin : public EditorPlugin { GDCLASS(WebToolsEditorPlugin, EditorPlugin); private: - void _zip_file(String p_path, String p_base_path, zipFile p_zip); - void _zip_recursive(String p_path, String p_base_path, zipFile p_zip); void _download_zip(); public: