1 /*************************************************************************/
2 /* export_template_manager.cpp */
3 /*************************************************************************/
4 /* This file is part of: */
5 /* GODOT ENGINE */
6 /* https://godotengine.org */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
10 /* */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the */
13 /* "Software"), to deal in the Software without restriction, including */
14 /* without limitation the rights to use, copy, modify, merge, publish, */
15 /* distribute, sublicense, and/or sell copies of the Software, and to */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions: */
18 /* */
19 /* The above copyright notice and this permission notice shall be */
20 /* included in all copies or substantial portions of the Software. */
21 /* */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29 /*************************************************************************/
30
31 #include "export_template_manager.h"
32
33 #include "core/io/json.h"
34 #include "core/io/zip_io.h"
35 #include "core/os/dir_access.h"
36 #include "core/os/input.h"
37 #include "core/os/keyboard.h"
38 #include "core/version.h"
39 #include "editor_node.h"
40 #include "editor_scale.h"
41 #include "progress_dialog.h"
42 #include "scene/gui/link_button.h"
43
_update_template_list()44 void ExportTemplateManager::_update_template_list() {
45
46 while (current_hb->get_child_count()) {
47 memdelete(current_hb->get_child(0));
48 }
49
50 while (installed_vb->get_child_count()) {
51 memdelete(installed_vb->get_child(0));
52 }
53
54 DirAccess *d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
55 Error err = d->change_dir(EditorSettings::get_singleton()->get_templates_dir());
56
57 Set<String> templates;
58 d->list_dir_begin();
59 if (err == OK) {
60
61 String c = d->get_next();
62 while (c != String()) {
63 if (d->current_is_dir() && !c.begins_with(".")) {
64 templates.insert(c);
65 }
66 c = d->get_next();
67 }
68 }
69 d->list_dir_end();
70
71 memdelete(d);
72
73 String current_version = VERSION_FULL_CONFIG;
74 // Downloadable export templates are only available for stable and official alpha/beta/RC builds
75 // (which always have a number following their status, e.g. "alpha1").
76 // Therefore, don't display download-related features when using a development version
77 // (whose builds aren't numbered).
78 const bool downloads_available =
79 String(VERSION_STATUS) != String("dev") &&
80 String(VERSION_STATUS) != String("alpha") &&
81 String(VERSION_STATUS) != String("beta") &&
82 String(VERSION_STATUS) != String("rc");
83
84 Label *current = memnew(Label);
85 current->set_h_size_flags(SIZE_EXPAND_FILL);
86 current_hb->add_child(current);
87
88 if (templates.has(current_version)) {
89 current->add_color_override("font_color", get_color("success_color", "Editor"));
90
91 // Only display a redownload button if it can be downloaded in the first place
92 if (downloads_available) {
93 Button *redownload = memnew(Button);
94 redownload->set_text(TTR("Redownload"));
95 current_hb->add_child(redownload);
96 redownload->connect("pressed", this, "_download_template", varray(current_version));
97 }
98
99 Button *uninstall = memnew(Button);
100 uninstall->set_text(TTR("Uninstall"));
101 current_hb->add_child(uninstall);
102 current->set_text(current_version + " " + TTR("(Installed)"));
103 uninstall->connect("pressed", this, "_uninstall_template", varray(current_version));
104
105 } else {
106 current->add_color_override("font_color", get_color("error_color", "Editor"));
107 Button *redownload = memnew(Button);
108 redownload->set_text(TTR("Download"));
109
110 if (!downloads_available) {
111 redownload->set_disabled(true);
112 redownload->set_tooltip(TTR("Official export templates aren't available for development builds."));
113 }
114
115 redownload->connect("pressed", this, "_download_template", varray(current_version));
116 current_hb->add_child(redownload);
117 current->set_text(current_version + " " + TTR("(Missing)"));
118 }
119
120 for (Set<String>::Element *E = templates.back(); E; E = E->prev()) {
121
122 HBoxContainer *hbc = memnew(HBoxContainer);
123 Label *version = memnew(Label);
124 version->set_modulate(get_color("disabled_font_color", "Editor"));
125 String text = E->get();
126 if (text == current_version) {
127 text += " " + TTR("(Current)");
128 }
129 version->set_text(text);
130 version->set_h_size_flags(SIZE_EXPAND_FILL);
131 hbc->add_child(version);
132
133 Button *uninstall = memnew(Button);
134
135 uninstall->set_text(TTR("Uninstall"));
136 hbc->add_child(uninstall);
137 uninstall->connect("pressed", this, "_uninstall_template", varray(E->get()));
138
139 installed_vb->add_child(hbc);
140 }
141 }
142
_download_template(const String & p_version)143 void ExportTemplateManager::_download_template(const String &p_version) {
144
145 while (template_list->get_child_count()) {
146 memdelete(template_list->get_child(0));
147 }
148 template_downloader->popup_centered_minsize();
149 template_list_state->set_text(TTR("Retrieving mirrors, please wait..."));
150 template_download_progress->set_max(100);
151 template_download_progress->set_value(0);
152 request_mirror->request("https://godotengine.org/mirrorlist/" + p_version + ".json");
153 template_list_state->show();
154 template_download_progress->show();
155 }
156
_uninstall_template(const String & p_version)157 void ExportTemplateManager::_uninstall_template(const String &p_version) {
158
159 remove_confirm->set_text(vformat(TTR("Remove template version '%s'?"), p_version));
160 remove_confirm->popup_centered_minsize();
161 to_remove = p_version;
162 }
163
_uninstall_template_confirm()164 void ExportTemplateManager::_uninstall_template_confirm() {
165
166 DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
167 const String &templates_dir = EditorSettings::get_singleton()->get_templates_dir();
168 Error err = da->change_dir(templates_dir);
169 ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir + "'.");
170 err = da->change_dir(to_remove);
171 ERR_FAIL_COND_MSG(err != OK, "Could not access templates directory at '" + templates_dir.plus_file(to_remove) + "'.");
172
173 err = da->erase_contents_recursive();
174 ERR_FAIL_COND_MSG(err != OK, "Could not remove all templates in '" + templates_dir.plus_file(to_remove) + "'.");
175
176 da->change_dir("..");
177 err = da->remove(to_remove);
178 ERR_FAIL_COND_MSG(err != OK, "Could not remove templates directory at '" + templates_dir.plus_file(to_remove) + "'.");
179
180 _update_template_list();
181 }
182
_install_from_file(const String & p_file,bool p_use_progress)183 bool ExportTemplateManager::_install_from_file(const String &p_file, bool p_use_progress) {
184
185 // unzClose() will take care of closing the file stored in the unzFile,
186 // so we don't need to `memdelete(fa)` in this method.
187 FileAccess *fa = NULL;
188 zlib_filefunc_def io = zipio_create_io_from_file(&fa);
189
190 unzFile pkg = unzOpen2(p_file.utf8().get_data(), &io);
191 if (!pkg) {
192
193 EditorNode::get_singleton()->show_warning(TTR("Can't open export templates zip."));
194 return false;
195 }
196 int ret = unzGoToFirstFile(pkg);
197
198 int fc = 0; //count them and find version
199 String version;
200 String contents_dir;
201
202 while (ret == UNZ_OK) {
203
204 unz_file_info info;
205 char fname[16384];
206 ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
207
208 String file = fname;
209
210 if (file.ends_with("version.txt")) {
211
212 Vector<uint8_t> data;
213 data.resize(info.uncompressed_size);
214
215 //read
216 unzOpenCurrentFile(pkg);
217 ret = unzReadCurrentFile(pkg, data.ptrw(), data.size());
218 unzCloseCurrentFile(pkg);
219
220 String data_str;
221 data_str.parse_utf8((const char *)data.ptr(), data.size());
222 data_str = data_str.strip_edges();
223
224 // Version number should be of the form major.minor[.patch].status[.module_config]
225 // so it can in theory have 3 or more slices.
226 if (data_str.get_slice_count(".") < 3) {
227 EditorNode::get_singleton()->show_warning(vformat(TTR("Invalid version.txt format inside templates: %s."), data_str));
228 unzClose(pkg);
229 return false;
230 }
231
232 version = data_str;
233 contents_dir = file.get_base_dir().trim_suffix("/").trim_suffix("\\");
234 }
235
236 if (file.get_file().size() != 0) {
237 fc++;
238 }
239
240 ret = unzGoToNextFile(pkg);
241 }
242
243 if (version == String()) {
244 EditorNode::get_singleton()->show_warning(TTR("No version.txt found inside templates."));
245 unzClose(pkg);
246 return false;
247 }
248
249 String template_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(version);
250
251 DirAccessRef d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
252 Error err = d->make_dir_recursive(template_path);
253 if (err != OK) {
254 EditorNode::get_singleton()->show_warning(TTR("Error creating path for templates:") + "\n" + template_path);
255 unzClose(pkg);
256 return false;
257 }
258
259 ret = unzGoToFirstFile(pkg);
260
261 EditorProgress *p = NULL;
262 if (p_use_progress) {
263 p = memnew(EditorProgress("ltask", TTR("Extracting Export Templates"), fc));
264 }
265
266 fc = 0;
267
268 while (ret == UNZ_OK) {
269
270 //get filename
271 unz_file_info info;
272 char fname[16384];
273 unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0);
274
275 String file_path(String(fname).simplify_path());
276
277 String file = file_path.get_file();
278
279 if (file.size() == 0) {
280 ret = unzGoToNextFile(pkg);
281 continue;
282 }
283
284 Vector<uint8_t> data;
285 data.resize(info.uncompressed_size);
286
287 //read
288 unzOpenCurrentFile(pkg);
289 unzReadCurrentFile(pkg, data.ptrw(), data.size());
290 unzCloseCurrentFile(pkg);
291
292 String base_dir = file_path.get_base_dir().trim_suffix("/");
293
294 if (base_dir != contents_dir && base_dir.begins_with(contents_dir)) {
295 base_dir = base_dir.substr(contents_dir.length(), file_path.length()).trim_prefix("/");
296 file = base_dir.plus_file(file);
297
298 DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
299 ERR_CONTINUE(!da);
300
301 String output_dir = template_path.plus_file(base_dir);
302
303 if (!DirAccess::exists(output_dir)) {
304 Error mkdir_err = da->make_dir_recursive(output_dir);
305 ERR_CONTINUE(mkdir_err != OK);
306 }
307 }
308
309 if (p) {
310 p->step(TTR("Importing:") + " " + file, fc);
311 }
312
313 String to_write = template_path.plus_file(file);
314 FileAccessRef f = FileAccess::open(to_write, FileAccess::WRITE);
315
316 if (!f) {
317 ret = unzGoToNextFile(pkg);
318 fc++;
319 ERR_CONTINUE_MSG(true, "Can't open file from path '" + String(to_write) + "'.");
320 }
321
322 f->store_buffer(data.ptr(), data.size());
323
324 #ifndef WINDOWS_ENABLED
325 FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
326 #endif
327
328 ret = unzGoToNextFile(pkg);
329 fc++;
330 }
331
332 if (p) {
333 memdelete(p);
334 }
335
336 unzClose(pkg);
337
338 _update_template_list();
339 return true;
340 }
341
popup_manager()342 void ExportTemplateManager::popup_manager() {
343
344 _update_template_list();
345 popup_centered_minsize(Size2(400, 400) * EDSCALE);
346 }
347
ok_pressed()348 void ExportTemplateManager::ok_pressed() {
349
350 template_open->popup_centered_ratio();
351 }
352
_http_download_mirror_completed(int p_status,int p_code,const PoolStringArray & headers,const PoolByteArray & p_data)353 void ExportTemplateManager::_http_download_mirror_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data) {
354
355 if (p_status != HTTPRequest::RESULT_SUCCESS || p_code != 200) {
356 EditorNode::get_singleton()->show_warning(TTR("Error getting the list of mirrors."));
357 return;
358 }
359
360 String mirror_str;
361 {
362 PoolByteArray::Read r = p_data.read();
363 mirror_str.parse_utf8((const char *)r.ptr(), p_data.size());
364 }
365
366 template_list_state->hide();
367 template_download_progress->hide();
368
369 Variant r;
370 String errs;
371 int errline;
372 Error err = JSON::parse(mirror_str, r, errs, errline);
373 if (err != OK) {
374 EditorNode::get_singleton()->show_warning(TTR("Error parsing JSON of mirror list. Please report this issue!"));
375 return;
376 }
377
378 bool mirrors_found = false;
379
380 Dictionary d = r;
381 if (d.has("mirrors")) {
382 Array mirrors = d["mirrors"];
383 for (int i = 0; i < mirrors.size(); i++) {
384 Dictionary m = mirrors[i];
385 ERR_CONTINUE(!m.has("url") || !m.has("name"));
386 LinkButton *lb = memnew(LinkButton);
387 lb->set_text(m["name"]);
388 lb->connect("pressed", this, "_begin_template_download", varray(m["url"]));
389 template_list->add_child(lb);
390 mirrors_found = true;
391 }
392 }
393
394 if (!mirrors_found) {
395 EditorNode::get_singleton()->show_warning(TTR("No download links found for this version. Direct download is only available for official releases."));
396 return;
397 }
398 }
_http_download_templates_completed(int p_status,int p_code,const PoolStringArray & headers,const PoolByteArray & p_data)399 void ExportTemplateManager::_http_download_templates_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data) {
400
401 switch (p_status) {
402
403 case HTTPRequest::RESULT_CANT_RESOLVE: {
404 template_list_state->set_text(TTR("Can't resolve."));
405 } break;
406 case HTTPRequest::RESULT_BODY_SIZE_LIMIT_EXCEEDED:
407 case HTTPRequest::RESULT_CONNECTION_ERROR:
408 case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH:
409 case HTTPRequest::RESULT_SSL_HANDSHAKE_ERROR:
410 case HTTPRequest::RESULT_CANT_CONNECT: {
411 template_list_state->set_text(TTR("Can't connect."));
412 } break;
413 case HTTPRequest::RESULT_NO_RESPONSE: {
414 template_list_state->set_text(TTR("No response."));
415 } break;
416 case HTTPRequest::RESULT_REQUEST_FAILED: {
417 template_list_state->set_text(TTR("Request Failed."));
418 } break;
419 case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: {
420 template_list_state->set_text(TTR("Redirect Loop."));
421 } break;
422 default: {
423 if (p_code != 200) {
424 template_list_state->set_text(TTR("Failed:") + " " + itos(p_code));
425 } else {
426 String path = download_templates->get_download_file();
427 template_list_state->set_text(TTR("Download Complete."));
428 template_downloader->hide();
429 bool ret = _install_from_file(path, false);
430 if (ret) {
431 // Clean up downloaded file.
432 DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
433 Error err = da->remove(path);
434 if (err != OK) {
435 EditorNode::get_singleton()->add_io_error(TTR("Cannot remove temporary file:") + "\n" + path + "\n");
436 }
437 } else {
438 EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path));
439 }
440 }
441 } break;
442 }
443
444 set_process(false);
445 }
446
_begin_template_download(const String & p_url)447 void ExportTemplateManager::_begin_template_download(const String &p_url) {
448
449 if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
450 OS::get_singleton()->shell_open(p_url);
451 return;
452 }
453
454 for (int i = 0; i < template_list->get_child_count(); i++) {
455 BaseButton *b = Object::cast_to<BaseButton>(template_list->get_child(0));
456 if (b) {
457 b->set_disabled(true);
458 }
459 }
460
461 download_data.clear();
462 download_templates->set_download_file(EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_templates.tpz"));
463 download_templates->set_use_threads(true);
464
465 Error err = download_templates->request(p_url);
466 if (err != OK) {
467 EditorNode::get_singleton()->show_warning(TTR("Error requesting URL:") + " " + p_url);
468 return;
469 }
470
471 set_process(true);
472
473 template_list_state->show();
474 template_download_progress->set_max(100);
475 template_download_progress->set_value(0);
476 template_download_progress->show();
477 template_list_state->set_text(TTR("Connecting to Mirror..."));
478 }
479
_window_template_downloader_closed()480 void ExportTemplateManager::_window_template_downloader_closed() {
481 download_templates->cancel_request();
482 }
483
_notification(int p_what)484 void ExportTemplateManager::_notification(int p_what) {
485
486 if (p_what == NOTIFICATION_PROCESS) {
487
488 update_countdown -= get_process_delta_time();
489
490 if (update_countdown > 0) {
491 return;
492 }
493 update_countdown = 0.5;
494 String status;
495 bool errored = false;
496
497 switch (download_templates->get_http_client_status()) {
498 case HTTPClient::STATUS_DISCONNECTED:
499 status = TTR("Disconnected");
500 errored = true;
501 break;
502 case HTTPClient::STATUS_RESOLVING: status = TTR("Resolving"); break;
503 case HTTPClient::STATUS_CANT_RESOLVE:
504 status = TTR("Can't Resolve");
505 errored = true;
506 break;
507 case HTTPClient::STATUS_CONNECTING: status = TTR("Connecting..."); break;
508 case HTTPClient::STATUS_CANT_CONNECT:
509 status = TTR("Can't Connect");
510 errored = true;
511 break;
512 case HTTPClient::STATUS_CONNECTED: status = TTR("Connected"); break;
513 case HTTPClient::STATUS_REQUESTING: status = TTR("Requesting..."); break;
514 case HTTPClient::STATUS_BODY:
515 status = TTR("Downloading");
516 if (download_templates->get_body_size() > 0) {
517 status += " " + String::humanize_size(download_templates->get_downloaded_bytes()) + "/" + String::humanize_size(download_templates->get_body_size());
518 template_download_progress->set_max(download_templates->get_body_size());
519 template_download_progress->set_value(download_templates->get_downloaded_bytes());
520 } else {
521 status += " " + String::humanize_size(download_templates->get_downloaded_bytes());
522 }
523 break;
524 case HTTPClient::STATUS_CONNECTION_ERROR:
525 status = TTR("Connection Error");
526 errored = true;
527 break;
528 case HTTPClient::STATUS_SSL_HANDSHAKE_ERROR:
529 status = TTR("SSL Handshake Error");
530 errored = true;
531 break;
532 }
533
534 template_list_state->set_text(status);
535 if (errored) {
536 set_process(false);
537 }
538 }
539
540 if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
541 if (!is_visible_in_tree()) {
542 set_process(false);
543 }
544 }
545 }
546
can_install_android_template()547 bool ExportTemplateManager::can_install_android_template() {
548
549 const String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
550 return FileAccess::exists(templates_dir.plus_file("android_source.zip"));
551 }
552
install_android_template()553 Error ExportTemplateManager::install_android_template() {
554
555 // To support custom Android builds, we install the Java source code and buildsystem
556 // from android_source.zip to the project's res://android folder.
557
558 DirAccessRef da = DirAccess::open("res://");
559 ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
560
561 // Make res://android dir (if it does not exist).
562 da->make_dir("android");
563 {
564 // Add version, to ensure building won't work if template and Godot version don't match.
565 FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::WRITE);
566 ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
567 f->store_line(VERSION_FULL_CONFIG);
568 f->close();
569 }
570
571 // Create the android plugins directory.
572 Error err = da->make_dir_recursive("android/plugins");
573 ERR_FAIL_COND_V(err != OK, err);
574
575 err = da->make_dir_recursive("android/build");
576 ERR_FAIL_COND_V(err != OK, err);
577 {
578 // Add an empty .gdignore file to avoid scan.
579 FileAccessRef f = FileAccess::open("res://android/build/.gdignore", FileAccess::WRITE);
580 ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
581 f->store_line("");
582 f->close();
583 }
584
585 // Uncompress source template.
586
587 const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
588 const String &source_zip = templates_path.plus_file("android_source.zip");
589 ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN);
590
591 FileAccess *src_f = NULL;
592 zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
593
594 unzFile pkg = unzOpen2(source_zip.utf8().get_data(), &io);
595 ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in ZIP format.");
596
597 int ret = unzGoToFirstFile(pkg);
598 int total_files = 0;
599 // Count files to unzip.
600 while (ret == UNZ_OK) {
601 total_files++;
602 ret = unzGoToNextFile(pkg);
603 }
604 ret = unzGoToFirstFile(pkg);
605
606 ProgressDialog::get_singleton()->add_task("uncompress_src", TTR("Uncompressing Android Build Sources"), total_files);
607
608 Set<String> dirs_tested;
609 int idx = 0;
610 while (ret == UNZ_OK) {
611
612 // Get file path.
613 unz_file_info info;
614 char fpath[16384];
615 ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, NULL, 0, NULL, 0);
616
617 String path = fpath;
618 String base_dir = path.get_base_dir();
619
620 if (!path.ends_with("/")) {
621 Vector<uint8_t> data;
622 data.resize(info.uncompressed_size);
623
624 // Read.
625 unzOpenCurrentFile(pkg);
626 unzReadCurrentFile(pkg, data.ptrw(), data.size());
627 unzCloseCurrentFile(pkg);
628
629 if (!dirs_tested.has(base_dir)) {
630 da->make_dir_recursive(String("android/build").plus_file(base_dir));
631 dirs_tested.insert(base_dir);
632 }
633
634 String to_write = String("res://android/build").plus_file(path);
635 FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE);
636 if (f) {
637 f->store_buffer(data.ptr(), data.size());
638 memdelete(f);
639 #ifndef WINDOWS_ENABLED
640 FileAccess::set_unix_permissions(to_write, (info.external_fa >> 16) & 0x01FF);
641 #endif
642 } else {
643 ERR_PRINTS("Can't uncompress file: " + to_write);
644 }
645 }
646
647 ProgressDialog::get_singleton()->task_step("uncompress_src", path, idx);
648
649 idx++;
650 ret = unzGoToNextFile(pkg);
651 }
652
653 ProgressDialog::get_singleton()->end_task("uncompress_src");
654 unzClose(pkg);
655
656 return OK;
657 }
658
_bind_methods()659 void ExportTemplateManager::_bind_methods() {
660
661 ClassDB::bind_method("_download_template", &ExportTemplateManager::_download_template);
662 ClassDB::bind_method("_uninstall_template", &ExportTemplateManager::_uninstall_template);
663 ClassDB::bind_method("_uninstall_template_confirm", &ExportTemplateManager::_uninstall_template_confirm);
664 ClassDB::bind_method("_install_from_file", &ExportTemplateManager::_install_from_file);
665 ClassDB::bind_method("_http_download_mirror_completed", &ExportTemplateManager::_http_download_mirror_completed);
666 ClassDB::bind_method("_http_download_templates_completed", &ExportTemplateManager::_http_download_templates_completed);
667 ClassDB::bind_method("_begin_template_download", &ExportTemplateManager::_begin_template_download);
668 ClassDB::bind_method("_window_template_downloader_closed", &ExportTemplateManager::_window_template_downloader_closed);
669 }
670
ExportTemplateManager()671 ExportTemplateManager::ExportTemplateManager() {
672
673 VBoxContainer *main_vb = memnew(VBoxContainer);
674 add_child(main_vb);
675
676 current_hb = memnew(HBoxContainer);
677 main_vb->add_margin_child(TTR("Current Version:"), current_hb, false);
678
679 installed_scroll = memnew(ScrollContainer);
680 main_vb->add_margin_child(TTR("Installed Versions:"), installed_scroll, true);
681
682 installed_vb = memnew(VBoxContainer);
683 installed_scroll->add_child(installed_vb);
684 installed_scroll->set_enable_v_scroll(true);
685 installed_scroll->set_enable_h_scroll(false);
686 installed_vb->set_h_size_flags(SIZE_EXPAND_FILL);
687
688 get_cancel()->set_text(TTR("Close"));
689 get_ok()->set_text(TTR("Install From File"));
690
691 remove_confirm = memnew(ConfirmationDialog);
692 remove_confirm->set_title(TTR("Remove Template"));
693 add_child(remove_confirm);
694 remove_confirm->connect("confirmed", this, "_uninstall_template_confirm");
695
696 template_open = memnew(FileDialog);
697 template_open->set_title(TTR("Select Template File"));
698 template_open->add_filter("*.tpz ; " + TTR("Godot Export Templates"));
699 template_open->set_access(FileDialog::ACCESS_FILESYSTEM);
700 template_open->set_mode(FileDialog::MODE_OPEN_FILE);
701 template_open->connect("file_selected", this, "_install_from_file", varray(true));
702 add_child(template_open);
703
704 set_title(TTR("Export Template Manager"));
705 set_hide_on_ok(false);
706
707 request_mirror = memnew(HTTPRequest);
708 add_child(request_mirror);
709 request_mirror->connect("request_completed", this, "_http_download_mirror_completed");
710
711 download_templates = memnew(HTTPRequest);
712 add_child(download_templates);
713 download_templates->connect("request_completed", this, "_http_download_templates_completed");
714
715 template_downloader = memnew(AcceptDialog);
716 template_downloader->set_title(TTR("Download Templates"));
717 template_downloader->get_ok()->set_text(TTR("Close"));
718 template_downloader->set_exclusive(true);
719 add_child(template_downloader);
720 template_downloader->connect("popup_hide", this, "_window_template_downloader_closed");
721
722 VBoxContainer *vbc = memnew(VBoxContainer);
723 template_downloader->add_child(vbc);
724 ScrollContainer *sc = memnew(ScrollContainer);
725 sc->set_custom_minimum_size(Size2(400, 200) * EDSCALE);
726 vbc->add_margin_child(TTR("Select mirror from list: (Shift+Click: Open in Browser)"), sc);
727 template_list = memnew(VBoxContainer);
728 sc->add_child(template_list);
729 sc->set_enable_v_scroll(true);
730 sc->set_enable_h_scroll(false);
731 template_list_state = memnew(Label);
732 vbc->add_child(template_list_state);
733 template_download_progress = memnew(ProgressBar);
734 vbc->add_child(template_download_progress);
735
736 update_countdown = 0;
737 }
738