editor_file_server.cpp 11 KB

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