Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add "Pack Project as ZIP..." to Project menu #99781

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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"
Expand Down Expand Up @@ -2179,6 +2180,15 @@ void EditorNode::_dialog_action(String p_file) {

} break;

case FILE_PACK_PROJECT_AS_ZIP: {
ProjectZIPPacker::pack_project_zip(p_file);
{
Ref<FileAccess> f = FileAccess::open(p_file, FileAccess::READ);
ERR_FAIL_COND_MSG(f.is_null(), "Unable to create ZIP file.");
}

} break;

case RESOURCE_SAVE:
case RESOURCE_SAVE_AS: {
ERR_FAIL_COND(saving_resource.is_null());
Expand Down Expand Up @@ -2838,6 +2848,20 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
project_export->popup_export();
} break;

case FILE_PACK_PROJECT_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_EXTERNAL_OPEN_SCENE: {
if (unsaved_cache && !p_confirmed) {
confirmation->set_ok_button_text(TTR("Open"));
Expand Down Expand Up @@ -7312,6 +7336,7 @@ EditorNode::EditorNode() {

project_menu->add_separator();
project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT);
project_menu->add_item(TTR("Pack Project as ZIP..."), FILE_PACK_PROJECT_AS_ZIP);
#ifndef ANDROID_ENABLED
project_menu->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE);
project_menu->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER);
Expand Down
1 change: 1 addition & 0 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class EditorNode : public Node {
FILE_RUN_SCENE,
FILE_SHOW_IN_FILESYSTEM,
FILE_EXPORT_PROJECT,
FILE_PACK_PROJECT_AS_ZIP,
FILE_EXPORT_MESH_LIBRARY,
FILE_INSTALL_ANDROID_SOURCE,
FILE_EXPLORE_ANDROID_BUILD_TEMPLATES,
Expand Down
118 changes: 118 additions & 0 deletions editor/export/project_zip_packer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**************************************************************************/
/* 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/os/os.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<FileAccess> 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<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
if (f.is_null()) {
WARN_PRINT("Unable to open file for zipping: " + p_path);
return;
}
Vector<uint8_t> 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<DirAccess> 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();
}
}
49 changes: 49 additions & 0 deletions editor/export/project_zip_packer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**************************************************************************/
/* 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/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/io/zip_io.h"
#include "core/os/time.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
81 changes: 3 additions & 78 deletions platform/web/editor/web_tools_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <emscripten/emscripten.h>

Expand All @@ -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<FileAccess> 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<FileAccess> f = FileAccess::open(output_path, FileAccess::READ);
ERR_FAIL_COND_MSG(f.is_null(), "Unable to create ZIP file.");
Expand All @@ -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<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
if (f.is_null()) {
WARN_PRINT("Unable to open file for zipping: " + p_path);
return;
}
Vector<uint8_t> 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<DirAccess> 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();
}
}
2 changes: 0 additions & 2 deletions platform/web/editor/web_tools_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down