Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/debugger/editor_file_server.cpp
9902 views
1
/**************************************************************************/
2
/* editor_file_server.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
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 "editor_file_server.h"
32
33
#include "editor/editor_node.h"
34
#include "editor/export/editor_export_platform.h"
35
#include "editor/settings/editor_settings.h"
36
37
#define FILESYSTEM_PROTOCOL_VERSION 1
38
#define PASSWORD_LENGTH 32
39
#define MAX_FILE_BUFFER_SIZE 100 * 1024 * 1024 // 100mb max file buffer size (description of files to update, compressed).
40
41
static void _add_file(String f, const uint64_t &p_modified_time, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files) {
42
f = f.replace_first("res://", ""); // remove res://
43
const uint64_t *cached_mt = cached_files.getptr(f);
44
if (cached_mt && *cached_mt == p_modified_time) {
45
// File is good, skip it.
46
cached_files.erase(f); // Erase to mark this file as existing. Remaining files not added to files_to_send will be considered erased here, so they need to be erased in the client too.
47
return;
48
}
49
files_to_send.insert(f, p_modified_time);
50
}
51
52
void EditorFileServer::_scan_files_changed(EditorFileSystemDirectory *efd, const Vector<String> &p_tags, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files) {
53
for (int i = 0; i < efd->get_file_count(); i++) {
54
String f = efd->get_file_path(i);
55
if (FileAccess::exists(f + ".import")) {
56
// is imported, determine what to do
57
// Todo the modified times of remapped files should most likely be kept in EditorFileSystem to speed this up in the future.
58
Ref<ConfigFile> cf;
59
cf.instantiate();
60
Error err = cf->load(f + ".import");
61
62
ERR_CONTINUE(err != OK);
63
{
64
uint64_t mt = FileAccess::get_modified_time(f + ".import");
65
_add_file(f + ".import", mt, files_to_send, cached_files);
66
}
67
68
if (!cf->has_section("remap")) {
69
continue;
70
}
71
72
Vector<String> remaps = cf->get_section_keys("remap");
73
74
for (const String &remap : remaps) {
75
if (remap == "path") {
76
String remapped_path = cf->get_value("remap", remap);
77
uint64_t mt = FileAccess::get_modified_time(remapped_path);
78
_add_file(remapped_path, mt, files_to_send, cached_files);
79
} else if (remap.begins_with("path.")) {
80
String feature = remap.get_slicec('.', 1);
81
if (p_tags.has(feature)) {
82
String remapped_path = cf->get_value("remap", remap);
83
uint64_t mt = FileAccess::get_modified_time(remapped_path);
84
_add_file(remapped_path, mt, files_to_send, cached_files);
85
}
86
}
87
}
88
} else {
89
uint64_t mt = efd->get_file_modified_time(i);
90
_add_file(f, mt, files_to_send, cached_files);
91
}
92
}
93
94
for (int i = 0; i < efd->get_subdir_count(); i++) {
95
_scan_files_changed(efd->get_subdir(i), p_tags, files_to_send, cached_files);
96
}
97
}
98
99
static void _add_custom_file(const String &f, HashMap<String, uint64_t> &files_to_send, HashMap<String, uint64_t> &cached_files) {
100
if (!FileAccess::exists(f)) {
101
return;
102
}
103
_add_file(f, FileAccess::get_modified_time(f), files_to_send, cached_files);
104
}
105
106
void EditorFileServer::poll() {
107
if (!active) {
108
return;
109
}
110
111
if (!server->is_connection_available()) {
112
return;
113
}
114
115
Ref<StreamPeerTCP> tcp_peer = server->take_connection();
116
ERR_FAIL_COND(tcp_peer.is_null());
117
118
// Got a connection!
119
EditorProgress pr("updating_remote_file_system", TTR("Updating assets on target device:"), 105);
120
121
pr.step(TTR("Syncing headers"), 0, true);
122
print_verbose("EFS: Connecting taken!");
123
char header[4];
124
Error err = tcp_peer->get_data((uint8_t *)&header, 4);
125
ERR_FAIL_COND(err != OK);
126
ERR_FAIL_COND(header[0] != 'G');
127
ERR_FAIL_COND(header[1] != 'R');
128
ERR_FAIL_COND(header[2] != 'F');
129
ERR_FAIL_COND(header[3] != 'S');
130
131
uint32_t protocol_version = tcp_peer->get_u32();
132
ERR_FAIL_COND(protocol_version != FILESYSTEM_PROTOCOL_VERSION);
133
134
char cpassword[PASSWORD_LENGTH + 1];
135
err = tcp_peer->get_data((uint8_t *)cpassword, PASSWORD_LENGTH);
136
cpassword[PASSWORD_LENGTH] = 0;
137
ERR_FAIL_COND(err != OK);
138
print_verbose("EFS: Got password: " + String(cpassword));
139
ERR_FAIL_COND_MSG(password != cpassword, "Client disconnected because password mismatch.");
140
141
uint32_t tag_count = tcp_peer->get_u32();
142
print_verbose("EFS: Getting tags: " + itos(tag_count));
143
144
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
145
Vector<String> tags;
146
for (uint32_t i = 0; i < tag_count; i++) {
147
String tag = tcp_peer->get_utf8_string();
148
print_verbose("EFS: tag #" + itos(i) + ": " + tag);
149
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
150
tags.push_back(tag);
151
}
152
153
uint32_t file_buffer_decompressed_size = tcp_peer->get_32();
154
HashMap<String, uint64_t> cached_files;
155
156
if (file_buffer_decompressed_size > 0) {
157
pr.step(TTR("Getting remote file system"), 1, true);
158
159
// Got files cached by client.
160
uint32_t file_buffer_size = tcp_peer->get_32();
161
print_verbose("EFS: Getting file buffer: compressed - " + String::humanize_size(file_buffer_size) + " decompressed: " + String::humanize_size(file_buffer_decompressed_size));
162
163
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
164
ERR_FAIL_COND(file_buffer_size > MAX_FILE_BUFFER_SIZE);
165
LocalVector<uint8_t> file_buffer;
166
file_buffer.resize(file_buffer_size);
167
LocalVector<uint8_t> file_buffer_decompressed;
168
file_buffer_decompressed.resize(file_buffer_decompressed_size);
169
170
err = tcp_peer->get_data(file_buffer.ptr(), file_buffer_size);
171
172
pr.step(TTR("Decompressing remote file system"), 2, true);
173
174
ERR_FAIL_COND(err != OK);
175
// Decompress the text with all the files
176
const int64_t decompressed_size = Compression::decompress(file_buffer_decompressed.ptr(), file_buffer_decompressed.size(), file_buffer.ptr(), file_buffer.size(), Compression::MODE_ZSTD);
177
ERR_FAIL_COND_MSG(decompressed_size != file_buffer_decompressed.size(), "Error decompressing file buffer. Decompressed size did not match the expected size.");
178
String files_text = String::utf8((const char *)file_buffer_decompressed.ptr(), file_buffer_decompressed.size());
179
Vector<String> files = files_text.split("\n");
180
181
print_verbose("EFS: Total cached files received: " + itos(files.size()));
182
for (int i = 0; i < files.size(); i++) {
183
if (files[i].get_slice_count("::") != 2) {
184
continue;
185
}
186
String file = files[i].get_slice("::", 0);
187
uint64_t modified_time = files[i].get_slice("::", 1).to_int();
188
189
cached_files.insert(file, modified_time);
190
}
191
} else {
192
// Client does not have any files stored.
193
}
194
195
pr.step(TTR("Scanning for local changes"), 3, true);
196
197
print_verbose("EFS: Scanning changes:");
198
199
HashMap<String, uint64_t> files_to_send;
200
// Scan files to send.
201
_scan_files_changed(EditorFileSystem::get_singleton()->get_filesystem(), tags, files_to_send, cached_files);
202
// Add forced export files
203
Vector<String> forced_export = EditorExportPlatform::get_forced_export_files(Ref<EditorExportPreset>());
204
for (int i = 0; i < forced_export.size(); i++) {
205
_add_custom_file(forced_export[i], files_to_send, cached_files);
206
}
207
208
_add_custom_file("res://project.godot", files_to_send, cached_files);
209
// Check which files were removed and also add them
210
for (KeyValue<String, uint64_t> K : cached_files) {
211
if (!files_to_send.has(K.key)) {
212
files_to_send.insert(K.key, 0); //0 means removed
213
}
214
}
215
216
tcp_peer->put_32(files_to_send.size());
217
218
print_verbose("EFS: Sending list of changed files.");
219
pr.step(TTR("Sending list of changed files:"), 4, true);
220
221
// Send list of changed files first, to ensure that if connecting breaks, the client is not found in a broken state.
222
for (KeyValue<String, uint64_t> K : files_to_send) {
223
tcp_peer->put_utf8_string(K.key);
224
tcp_peer->put_64(K.value);
225
}
226
227
print_verbose("EFS: Sending " + itos(files_to_send.size()) + " files.");
228
229
int idx = 0;
230
for (KeyValue<String, uint64_t> K : files_to_send) {
231
pr.step(TTR("Sending file:") + " " + K.key.get_file(), 5 + idx * 100 / files_to_send.size(), false);
232
idx++;
233
234
if (K.value == 0 || !FileAccess::exists("res://" + K.key)) { // File was removed
235
continue;
236
}
237
238
Vector<uint8_t> array = FileAccess::_get_file_as_bytes("res://" + K.key);
239
tcp_peer->put_64(array.size());
240
tcp_peer->put_data(array.ptr(), array.size());
241
ERR_FAIL_COND(tcp_peer->get_status() != StreamPeerTCP::STATUS_CONNECTED);
242
}
243
244
tcp_peer->put_data((const uint8_t *)"GEND", 4); // End marker.
245
246
print_verbose("EFS: Done.");
247
}
248
249
void EditorFileServer::start() {
250
if (active) {
251
stop();
252
}
253
port = EDITOR_GET("filesystem/file_server/port");
254
password = EDITOR_GET("filesystem/file_server/password");
255
Error err = server->listen(port);
256
ERR_FAIL_COND_MSG(err != OK, "EditorFileServer: Unable to listen on port " + itos(port));
257
active = true;
258
}
259
260
bool EditorFileServer::is_active() const {
261
return active;
262
}
263
264
void EditorFileServer::stop() {
265
if (active) {
266
server->stop();
267
active = false;
268
}
269
}
270
271
EditorFileServer::EditorFileServer() {
272
server.instantiate();
273
}
274
275
EditorFileServer::~EditorFileServer() {
276
stop();
277
}
278
279