From aef663af5e579eaf8a30553e2a74e3a41c2c28b0 Mon Sep 17 00:00:00 2001 From: Team Lightly Date: Sat, 10 Mar 2018 17:49:38 -0500 Subject: [PATCH] Cut release from b0234b91c64d08f69e15c15bf6579f358c93ea94 --- LICENSE.txt | 188 + README.md | 75 + rplugin/python/tandem_lib/__init__.py | 0 rplugin/python/tandem_lib/agent/__init__.py | 0 rplugin/python/tandem_lib/agent/__init__.pyc | Bin 0 -> 170 bytes rplugin/python/tandem_lib/agent/main.py | 56 + .../tandem_lib/agent/tandem/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 150 bytes .../tandem_lib/agent/tandem/agent/__init__.py | 0 .../agent/tandem/agent/__init__.pyc | Bin 0 -> 183 bytes .../agent/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 156 bytes .../__pycache__/configuration.cpython-36.pyc | Bin 0 -> 499 bytes .../agent/tandem/agent/configuration.py | 29 + .../agent/tandem/agent/configuration.pyc | Bin 0 -> 647 bytes .../tandem/agent/executables/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 168 bytes .../__pycache__/agent.cpython-36.pyc | Bin 0 -> 3442 bytes .../agent/tandem/agent/executables/agent.py | 103 + .../agent/tandem/agent/io/__init__.py | 0 .../io/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 159 bytes .../io/__pycache__/document.cpython-36.pyc | Bin 0 -> 2838 bytes .../io/__pycache__/std_streams.cpython-36.pyc | Bin 0 -> 1465 bytes .../agent/tandem/agent/io/document.py | 69 + .../proxies/__pycache__/relay.cpython-36.pyc | Bin 0 -> 1754 bytes .../agent/tandem/agent/io/proxies/relay.py | 52 + .../agent/tandem/agent/io/std_streams.py | 33 + .../agent/tandem/agent/models/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 163 bytes .../__pycache__/connection.cpython-36.pyc | Bin 0 -> 4409 bytes .../connection_state.cpython-36.pyc | Bin 0 -> 488 bytes .../agent/tandem/agent/models/connection.py | 115 + .../tandem/agent/models/connection_state.py | 10 + .../agent/tandem/agent/protocol/__init__.py | 0 .../agent/tandem/agent/protocol/__init__.pyc | Bin 0 -> 192 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 165 bytes .../agent/protocol/handlers/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 174 bytes .../__pycache__/editor.cpython-36.pyc | Bin 0 -> 5066 bytes .../__pycache__/interagent.cpython-36.pyc | Bin 0 -> 6186 bytes .../__pycache__/rendezvous.cpython-36.pyc | Bin 0 -> 3707 bytes .../tandem/agent/protocol/handlers/editor.py | 150 + .../agent/protocol/handlers/interagent.py | 220 + .../agent/protocol/handlers/rendezvous.py | 108 + .../agent/protocol/messages/__init__.py | 0 .../agent/protocol/messages/__init__.pyc | Bin 0 -> 201 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 174 bytes .../__pycache__/editor.cpython-36.pyc | Bin 0 -> 10384 bytes .../__pycache__/interagent.cpython-36.pyc | Bin 0 -> 4690 bytes .../tandem/agent/protocol/messages/editor.py | 317 + .../tandem/agent/protocol/messages/editor.pyc | Bin 0 -> 15643 bytes .../agent/protocol/messages/interagent.py | 130 + .../agent/tandem/agent/stores/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 163 bytes .../__pycache__/connection.cpython-36.pyc | Bin 0 -> 1753 bytes .../agent/tandem/agent/stores/connection.py | 31 + .../agent/tandem/agent/utils/__init__.py | 0 .../utils/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 162 bytes .../__pycache__/hole_punching.cpython-36.pyc | Bin 0 -> 1531 bytes .../agent/tandem/agent/utils/hole_punching.py | 39 + .../agent/tandem/shared/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 157 bytes .../agent/tandem/shared/io/__init__.py | 0 .../io/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 160 bytes .../shared/io/__pycache__/base.cpython-36.pyc | Bin 0 -> 2920 bytes .../io/__pycache__/udp_gateway.cpython-36.pyc | Bin 0 -> 3074 bytes .../tandem_lib/agent/tandem/shared/io/base.py | 69 + .../tandem/shared/io/proxies/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 168 bytes .../proxies/__pycache__/base.cpython-36.pyc | Bin 0 -> 908 bytes .../__pycache__/fragment.cpython-36.pyc | Bin 0 -> 1394 bytes .../list_parameters.cpython-36.pyc | Bin 0 -> 1026 bytes .../__pycache__/reliability.cpython-36.pyc | Bin 0 -> 2116 bytes .../__pycache__/unicode.cpython-36.pyc | Bin 0 -> 1114 bytes .../agent/tandem/shared/io/proxies/base.py | 12 + .../tandem/shared/io/proxies/fragment.py | 48 + .../shared/io/proxies/list_parameters.py | 20 + .../tandem/shared/io/proxies/reliability.py | 75 + .../agent/tandem/shared/io/proxies/unicode.py | 24 + .../agent/tandem/shared/io/udp_gateway.py | 97 + .../agent/tandem/shared/models/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 164 bytes .../models/__pycache__/base.cpython-36.pyc | Bin 0 -> 317 bytes .../__pycache__/fragment.cpython-36.pyc | Bin 0 -> 2131 bytes .../models/__pycache__/peer.cpython-36.pyc | Bin 0 -> 1307 bytes .../agent/tandem/shared/models/base.py | 2 + .../agent/tandem/shared/models/fragment.py | 40 + .../agent/tandem/shared/models/peer.py | 30 + .../agent/tandem/shared/protocol/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 166 bytes .../shared/protocol/handlers/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 175 bytes .../__pycache__/addressed.cpython-36.pyc | Bin 0 -> 596 bytes .../handlers/__pycache__/base.cpython-36.pyc | Bin 0 -> 1890 bytes .../handlers/__pycache__/multi.cpython-36.pyc | Bin 0 -> 962 bytes .../shared/protocol/handlers/addressed.py | 6 + .../tandem/shared/protocol/handlers/base.py | 54 + .../tandem/shared/protocol/handlers/multi.py | 14 + .../shared/protocol/messages/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 175 bytes .../messages/__pycache__/base.cpython-36.pyc | Bin 0 -> 2395 bytes .../__pycache__/rendezvous.cpython-36.pyc | Bin 0 -> 2743 bytes .../tandem/shared/protocol/messages/base.py | 55 + .../shared/protocol/messages/rendezvous.py | 70 + .../agent/tandem/shared/stores/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 164 bytes .../stores/__pycache__/base.cpython-36.pyc | Bin 0 -> 634 bytes .../__pycache__/fragment.cpython-36.pyc | Bin 0 -> 1244 bytes .../__pycache__/reliability.cpython-36.pyc | Bin 0 -> 998 bytes .../agent/tandem/shared/stores/base.py | 12 + .../agent/tandem/shared/stores/fragment.py | 24 + .../agent/tandem/shared/stores/reliability.py | 16 + .../agent/tandem/shared/utils/__init__.py | 0 .../utils/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 163 bytes .../utils/__pycache__/fragment.cpython-36.pyc | Bin 0 -> 3150 bytes .../utils/__pycache__/proxy.cpython-36.pyc | Bin 0 -> 621 bytes .../utils/__pycache__/relay.cpython-36.pyc | Bin 0 -> 1493 bytes .../__pycache__/reliability.cpython-36.pyc | Bin 0 -> 2340 bytes .../__pycache__/static_value.cpython-36.pyc | Bin 0 -> 507 bytes .../__pycache__/time_scheduler.cpython-36.pyc | Bin 0 -> 4694 bytes .../agent/tandem/shared/utils/fragment.py | 100 + .../agent/tandem/shared/utils/proxy.py | 7 + .../agent/tandem/shared/utils/relay.py | 42 + .../agent/tandem/shared/utils/reliability.py | 64 + .../agent/tandem/shared/utils/static_value.py | 11 + .../tandem/shared/utils/time_scheduler.py | 146 + .../python/tandem_lib/agent/test_client.py | 317 + .../python/tandem_lib/crdt/build/bundle.js | 16615 ++++++++++++++++ rplugin/python/tandem_lib/diff_match_patch.py | 1907 ++ rplugin/python/tandem_lib/tandem_plugin.py | 318 + rplugin/python/tandem_neovim.py | 84 + 130 files changed, 22004 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 rplugin/python/tandem_lib/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/__init__.pyc create mode 100644 rplugin/python/tandem_lib/agent/main.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/__init__.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/__pycache__/configuration.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/configuration.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/configuration.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/executables/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/executables/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/executables/__pycache__/agent.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/executables/agent.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/document.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/std_streams.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/document.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/proxies/__pycache__/relay.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/proxies/relay.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/io/std_streams.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/models/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/connection.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/connection_state.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/models/connection.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/models/connection_state.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/__init__.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/editor.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/interagent.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/rendezvous.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/editor.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/interagent.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/rendezvous.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__init__.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/editor.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/interagent.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/interagent.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/stores/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/stores/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/stores/__pycache__/connection.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/stores/connection.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/utils/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/utils/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/utils/__pycache__/hole_punching.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/agent/utils/hole_punching.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/base.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/udp_gateway.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/base.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/base.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/fragment.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/list_parameters.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/reliability.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/unicode.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/base.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/fragment.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/list_parameters.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/reliability.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/unicode.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/io/udp_gateway.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/base.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/fragment.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/peer.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/base.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/fragment.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/models/peer.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__pycache__/addressed.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__pycache__/base.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__pycache__/multi.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/addressed.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/base.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/multi.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/__pycache__/base.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/__pycache__/rendezvous.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/base.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/rendezvous.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/base.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/fragment.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/reliability.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/base.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/fragment.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/stores/reliability.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__init__.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/__init__.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/fragment.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/proxy.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/relay.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/reliability.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/static_value.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/time_scheduler.cpython-36.pyc create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/fragment.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/proxy.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/relay.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/reliability.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/static_value.py create mode 100644 rplugin/python/tandem_lib/agent/tandem/shared/utils/time_scheduler.py create mode 100644 rplugin/python/tandem_lib/agent/test_client.py create mode 100644 rplugin/python/tandem_lib/crdt/build/bundle.js create mode 100644 rplugin/python/tandem_lib/diff_match_patch.py create mode 100644 rplugin/python/tandem_lib/tandem_plugin.py create mode 100644 rplugin/python/tandem_neovim.py diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2d4d2a9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,188 @@ + Copyright (c) [2018] [Team Lightly] + + "Lightly-Modified Apache License" + A Variant of the Apache License + + View the Apache License at: + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + Our license adds clause 4.(e) to the Apache License + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + (e) Any Derivative Works may not be configured to use the default server + provided by the Copyright holders. You must configure all Derivative + Works to use servers owned by You, or servers You are otherwise + authorized to use. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cf61ad --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Tandem + +Tandem is an add-on for your favorite text editor that enables peer-to-peer +collaborative editing across different editors. + +This repository contains code for the Neovim plugin. For more details on +Tandem, visit [our website](http://typeintandem.com), or our [mono-repository +containing all other source code.](https://github.com/typeintandem/tandem) + +## Installation +To install, you will need to have a copy of the Neovim Python2 client. +``` +pip install neovim +``` +You must also have `python3` and `node.js` installed for the networking code to +work. + +Neovim users have the option of installing in one of the following ways: +- **[Recommended]** Using your favourite plugin manager (e.g. Vundle, vim-plug, + etc.) Tandem should be compatible with most popular plugin managers +- Installing Tandem directly. You’ll need download this repository to + `~/.config/nvim/rplugin/python`. + +Whether you use a plugin manager or download directly, you will also need to +complete an additional one-time step. +Tandem uses Neovim remote plugin functionality for thread-safety, so you will +need to: +- launch `nvim` +- run `:UpdateRemotePlugins` +- quit `nvim` + +You are now ready to use Tandem! + +## Usage +Tandem users can choose either start a collaborative session or join an +existing one. Starting a collaborative session will share the contents of your +current buffer. Joining an existing session will open it’s contents in a new +buffer. + +Please use one of the following commands: +- `:Tandem` - creates a new tandem session and prints the session ID +- `:Tandem ` - joins an existing tandem session with the specified + session ID +- `:TandemStop` - leaves the current session +- `:TandemSession` - prints the current session ID + +It is recommended to leave the session before exiting neovim, but that process +should be automated. + + +## License +Copyright (c) 2018 Team Lightly + +Licensed under the "Lightly-Modified Apache License", a variant of the Apache +License, Version 2.0 (the "License"); you may not use this file except in +compliance with the License. + +See [LICENSE.txt](LICENSE.txt) + +This license is essentially the Apache License 2.0, except for an added a +clause that requires you to use your own servers instead of ours if you do +modify Tandem. +You can also modify this in the configuration file at: +`agent/tandem/agent/configuration.py` + +## Authors +Team Lightly +[Geoffrey Yu](https://github.com/geoffxy), [Jamiboy +Mohammad](https://github.com/jamiboym) and [Sameer +Chitley](https://github.com/rageandqq) + +We are a team of senior Software Engineering students at the University of +Waterloo. +Tandem was created as our [Engineering Capstone Design +Project](https://uwaterloo.ca/capstone-design). diff --git a/rplugin/python/tandem_lib/__init__.py b/rplugin/python/tandem_lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/__init__.py b/rplugin/python/tandem_lib/agent/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/__init__.pyc b/rplugin/python/tandem_lib/agent/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39bb6da2c6f3c50670e2b1be07a709c38bb6ea83 GIT binary patch literal 170 zcmZSn%*(Z*uq7&)0SXv_v;zlH?*b>D61dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnFK7MG;?$yI{o=&j z)YKwPBrz`~HCI0|9f=bkpP83g5+AQuPj&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnFHil@;?$yI{o=&j z)YKwPBrz`~HCI0|9fK1epP83g5+AQuPMZ!x+W4ELvaTGg9?V3kn;%%6{ zAtrbPCQM*J!bkq??;QL5I){Tn_htVHzqSB)1AkUq-A~l!LJ0u`rU3t(5JRG2je>P+ ztvA|k)SzY!Ye-t(QR^7})=&*`=l69-)%3cByMMT?xJTi-PYm_jTxxhgpbrkvyK)AT zV5?ac*5i^0Y!+#?N^-v4D``&X_K{WG5}Sf1xrkIEvtpJdcOL*8>`0AqX>b-lP=Pyh z-wi$YdVDMOA`1#j{w43|kt!w~mp-t@{n3qo%?4KO+#s}9bk zCp%}J6NIyIaDjWX>-qWRqyaftQE|_>SZtb(BhQ^UF20%0{c!9!p6mP1pzN!O8u^^q zb52Wcb7fCU+hK8)(Ohtw&_|lG;v4Lrq3me&4`P-tlT}qlB4N2zY$Q6$7^zZv43!1n WRe~Vu>AC?&P*Y)OeK>?e4Z$xSvyBb_ literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/configuration.py b/rplugin/python/tandem_lib/agent/tandem/agent/configuration.py new file mode 100644 index 0000000..2de3dd8 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/configuration.py @@ -0,0 +1,29 @@ +import os +import socket + +# Tandem will try to establish a direct connection with other peers in a +# session. However, this is not always possible. When Tandem is unable to +# establish a peer-to-peer connection, we will relay messages to each peer +# through our servers. If this is undesirable for your use case, you can set +# this flag to "False". +# +# Please note that with relay disabled, you will not be able to collaborate +# with any peers that Tandem cannot reach directly. Tandem does not notify you +# if a peer-to-peer connection cannot be established. +USE_RELAY = True + +# DO NOT edit anything below this unless you know what you're doing! + +PROJECT_ROOT = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '..', + '..', + '..', +) +BASE_DIR = os.path.dirname(PROJECT_ROOT) +CRDT_PATH = os.path.join(BASE_DIR, "..", "crdt") +PLUGIN_PATH = os.path.join(BASE_DIR, "..", "plugins") +RENDEZVOUS_ADDRESS = ( + socket.gethostbyname("rendezvous.typeintandem.com"), + 60000, +) diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/configuration.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/configuration.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bbf6e9bb86927f2a0a967bc3fb21b033a961233 GIT binary patch literal 647 zcmZWm!H%0S5FG=_E(vL+Jw@7U4>`dPR8`XjwOg%{6_85h;u3JOP5~oqBBj@SO0WHx zen+qUfOhO8ZKaxF=FOX@nZejUC!_B#zGelS&j90VeC0QEB^rSWAp3&}WCWN6nGFnJ z=fG}o0mBQp0(K2YqOG*fc618nG_c%yP8YQGRQ z|MUZ3_8dh@cM(zzF-TE-X38{7yPhc*xm6O;IC_|WG<2J*Cj)i@+v$NN@0z@32Ip&O z1lPz@y5SY0RC$;@#>@B7T}tESGF7_zW{z#c``C=Qi^G&Y&eL~lthEoTxA%)vKEZs; z)D!NvKA15#$i&eijIs~QRYK=s7)MDmLl*uziT@0Om1La^BreCgAQVMkvqs6FWP4VL zRwu#fLW1+91$$nfDO&M$kZ=FdoRUH`8@}zkTyfF(?ZJG^%UYCu#aj&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuTcHa;?$yI{o=&j z)YKwPBrz`~HCI0|9fOlvk(yjul9-f}TC5)*pP83g a5+AQuPQ<}Ol3Jg-C4JcKw*I{H8~bU&vi`Cz zd@jiMf%G51geC0QVnt~)8|qH%BrbDp)VZ;j)L4yVFRmvI)*!hS`$>~ENv_AOq|MqS zH{wpxWnGf}cp>Sr9?8vkFTe1HMT}_C%%$gWmie=#_P!j+aP%% zzLspVO_F=@^<;}}+13L~EQ+Ont|WoBHnKD3J%@u@qS`MfYtKt4NzifeI1{bK}sY?mQVqVJc)* z?|q71?aMfLQK^<6AH({@qcn|oPi2_Fl3={${oTEtUG~kxCkOn~ot^#NgM$zOsy+vV z`#^dJn6z-~nImlBytJ4rT;W0KiJGWGSrZN6Ls=J1(Soue+M)xcFS=p@%BJYS1}x_7 z#{i*B$i)!j3_mQJK%W6o%o*#%o>=D=cg}6#?zscpGc}z2SJc!^x_Pf+$vlWbe2 z4Z05Lx@G}n7svzZnZ=1^mVlOlR?O+^c3`;!{< z*)Z42HnjJFiawj>I>;M9n-lbROIWaEckh)A%Np3Modl=+d8)pLy&K%fy)26at0Di0 zxg6vkSMrBUY9j?dPPLg)j8kP4B&wA!@Fa*vT$UTD5V^d^4TKNZRs1O2yu8Up8RrTy z&bvGvacu;TM&r!n9g=ks`^rO$b$(O?KTo9A5a0Z`hNOhq%4qmUX-3RFZc=F0^N>7gAzaO%YC>5Pm=J6+kIZ5y~ByM`Ns7_B;@U z!3+>+NI-lH0s?`@1VVA*OeJ?Jd4*(ZCv{;@Y`h)-GH35>qgZ>NPNDvBp!_pNZ z0Q=cGwCR}jf3$z0MV^!UBs;iZ+2)&D1q1hSlHxs7^ife}3HnM) z+XF%40b|*ndIz@aY;oSZkm3_8s-5}_`&-xi2XxWBI~UQL9@%r3$=;xar|Ad|XU_8$=ShmM9*{en|BwZ7(V}kQ zq<4@FkljW`@rb}Eoc@AKeXJ5}iy*B4X}m9e+kemZeAjRIZNH(gZSYnRAD9pA{#Uyg-nM)^ecaM>gZ2N_JS*P3^dMndrP*`~tH~8Ug6XNkmcw)u zW(v?S!^6yEN@{vev1>D(!RjzN%2Z&YbVTnPikQp7EcZVt!uFmG5a>QIh&6f(;CtYb G-~9(Q+h)!H literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/executables/agent.py b/rplugin/python/tandem_lib/agent/tandem/agent/executables/agent.py new file mode 100644 index 0000000..8f25953 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/executables/agent.py @@ -0,0 +1,103 @@ +import logging +import uuid +from tandem.agent.io.document import Document +from tandem.agent.io.std_streams import STDStreams +from tandem.shared.io.udp_gateway import UDPGateway +from tandem.agent.protocol.handlers.editor import EditorProtocolHandler +from tandem.agent.protocol.handlers.interagent import InteragentProtocolHandler +from tandem.agent.protocol.handlers.rendezvous import RendezvousProtocolHandler +from tandem.shared.protocol.handlers.multi import MultiProtocolHandler +from tandem.shared.utils.time_scheduler import TimeScheduler +from tandem.shared.io.proxies.fragment import FragmentProxy +from tandem.shared.io.proxies.list_parameters import ListParametersProxy +from tandem.shared.io.proxies.unicode import UnicodeProxy +from tandem.shared.io.proxies.reliability import ReliabilityProxy +from tandem.agent.io.proxies.relay import AgentRelayProxy +from concurrent.futures import ThreadPoolExecutor +from tandem.agent.configuration import RENDEZVOUS_ADDRESS + + +class TandemAgent: + def __init__(self, host, port): + self._id = uuid.uuid4() + self._requested_host = host + # This is the port the user specified on the command line (it can be 0) + self._requested_port = port + self._main_executor = ThreadPoolExecutor(max_workers=1) + self._time_scheduler = TimeScheduler(self._main_executor) + self._document = Document() + self._std_streams = STDStreams(self._on_std_input) + self._interagent_gateway = UDPGateway( + self._requested_host, + self._requested_port, + self._gateway_message_handler, + [ + ListParametersProxy(), + UnicodeProxy(), + FragmentProxy(), + AgentRelayProxy(RENDEZVOUS_ADDRESS), + ReliabilityProxy(self._time_scheduler), + ], + ) + self._editor_protocol = EditorProtocolHandler( + self._id, + self._std_streams, + self._interagent_gateway, + self._document, + ) + self._interagent_protocol = InteragentProtocolHandler( + self._id, + self._std_streams, + self._interagent_gateway, + self._document, + self._time_scheduler, + ) + self._rendezvous_protocol = RendezvousProtocolHandler( + self._id, + self._interagent_gateway, + self._time_scheduler, + self._document, + ) + self._gateway_handlers = MultiProtocolHandler( + self._interagent_protocol, + self._rendezvous_protocol, + ) + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + + def start(self): + self._time_scheduler.start() + self._document.start() + self._std_streams.start() + self._interagent_gateway.start() + logging.info("Tandem Agent has started.") + + def stop(self): + def atomic_shutdown(): + self._interagent_protocol.stop() + self._interagent_gateway.stop() + self._std_streams.stop() + self._document.stop() + self._time_scheduler.stop() + self._main_executor.submit(atomic_shutdown) + self._main_executor.shutdown() + logging.info("Tandem Agent has shut down.") + + def _on_std_input(self, retrieve_data): + # Called by _std_streams after receiving a new message from the plugin + self._main_executor.submit( + self._editor_protocol.handle_message, + retrieve_data, + ) + + def _gateway_message_handler(self, retrieve_data): + # Do not call directly - called by _interagent_gateway + self._main_executor.submit( + self._gateway_handlers.handle_raw_data, + retrieve_data, + ) diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/io/__init__.py b/rplugin/python/tandem_lib/agent/tandem/agent/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b06ec2c3733043eddb8a560edb0c74637bf28ea GIT binary patch literal 159 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnFCYET;?$yI{o=&j z)YKwPBrz`~HCI0|9fOmZuOA;%r(4O~Xwox~eAf(G_o!A5H%Uo^mj#@rd&LGybH*1&&f zfj_r{7O-Qh+x#2XwFX^VdxzOXq}n}vcKEc*bo24ilNbE(+ZR7*H_bo<-boQBFm0X` zDI}sdQW4EuJPvjqVNoeo!Xj*e4GipeR^WgGF19Wq!PYBL1Py3H3&(4)4sC3G=)eZH zP1uAjY+JAmJJ_zn4Y-MI8*afawjDTt+pzzR1skx3I^5CzlPoI6BF!U)+tWj>jpZ>G zwTl&4GmBe8su?h>ZLn~zJ@~Hc=nju0oY5I2J?-C7BYsnF~3N(=Zp>eI3SVp({bUD$gc+IIp6$&01{QS;Ci1BGR26YDsAWkM<%+ z=t*qI=C<*qU^ARdk~hz5XFBv$zA=ePLjnW({=j1uc-zP+d zBQ`x)EY!Os7P%-#54Duk42?V3S*3M`1b?3si1tMw+J`j8W|p zhE>|Oak2r2fx{)u{}?AM^LCE(lIZ^>?$#1<%O?LX`t~mHa&G+32wp|vy{kz)7rx+; zwPm*|Do5y%b9Npaw!|aF(hZ~u`KlaG-o|gMtnG^wQ(8f(Z6)dTmf+p5;aw|UGf3cS zfCo4rTVp|hZ!rbeu}!XsaqP%_f@Sbx9E(s^aDz5knNPwzIumLY_g?Ax20JXp$`^#X zOqV9E&xyTeLXDfUP@Rcr1;SloWU`6lYocT4^3m`Wfv7Bdr>5?*+|@=k;Yx>9o$IRo ztNh`;3X?*t60%D^eR+#-mY{r4L%CXZ5K*e5hhnu(d%xg-QeMp7Ix{>#e{IU!6t`eM zFdUIZb7$nvoVjsA;*Gumj;kEbMjqvElxmp2ctx38=nwA91$UoV_6FKtBud?ygc46t zOy^3omZW2$+g#4~Mo;aIRF-NFit$8g?=&gYnf8(_1SRPR(!LZSBylS6upm-{&SD3i z;_~tn`qnn7dW<&!Jd^{Rq9%CauhMJc`vJ-;3OS4U=Do=_SlixV)4MgZS7(0o^G!O^ zX9i}P^?MMC{H=P!#eDgVe0E-oq2NxHt2p+PYu->2#>Rrjd6Pt}K1QIa$0mK=ia zBa#keNq$Vet0?vnwCDR+8opzSgMG($j8u1Beu^^M&XjJc;-tD3nOlH4_w_+}dFh$U yOE1dO(|A~vHzY|3Ub`5pXK0lyPUSX!Fza3gOZyK>{U1`mnX3X3x`kxkbp8dCq?*J4 literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/std_streams.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/io/__pycache__/std_streams.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdd5f663979e6cfb143c1a08758e2dd6ce23eaad GIT binary patch literal 1465 zcmZWp!H(ND5amdeC9C$vX`5h+7Qv!7UF-tA6+sZRE{a?>hi#FBXhEnV?UgG_4k>4| z#^zElkPqppzr<@!`GuS^L#@3|OJIf^aX6fL^QiBK!}QVpf5b3k>>WE89M_Lg>=cz^ z3MwYL83dpmRKYS7;gSmu>=&j&#jly-I^2dLLOW7Dw0oMP-BWPJviLnlVy9W)?mnqo zZDx6)r+J(Im|N|J`%h#RLyt*_5S&tG8P$l5)Pbxu-{jd*l+Et=M!QZkOC{V$5A|k&gG!w+l zqbOn(tK^!AL1Mo|)v^|@*fv-MSFi=Z{^405up+x& zX=74!BH+rpY^B83aI34C7@cF;l{Rv=u8X#8>I__8%A(4xm9oRIk4Fu)WQgKRX0Z9< zHmmV2YC#R8LGwPnZ_v0+Z;P94xZsP3R=YHN<}>u_lH|5+RxY%c*7XqgX5G4|s2Zy? zFq8pKN964L>vOw<<_N`7Ul<(0=FUNO6mFjxc@TI{QS5i9uHbig6Siyt2wk`cw~XNH zY~`W!YrfAmICYR_k)aFCT~sc*FlDPp2q`owi#8kI$%enRoQyL|Ban3A8eKaSoYJ9gZ7 znLcCBSbYrGfUX%`p?|X%tPY-cO$kmn-~Rl%&?_Rv$;G*@Pmunqtmh{)(=1Q0GiCk5 z4Gu^$GR2WJy3pk-tvVp$s+rI6i0kWJs4Tcar7COvUl39}pLb`!rYeYZ3<9kGbLc?| zn|lW+-1=sziO|u|lQc0YYO_eG86A~9eGT!%d`0R3DmT30d7-On$92MdM=;MVdN(AN zR02F0gae+$o~l{gv4CNxL{NWeJa~LLu zsirkaA!fKn&%vb!sYZWOqx3taQz4~R#tfMCjmoc+&+pp(ljt6p`uFG26Me&@N0QcNgZ@pj2c^H>6=To3fg2Xyesn>ecj(S3a%XnEIP72!RgZhV4&O1&8n+TjgIhioS7CmB?rDnZ*8;%&wjFZ&mhk>{#&Ic z4N`}Q(T3`aiUBquBC=g$4X=V}SaUcRZ5W&nro4_eP%dweeeE9QA<*JeMmalVAzeSYP0#-{S#%}`5+1dv_z5|J&1RNq) zVI5rqBfy5kyQY`B0`?rr3GcGe9!RxAe9&`9>SK6JpWyTbOqO?m=p#@QhYE7hLf5hP z2+yO>d%1eQEFe!Sok4wfNMW^^LzoaL4k>%+zUwbAWLgCA%YN1wXC_a6(c&9(pl literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/io/proxies/relay.py b/rplugin/python/tandem_lib/agent/tandem/agent/io/proxies/relay.py new file mode 100644 index 0000000..1243a02 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/io/proxies/relay.py @@ -0,0 +1,52 @@ +from tandem.shared.io.proxies.base import ProxyBase +from tandem.shared.utils.relay import RelayUtils +from tandem.shared.io.udp_gateway import UDPGateway +from tandem.agent.stores.connection import ConnectionStore + + +class AgentRelayProxy(ProxyBase): + def __init__(self, relay_server_address): + self._relay_server_address = relay_server_address + + def should_relay(self, address): + connection_store = ConnectionStore.get_instance() + connection = connection_store.get_connection_by_address(address) + return ( + self._relay_server_address != address and + connection and connection.is_relayed() + ) + + def pre_write_io_data(self, params): + args, kwargs = params + io_datas, = args + + new_io_datas = [] + for io_data in io_datas: + new_io_data = io_data + if self.should_relay(io_data.get_address()): + new_raw_data = RelayUtils.serialize( + io_data.get_data(), + io_data.get_address(), + ) + new_io_data = UDPGateway.data_class( + new_raw_data, + self._relay_server_address, + ) + new_io_datas.append(new_io_data) + + new_args = (new_io_datas,) + return (new_args, kwargs) + + def on_retrieve_io_data(self, params): + args, kwargs = params + if args is None or args is (None, None): + return params + + raw_data, address = args + + if RelayUtils.is_relay(raw_data): + new_data, new_address = RelayUtils.deserialize(raw_data) + new_args = new_data, new_address + return (new_args, kwargs) + else: + return params diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/io/std_streams.py b/rplugin/python/tandem_lib/agent/tandem/agent/io/std_streams.py new file mode 100644 index 0000000..987394c --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/io/std_streams.py @@ -0,0 +1,33 @@ +import sys +import logging +from tandem.shared.io.base import InterfaceDataBase, InterfaceBase + + +class STDData(InterfaceDataBase): + pass + + +class STDStreams(InterfaceBase): + data_class = STDData + + def __init__(self, handler_function): + super(STDStreams, self).__init__(handler_function) + + def stop(self): + super(STDStreams, self).stop() + sys.stdout.close() + + def write_io_data(self, *args, **kwargs): + io_data, = args + + sys.stdout.write(io_data.get_data()) + sys.stdout.write("\n") + sys.stdout.flush() + + def _read_data(self): + try: + for line in sys.stdin: + self._received_data(line) + except: + logging.exception("Exception when reading from stdin:") + raise diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/models/__init__.py b/rplugin/python/tandem_lib/agent/tandem/agent/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..376977fed27fbb2a402faf5d67c1605251412e59 GIT binary patch literal 163 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuK@kf;?$yI{o=&j z)YKwPBrz`~HCI0|9fOmbpOTtWtREkrnU`4-AFo$X Td5gmaC|H`4Y6r5Z7>F4FHqI)l literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/connection.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/connection.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01bc5015692d1da998d0ea1c5d1f68f721517ae6 GIT binary patch literal 4409 zcmb7IO>f&q5ZxsyiIOEtj_sr_l2k>KHVP0KNDH)wAc&hdse#0CofJsb0tBOFY{}F| zmz2{O@}&xpAJS9*5-&aUl)unZ-z-InqN#k)*j+8T+?kzu^JeI8DwWcm^MAGesWA30 zOI!wi-$nDDp%YASm-TpybNa2hT2F83>bLG1E#n~*hRD5PBIodl-pXN~7X{1;t|jHalFWsF5pg%-7-G?Un- z{pq5S+C4O{gl^2o%);662H%C6Icw-RvY`cr=eREegJyp-X=3Sr`I!)7!KU&P;k_4R5m@+o3sp9hjEY>34j~Dv(A>O@6XE z)4UlyZ*wf5i$-d*Bd{*(^2ziFn#|#0kUwyIt0Nj*<}p?Jhh34TOX%qIk-(LN!0^Rv zabyOc{R!_x_FCN?_&dF!>(JpHu`T65W(y6@(atsyaO_yN5Yq8HfHT(NQc8#Ul^>~( zEMvz7^4CcI%=(r`Fsvms_#&l2dhHyFVi%mTNvMOS3?Jl_l$8C0H7`Sz)di|#t(`MR z4CrO-Fa{bX%;#P0NDG~$OS&(0bU|)^`_a}f3k+21c$Rcr`?Vv^Cv;g)m}RzNPJ`GO z1UAxO3J-L6; zp(EIO?|#F>9(l=3q(+Q}fo)`M|}PO|-5y<`YHvQ-lDf zqoE_^1uRl_Y=rQY#7e~EmeqD`&$FxqiIBa&M2kpdBYmb1a2JJYh}I-~k)J}U7>;n{ zc0?7~C{nJzno#1(870oL&SL2LAkxqzS{N_Jlf7-Eg-jA9ynLMyeJxY;%%36Q%y}n{ z8v@#tRF#Dz`&V&4fm#Jv!t{;z%FSYoMo|(3TDc-wK^ z$mWASI8E^-Q57|$`X#X<&S5O8(5k?7tHJt@2#Gz42uFNueEEmAUw>tLX96uN zzTVMk+R>3{jG8*$*&suOp$kCWGW=XOkFhY;(3>5)R}=`zZgI@Jr3t%@dZfEF0Thmk z@_GCOcW#I9&~_zZrcsukVUU-pBliW>RAvoos4IL&9^0;Uh?2y0rhwkt*?kzSoXLa1 zx~fAuNQolxFj$M?&l-YY@MMI7M}AHsR~13a&N1GJRyFi6RERf%HL7gVJ4OJS5isfh zEt*F>&3KLH@n>pzEzg&EnV($FbZ9oY#Ho-pByxczO}U0&D3D-nbuWtWI@SaiaHigH zL`Rq_O%?<_g~=A0M=68x68=uEXDj$A#6zlvYH@i58!wPfl=A>BqTCycCBK(Brtx`$To7uTXXsmrQf4% z>gY(nE<4K0i=nl1Yy+QO-+?*ez<#F!o6BohK2<#^OzY%S3J9t%M!jJoBI#SsBY|Yu zlWW-ur`}nd1xdvSjFej!fRS?FbF|1#3mln602rm~pHCck~T*YK#>VW1T<7 z7pqFV5wjK&T%%QM0(V)08Wy5T3gkQ@|@6eWx zXog?N%@_EOfh1G}SEtX_*L3ta&1_*}E4YJ$1EeTbvxkMO%8s*OpGCZRJN$&WLm{V% OmogHKsn;&lR{jS$a>uU# literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/connection_state.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/models/__pycache__/connection_state.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59b0672ce1d7c99704e58a9590acfff87e7d081f GIT binary patch literal 488 zcmZWm%}xR_5bm-CR(|3ec;dpvJ$N++gcV81LO`O-rK#HqVs^W@-PLf`H}T{X_!^#j z_2esfvW18T+sv15I@6i?+LLCp_R)HUcS6XgvHhZkb&RPa1A+)ACO`&6l!W_2gp1_f zd3K>BoEd4;FOx&U$l~vE4CxqCEdxlv5yWPLD@s6x3uQr}BFaz|74SsWRPpp-FO?Fb ze3i;s&T|C1Udbi6Y9lp$;;UTKbd3_q8OD4A^+IOIqdR^ZW7s~>9%GUx$e6A%mZYMH zEpIUPQ1Eyc0Xi9m1Mp@+*k`jv2#xFR@QPiGr`FSSJPATwo(7}t0_yW{8gzS?-SbiK zMzWgGv4!s&f37f7zB0`)^ZRL3Bq(#`3w*>lU0cV`xfGcAeEH8YlVPmgJyIPIuzZ>>nsw({q0SyL5fy literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/models/connection.py b/rplugin/python/tandem_lib/agent/tandem/agent/models/connection.py new file mode 100644 index 0000000..7caa960 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/models/connection.py @@ -0,0 +1,115 @@ +from tandem.shared.models.base import ModelBase +from tandem.agent.models.connection_state import ConnectionState +import logging + + +class Connection(ModelBase): + def __init__(self, peer): + self._peer = peer + + def get_id(self): + return self._peer.get_id() + + def get_active_address(self): + raise NotImplementedError + + def get_connection_state(self): + raise NotImplementedError + + def set_connection_state(self, state): + raise NotImplementedError + + def is_relayed(self): + return self.get_connection_state() == ConnectionState.RELAY + + def get_peer(self): + return self._peer + + +class DirectConnection(Connection): + """ + A connection to a peer established without using hole punching. + """ + def __init__(self, peer): + super(DirectConnection, self).__init__(peer) + + def get_active_address(self): + return self.get_peer().get_public_address() + + def get_connection_state(self): + return ConnectionState.OPEN + + def set_connection_state(self, state): + pass + + +class HolePunchedConnection(Connection): + """ + A connection to a peer that was established with hole punching. + """ + PROMOTE_AFTER = 3 + + def __init__(self, peer, initiated_connection): + super(HolePunchedConnection, self).__init__(peer) + self._active_address = None + self._interval_handle = None + self._connection_state = ConnectionState.PING + # If true, this agent initiated the connection to this peer + self._initiated_connection = initiated_connection + + self._address_ping_counts = {} + self._address_ping_counts[peer.get_public_address()] = 0 + if peer.get_private_address() is not None: + self._address_ping_counts[peer.get_private_address()] = 0 + + def get_active_address(self): + if self._active_address is None: + self._active_address = self._compute_active_address() + return self._active_address + + def get_connection_state(self): + return self._connection_state + + def set_connection_state(self, state): + if self._connection_state == state: + return + self._connection_state = state + if self._interval_handle is not None: + self._interval_handle.cancel() + self._interval_handle = None + + def set_interval_handle(self, interval_handle): + self._interval_handle = interval_handle + + def bump_ping_count(self, address): + if address in self._address_ping_counts: + self._address_ping_counts[address] += 1 + + def initiated_connection(self): + return self._initiated_connection + + def _compute_active_address(self): + if self.is_relayed(): + return self.get_peer().get_public_address() + + private_address = self.get_peer().get_private_address() + private_address_count = ( + self._address_ping_counts[private_address] + if private_address is not None else 0 + ) + + # If the private address is routable, always choose it + if private_address_count > 0: + return ( + private_address + if private_address_count >= HolePunchedConnection.PROMOTE_AFTER + else None + ) + + public_address = self.get_peer().get_public_address() + public_address_count = self._address_ping_counts[public_address] + return ( + public_address + if public_address_count >= HolePunchedConnection.PROMOTE_AFTER + else None + ) diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/models/connection_state.py b/rplugin/python/tandem_lib/agent/tandem/agent/models/connection_state.py new file mode 100644 index 0000000..f96819f --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/models/connection_state.py @@ -0,0 +1,10 @@ +import enum + + +class ConnectionState(enum.Enum): + PING = "ping" + SEND_SYN = "syn" + WAIT_FOR_SYN = "wait" + OPEN = "open" + RELAY = "relay" + UNREACHABLE = "unreachable" diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/__init__.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/__init__.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0f2595456bcaf2612d1b232e9d43db6c558c39f GIT binary patch literal 192 zcmZSn%*$mbwm2%80SXv_v;zj&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuOR)<;?$yI{o=&j z)YKwPBrz`~HCI0|9fMO)lwXpcoS&l~AD@|*SrQ+w XS5SG2!zMRBr8Fni4rEs`5HkP(ng}bJ literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__init__.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d236603bb6da78495f5dfabd3d1e0fa7e0efd3e GIT binary patch literal 174 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuW0?y;?$yI{o=&j z)YKwPBrz`~HCI0|9fMO)lwXpcoS&ng0aTI$GAKSi dGcU6wK3=b&@)n0pZhlH>PO2Tq(qbTH002}MFCzc| literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/editor.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/editor.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9fe78beb335c1c6e60bfbe2269796e7b5c5d7c5 GIT binary patch literal 5066 zcma)A&2!tv6~`wDf*+D)`7^D{={RYav{cf1dT zLbk#Gwu`sxmA!}`!0HUb}g zD`}2eL2J|w+M`a;F=-7uSsASctGewZXGUwm+URU>R`=axeRM83XBzJttj6k}8?1g} z1?QQ$WAwa#V=u;b&sG=ir8yVT9#8X+M3!fREP0g2iB#=v{`|u+M0uR0Qn`0|l4Lzo zt=!4dln?0cPM!(gv($wLJY{_HEIX9TYjn!75BSqVE_2LYd$6^=xfT58!$&*e?aj>x zTRS@g+Di4a(71&nAEM$0DG16jnf18=<=D)@)nP8H;p(zF^Kh-P2J>;PvnFfd>ajNK z;M!m-Y!z3ZondRZHrZJmeO;Z~Vlj4JZS`)HvV@C)izm{LuC$FiDDr!#rsmWLjj4s& zoZ6_Zse>9=VL5ZV=O{ajnQDbHXDEV4BdNS_FUtAz=twm}mJJR^P;k#xj^xR%vM{{4 zFgmDLgS~3(-wd&aXMgkY#v{pv+>p_TbFr~mE+sb@f5ww+OzUpsI07GSXcU(_;}X-2 z{o<5zgX_ch$4A&=7^iU_hOk&edZ-MurvH;Gr>dx5O&E}V=tsJ0qG(s(Y?>;uJv0m5Efu>PkQ(E(oDqZUN=skMM=!Mg_gUO`nw}8CE)2#-r0IF z;A3J>_xV0gyBZgo`7JZ9!(hEVo`)>TBW3Xs%$UO>VmxNTm6IQhdC(|%_ApaxVS#iw zC}(k`lOPlYJ;P{l5cr_|M^Qf5=Te=mhNb*@I4*7mXYTCt z!9it*J4fk2y;hCGtii#|+(QXr(7c<;dr5rZ#boE3VkR}Dw2wO+!b zDOXfyX=10;Whs6Q_vDXJ5l=kJGJUgSwoWM>a}{@Mrh_uMy1ezf1rhtpOV1cp!|KE; z&8Xj_ksCvE3KoGwR&Ea+W`a2u?$l1SEf(!v>)6f_EAmF}6B|z8GMW7)&4RBQwulAb zp>^!)UT5l(9c%k_wkKN;wFBwirZCWrWjD%mK7y6PF7vDl*Bw1cV!7Xqx-1rO{@v2L z$c!=I)8p%(9`_&iEU|(e2Q9gu9VQHRHBOFtE2<{5!2!=z8+J^z#Yc{;L{jn5f4 zLzj@XQ8c={Xb`$Ul<P4cTHzKvn4@MU^E zdvU!A)yWet1eXCmHg>-K=;eZ8#@GhGzMDTlIE4eS{&GGYp|5d_>;pu z(Zluu2!ATt)ViSOhY0kCoQ2sub2u;Iy_Ow~#Ph^?%nDk!>8Yi|5fn;g}uD;^LvHVtC!9wOn3^HXcn_@o0i+TgQ}d{3`DHgXHN183mv<}R_4*$0-m zPpC16(w?cSSE=R51#|Fw=o9YD{gSY+{|Q_N_Vp>e!Lg@%?Ws58c5QoLVpsGdKGjjk zvB@`4?ZMPO=^mSyq+JX%E+Kp~Q`god;~7W~u9q6dHD-+&*<2ia(RlQKL>yQ;a9_g)RaLj399NU}kc z$eUHFK4`V(! zdITX&%Zc}K)Sxqy>4ye>;Yf*9b0ikx5@jI?DXN3YFnxR7@_@i9%H-Ww>;9DdY=!}u zCb=?=!6q6MO3E;Vgr0Oq=?99N*Qn1-?Sd#EQE<{pzHXOEx(+kkiIPGhDBOtGsiN#rP~M^arXbP^vO7UgYS0&g z$}Bz#N+@U%Nw+-Ht9h5a75p!IEzkE{E$>|rw;&Zn(jtAp5kajOQlw|S?^rILeLO0H6|q5Q>cN$znUcN~$^Ja(6p`?) zauD9-1ZSsgSIj;v32iWBE9&yn(pYmh-a9P5QwePxq{P#-tGpWpZZ|1m(76FrT>e+h Lbr+W5wCw)^NDMiZ literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/interagent.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/interagent.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e203295e17c08403989aa486c2423aa20c84f4ee GIT binary patch literal 6186 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 literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/rendezvous.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/__pycache__/rendezvous.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef0bd2a3e15b4bba974f5d42c80710e4010a53ad GIT binary patch literal 3707 zcmb7HTW=f372X?{ONyi@iLxvwE;fyefJsC3lBd8hqS`{N!lnhu3X%ni!J0FYcDomL zW>WK#07xyRhr7tbe-_YOC=Xvc@{z9L6&g?EPQDq<$X7-$!Gnez7IT!u9-ERK- z(ciq!*EQ`w+S1Pg`YUMSpU_c_>XGIZU)|FcZ+HfLjmV5G&(d+ujB2s%*^0KJdhB?P zqH9qjZhB2c+tFIw@>+_nNA0-dbrkJH>v7lX#v9&-;v3Osyyb1hJ+G(uX0#pO^L7-y z7Tu3`y=0o6~me&>6*u{P}MvT=HHK#mVy?0*N$_VYAiM|m>3 zVDxF4By5xgX#x}L3+^OKIfJ2%xonV}pJqWMV9+{ZjKjCPPbp_YF#6n2Xar-1+&N|m zWs}P^7e_qJ(oq_zW%A+eiI=d}KWDG6#|&27E6&Ni9X!XpP!$HP5DX>cF>7 z8?*^uhpqvfExCJJhf?#A4!h$Izu2Ba6Zmv84VLO)1nvNb=m{2`{X^qjVE_BOy;H%s z*b{yXnC?AGM|lhYh&{?KS(J{E*~XZ)ZTK8Ixb$snp<()ho)Pj!N14KxA5|hsIP3O`t4A)dHgcz~&8*kn1Nb%g0AP z2aUnrLe`WS<$Y4&2e+a}>pK3&#g8D?Cn$R0l3wo25FTj!}38-|)IlR#; z|5~n-EQlEq=!iMEk8BWHK7f8rsV5CsP_N`WKM;2+=>h}qGmjR|9VIR-AJ#;nW;!Kp$E=4xk% zh4okSFW_vtmf2Z7bZ~s8#;Ntrys2f4X${7jwD!)pv8Q&p_Gc|@h3%{pu0!hrw=u1S zn==ilt(iG9aIZgUXF3H}ee2xRm3$9mx2N@~Ju~5?d*RMZ3-1F5vdRX|kfUpUkKN?A z$85xcOGe!vJV^I*w~T{smb!nuasA94=Wn851k9JnC)6GD;1aA@jPyLkIq(&9n?W>-PrVL{&0VJjtxE zM)lB!**Yld%m83i75E0oHY>H2oN=ez8p;)L&$Js|LEg~l-xIL`$f|g*yV7v`jO6%3p!H}cl z%6gQZpTj+*6(nb=w9ir=`bZ z!-Ak`8C|0Vy`y){meGWO(lu<|&^yND+xG;PJ0knwag>hyNIV(5?;8DGX%~(K8WnEG zKSgM2l)cH%`OlHAqrD)yavM1-&mnGFr>BR{I0i52R1PP%(0l<_8jyC%jKVQB$Og|7 z0$G<|a}4dWeRMednj8*a9~^(P|3boBC=DSX#gZ`qRamHv7A{?$3BC(!J++sA2B*mS z>%*4^uTEccbZOZ_;UIzcn2eb0z_O*c)OU)YUoUgD?BG$LSGha?7`EVO5&kpWtdp>- zrR5we9F+gWAdvX zZ`OJKK2WAlmfzIzB&<)3So^TxRe^4m<1<-sS zI{Bce`v$7+8-SCa1?PED1sCz_Co2MQTNVkFD=Yfq>8}b2tm{{V)U$L~c2XtcU?nCL zF0osRi3^`YbS_Us*DL(4zdB#lo~3(pF{s>xa;z)lr+3Jg?_Uh~yqZrD1vXLdE6(BX|peKgMf4T0!F$u1ncOS?>RtS~`!5*YgC64S}KD Q(ebaf^w+hz7Vzf(0H3lcBme*a literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/editor.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/editor.py new file mode 100644 index 0000000..e3ed0fd --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/editor.py @@ -0,0 +1,150 @@ +import json +import os +import logging +import socket +import uuid +import tandem.agent.protocol.messages.editor as em +from tandem.agent.protocol.messages.interagent import ( + InteragentProtocolUtils, + NewOperations, + Hello +) +from tandem.agent.stores.connection import ConnectionStore +from tandem.shared.protocol.messages.rendezvous import ( + RendezvousProtocolUtils, + ConnectRequest, +) +from tandem.agent.configuration import RENDEZVOUS_ADDRESS + + +class EditorProtocolHandler: + def __init__(self, id, std_streams, gateway, document): + self._id = id + self._std_streams = std_streams + self._gateway = gateway + self._document = document + + def handle_message(self, retrieve_io_data): + io_data = retrieve_io_data() + data = io_data.get_data() + + try: + message = em.deserialize(data) + if type(message) is em.ConnectTo: + self._handle_connect_to(message) + elif type(message) is em.WriteRequestAck: + self._handle_write_request_ack(message) + elif type(message) is em.NewPatches: + self._handle_new_patches(message) + elif type(message) is em.CheckDocumentSync: + self._handle_check_document_sync(message) + elif type(message) is em.HostSession: + self._handle_host_session(message) + elif type(message) is em.JoinSession: + self._handle_join_session(message) + except em.EditorProtocolMarshalError: + logging.info("Ignoring invalid editor protocol message.") + except: + logging.exception( + "Exception when handling editor protocol message:") + raise + + def _handle_connect_to(self, message): + hostname = socket.gethostbyname(message.host) + logging.info( + "Tandem Agent is attempting to establish a direct" + " connection to {}:{}.".format(hostname, message.port), + ) + + address = (hostname, message.port) + payload = InteragentProtocolUtils.serialize(Hello( + id=str(self._id), + should_reply=True, + )) + io_data = self._gateway.generate_io_data(payload, address) + self._gateway.write_io_data(io_data) + + def _handle_write_request_ack(self, message): + logging.debug("Received ACK for seq: {}".format(message.seq)) + text_patches = self._document.apply_queued_operations() + self._document.set_write_request_sent(False) + # Even if no text patches need to be applied, we need to reply to + # the plugin to allow it to accept changes from the user again + text_patches_message = em.ApplyPatches(text_patches) + io_data = self._std_streams.generate_io_data( + em.serialize(text_patches_message), + ) + self._std_streams.write_io_data(io_data) + logging.debug( + "Sent apply patches message for seq: {}".format(message.seq), + ) + + def _handle_new_patches(self, message): + nested_operations = [ + self._document.set_text_in_range( + patch["start"], + patch["end"], + patch["text"], + ) + for patch in message.patch_list + ] + operations = [] + for operations_list in nested_operations: + operations.extend(operations_list) + + connections = ConnectionStore.get_instance().get_open_connections() + if len(connections) == 0: + return + + addresses = [ + connection.get_active_address() for connection in connections + ] + payload = InteragentProtocolUtils.serialize(NewOperations( + operations_list=json.dumps(operations) + )) + io_data = self._gateway.generate_io_data(payload, addresses) + self._gateway.write_io_data( + io_data, + reliability=True, + ) + + def _handle_check_document_sync(self, message): + document_text_content = self._document.get_document_text() + + # TODO: ignore all other messages until we receive an ack + contents = os.linesep.join(message.contents) + os.linesep + + if (contents != document_text_content): + document_lines = document_text_content.split(os.linesep) + apply_text = em.serialize(em.ApplyText(document_lines)) + io_data = self._std_streams.generate_io_data(apply_text) + self._std_streams.write_io_data(io_data) + + def _handle_host_session(self, message): + # Register with rendezvous + session_id = uuid.uuid4() + self._send_connect_request(session_id) + + # Inform plugin of session id + session_info = em.serialize(em.SessionInfo(session_id=str(session_id))) + io_data = self._std_streams.generate_io_data(session_info) + self._std_streams.write_io_data(io_data) + + def _handle_join_session(self, message): + # Parse ID to make sure it's a UUID + session_id = uuid.UUID(message.session_id) + self._send_connect_request(session_id) + + def _send_connect_request(self, session_id): + io_data = self._gateway.generate_io_data( + RendezvousProtocolUtils.serialize(ConnectRequest( + session_id=str(session_id), + my_id=str(self._id), + private_address=( + socket.gethostbyname(socket.gethostname()), + self._gateway.get_port(), + ), + )), + RENDEZVOUS_ADDRESS, + ) + self._gateway.write_io_data(io_data) diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/interagent.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/interagent.py new file mode 100644 index 0000000..472692c --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/interagent.py @@ -0,0 +1,220 @@ +import logging +import json +import uuid +import tandem.agent.protocol.messages.editor as em + +from tandem.agent.models.connection import DirectConnection +from tandem.agent.models.connection_state import ConnectionState +from tandem.agent.protocol.messages.interagent import ( + InteragentProtocolMessageType, + InteragentProtocolUtils, + NewOperations, + Bye, + Hello, + PingBack, +) +from tandem.agent.stores.connection import ConnectionStore +from tandem.agent.utils.hole_punching import HolePunchingUtils +from tandem.shared.models.peer import Peer +from tandem.shared.protocol.handlers.addressed import AddressedHandler +from tandem.shared.utils.static_value import static_value as staticvalue + + +class InteragentProtocolHandler(AddressedHandler): + @staticvalue + def _protocol_message_utils(self): + return InteragentProtocolUtils + + @staticvalue + def _protocol_message_handlers(self): + return { + InteragentProtocolMessageType.Ping.value: self._handle_ping, + InteragentProtocolMessageType.PingBack.value: + self._handle_pingback, + InteragentProtocolMessageType.Syn.value: self._handle_syn, + InteragentProtocolMessageType.Hello.value: self._handle_hello, + InteragentProtocolMessageType.Bye.value: self._handle_bye, + InteragentProtocolMessageType.NewOperations.value: + self._handle_new_operations, + } + + def __init__(self, id, std_streams, gateway, document, time_scheduler): + self._id = id + self._std_streams = std_streams + self._gateway = gateway + self._document = document + self._time_scheduler = time_scheduler + self._next_editor_sequence = 0 + + def _handle_ping(self, message, sender_address): + peer_id = uuid.UUID(message.id) + connection = \ + ConnectionStore.get_instance().get_connection_by_id(peer_id) + + # Only reply to peers we know about to prevent the other peer from + # thinking it can reach us successfully + if connection is None: + return + + logging.debug( + "Replying to ping from {} at {}:{}." + .format(message.id, *sender_address), + ) + io_data = self._gateway.generate_io_data( + InteragentProtocolUtils.serialize(PingBack(id=str(self._id))), + sender_address, + ) + self._gateway.write_io_data(io_data) + + def _handle_pingback(self, message, sender_address): + peer_id = uuid.UUID(message.id) + connection = \ + ConnectionStore.get_instance().get_connection_by_id(peer_id) + # Only count PingBack messages from peers we know about and from whom + # we expect PingBack messages + if (connection is None or + connection.get_connection_state() != ConnectionState.PING): + return + + logging.debug( + "Counting ping from {} at {}:{}." + .format(message.id, *sender_address), + ) + connection.bump_ping_count(sender_address) + + # When the connection is ready to transition into the SYN/WAIT states, + # an active address will be available + if connection.get_active_address() is None: + return + + connection.set_connection_state( + ConnectionState.SEND_SYN + if connection.initiated_connection() + else ConnectionState.WAIT_FOR_SYN + ) + logging.debug( + "Promoted peer from {} with address {}:{}." + .format(message.id, *(connection.get_active_address())), + ) + + if connection.get_connection_state() == ConnectionState.SEND_SYN: + logging.debug( + "Will send SYN to {} at {}:{}" + .format(message.id, *(connection.get_active_address())), + ) + connection.set_interval_handle(self._time_scheduler.run_every( + HolePunchingUtils.SYN_INTERVAL, + HolePunchingUtils.generate_send_syn( + self._gateway, + connection.get_active_address(), + ), + )) + else: + logging.debug( + "Will wait for SYN from {} at {}:{}" + .format(message.id, *(connection.get_active_address())), + ) + + def _handle_syn(self, message, sender_address): + logging.debug("Received SYN from {}:{}".format(*sender_address)) + connection = ( + ConnectionStore.get_instance() + .get_connection_by_address(sender_address) + ) + if (connection is None or + connection.get_connection_state() == ConnectionState.SEND_SYN): + return + + connection.set_connection_state(ConnectionState.OPEN) + self._send_all_operations(connection, even_if_empty=True) + logging.debug( + "Connection to peer at {}:{} is open." + .format(*(connection.get_active_address())), + ) + + def _handle_hello(self, message, sender_address): + id = uuid.UUID(message.id) + new_connection = DirectConnection(Peer( + id=id, + public_address=sender_address, + )) + ConnectionStore.get_instance().add_connection(new_connection) + logging.info( + "Tandem Agent established a direct connection to {}:{}" + .format(*sender_address), + ) + + if message.should_reply: + io_data = self._gateway.generate_io_data( + InteragentProtocolUtils.serialize(Hello( + id=str(self._id), + should_reply=False, + )), + sender_address, + ) + self._gateway.write_io_data(io_data) + + self._send_all_operations(new_connection) + + def _handle_bye(self, message, sender_address): + connection_store = ConnectionStore.get_instance() + connection = connection_store.get_connection_by_address(sender_address) + if connection is None: + return + connection_store.remove_connection(connection) + + def _handle_new_operations(self, message, sender_address): + connection = ( + ConnectionStore.get_instance() + .get_connection_by_address(sender_address) + ) + if (connection is not None and + connection.get_connection_state() == ConnectionState.SEND_SYN): + connection.set_connection_state(ConnectionState.OPEN) + logging.debug( + "Connection to peer at {}:{} is open." + .format(*(connection.get_active_address())), + ) + + operations_list = json.loads(message.operations_list) + if len(operations_list) == 0: + return + self._document.enqueue_remote_operations(operations_list) + + if not self._document.write_request_sent(): + io_data = self._std_streams.generate_io_data( + em.serialize(em.WriteRequest(self._next_editor_sequence)), + ) + self._std_streams.write_io_data(io_data) + self._document.set_write_request_sent(True) + logging.debug( + "Sent write request seq: {}" + .format(self._next_editor_sequence), + ) + self._next_editor_sequence += 1 + + def _send_all_operations(self, connection, even_if_empty=False): + operations = self._document.get_document_operations() + if not even_if_empty and len(operations) == 0: + return + + payload = InteragentProtocolUtils.serialize(NewOperations( + operations_list=json.dumps(operations) + )) + io_data = self._gateway.generate_io_data( + payload, + connection.get_active_address(), + ) + self._gateway.write_io_data( + io_data, + reliability=True, + ) + + def stop(self): + connections = ConnectionStore.get_instance().get_open_connections() + io_data = self._gateway.generate_io_data( + InteragentProtocolUtils.serialize(Bye()), + [connection.get_active_address() for connection in connections], + ) + self._gateway.write_io_data(io_data) + ConnectionStore.reset_instance() diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/rendezvous.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/rendezvous.py new file mode 100644 index 0000000..047dc83 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/handlers/rendezvous.py @@ -0,0 +1,108 @@ +import logging +import json +import uuid +from tandem.agent.configuration import USE_RELAY +from tandem.agent.models.connection import HolePunchedConnection +from tandem.agent.stores.connection import ConnectionStore +from tandem.agent.utils.hole_punching import HolePunchingUtils +from tandem.shared.models.peer import Peer +from tandem.shared.protocol.handlers.addressed import AddressedHandler +from tandem.shared.protocol.messages.rendezvous import ( + RendezvousProtocolUtils, + RendezvousProtocolMessageType, +) +from tandem.agent.protocol.messages.interagent import ( + InteragentProtocolUtils, + NewOperations, +) +from tandem.shared.utils.static_value import static_value as staticvalue +from tandem.agent.models.connection_state import ConnectionState + + +class RendezvousProtocolHandler(AddressedHandler): + @staticvalue + def _protocol_message_utils(self): + return RendezvousProtocolUtils + + @staticvalue + def _protocol_message_handlers(self): + return { + RendezvousProtocolMessageType.SetupParameters.value: + self._handle_setup_parameters, + RendezvousProtocolMessageType.Error.value: + self._handle_error, + } + + def __init__(self, id, gateway, time_scheduler, document): + self._id = id + self._gateway = gateway + self._time_scheduler = time_scheduler + self._document = document + + def _handle_setup_parameters(self, message, sender_address): + public_address = (message.public[0], message.public[1]) + private_address = (message.private[0], message.private[1]) + logging.debug( + "Received SetupParameters - Connect to {} at public {}:{} " + "and private {}:{}" + .format(message.peer_id, *public_address, *private_address), + ) + peer = Peer( + id=uuid.UUID(message.peer_id), + public_address=public_address, + private_address=private_address, + ) + new_connection = HolePunchedConnection( + peer=peer, + initiated_connection=message.initiate, + ) + new_connection.set_interval_handle(self._time_scheduler.run_every( + HolePunchingUtils.PING_INTERVAL, + HolePunchingUtils.generate_send_ping( + self._gateway, + peer.get_addresses(), + self._id, + ), + )) + + def handle_hole_punching_timeout(connection): + if connection.get_connection_state() == ConnectionState.OPEN: + return + + if not USE_RELAY: + logging.info( + "Connection {} is unreachable. Not switching to RELAY " + "because it was disabled." + .format(connection.get_peer().get_public_address()), + ) + connection.set_connection_state(ConnectionState.UNREACHABLE) + return + + logging.info("Switching connection {} to RELAY".format( + connection.get_peer().get_public_address() + )) + + connection.set_connection_state(ConnectionState.RELAY) + + operations = self._document.get_document_operations() + payload = InteragentProtocolUtils.serialize(NewOperations( + operations_list=json.dumps(operations) + )) + io_data = self._gateway.generate_io_data( + payload, + connection.get_peer().get_public_address(), + ) + self._gateway.write_io_data( + io_data, + reliability=True, + ) + + self._time_scheduler.run_after( + HolePunchingUtils.TIMEOUT, + handle_hole_punching_timeout, + new_connection + ) + ConnectionStore.get_instance().add_connection(new_connection) + + def _handle_error(self, message, sender_address): + logging.info("Rendezvous Error: {}".format(message.message)) diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__init__.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__init__.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c07a221b514026e284e46619ba0f89cf2e89b2f0 GIT binary patch literal 201 zcmZ9GK?(vf3`Hxt5W%fyn3cJJ3@%*HM%=h5bsC03)0UR>DviEY9d-Y5|G~773EC wlM4*9qXlX29$==Qn{P-tmN-Tagv^2%w06N2t&izhwo-edXsgG*J;x&T0T)^}5dZ)H literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2170c3d647213b9b925707b10a2b8ed6c0514411 GIT binary patch literal 174 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuW0?y;?$yI{o=&j z)YKwPBrz`~HCI0|9fMO)lwXpcoS&ngn_659lrPqg ekI&4@EQycTE2zB1VUwGmQks)$2ePynh#3G?T`wvC literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/editor.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/editor.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a8a82b50efd9c08428ac2e979f4c68d67f2f80a GIT binary patch literal 10384 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 WmFWePMU>@fd-_|`v$&csPy8P}o^yKu literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/interagent.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/__pycache__/interagent.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c51bf9bce7867b7416252efb1db26851ddc14e7a GIT binary patch literal 4690 zcmb_gOK;mo5avq~ElYmKX`4P+AcqQwr9+CIf*`OBq&ZYg8wUl51_aGrNsK8{-KFeG z$SnbK>z`;ZJ@)VL+Ef2RPyJ>|>ft&MwWh#b&MrBRoo{Amb+_59-{1e${iCUAe`*Vt zh35yjaIRCTw`durcPrJe&|b{?OvKtgWKic(=?1E4Yl;FAyYpM1Ml3A z#ylXt<)!1{0d9F44cA4bM%YnknN zu|MRVm(@LQn6Pm~&kfJ}Y3xVEjP)3DGMm~%$jHnmlQ^qC9l}E8Dd=Ji#ijw3oVDeXUuV zOTMUzIzE{`tUI`#Kw;-};-mxaD21IgaeQY4>*#6dLqXh5A{?xEjHw`rV;<1Tw-twg z&_I`go3vWTEk8n&YN`H0ducp3QuDdq(_iSn=oB;1LGQM#tRlx)YNZW1E{wy}^OU76 zBeNurdRg`C!WVrBG_HAG5cyK#4dsq)2oSqyJ|uCpaGGA%tNOG_j%}ChX4;XKbRXeX zuQR=`LldK$86i_VTe{f8?A+aggk()`6R^Om=iWN zg&BCw%04$9o3!%+EI;DeJqZ^=<<~9J)$h{a~Yj?#H~_T zDRJ&+vYwLaqMvE(+Wbt3#lM3{U;r_m&LViGTIWpRS7tpIW=P!R;cQc9c43|tGa!Q)s0(E#j>DM!ja zjR0i8XC5SN=TVyDOA-Q8$M2!ODWFDh^qr%8r#!(nns4`Wkd2}-!eOQXK&(LR2|gk- zBERoI6$+%Vx3D)~_^34$E%-1=xl*9qtMhzKF_)66s-T%krf9)FPKr^qK03Y@WRove zLH2hgFbZUEC?&7PHbL}=-lwgUp`_^>*sjg4%GD@N7Z&VYkpF?$6p-I7L|!ZCX&oMt z5UNA$H3fPa3vkaavv`uUWtA11s@7l}sx_0NcBT^qs}4&T5-Ox(R!#oE|z!&%sH>Ir{$i zgo@|lxT1=u5yJdhB9K&C#j_FKU-az$(t+n4J|;xW%jV6wO&-!?`mQJxB)W*gTbINx z0pM~8y=oCfYceS9EQ)0sBizj{LdXN$@?$h54miaiMI&O6qL7)0Kr6fX@3X5qD9@sj zqJTEJBVza^npAtPDoub{7>ucEJ_uc5Dn6zVt>*{bTzk3^OHke~`; zVlGs+RiRAQ;7nu&kyNXmH|n(VsaIA}Ua{+V$o=rnI6y%se^|nPsoiFt-|8oA~W*-Jqv`0ERN!>;M1& literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.py new file mode 100644 index 0000000..ae6eda4 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.py @@ -0,0 +1,317 @@ +import json +import enum + + +class EditorProtocolMarshalError(ValueError): + pass + + +class EditorProtocolMessageType(enum.Enum): + ApplyText = "apply-text" + ApplyPatches = "apply-patches" + CheckDocumentSync = "check-document-sync" + ConnectTo = "connect-to" + HostSession = "host-session" + JoinSession = "join-session" + NewPatches = "new-patches" + SessionInfo = "session-info" + UserChangedEditorText = "user-changed-editor-text" + WriteRequest = "write-request" + WriteRequestAck = "write-request-ack" + + +class UserChangedEditorText: + """ + Sent by the editor plugin to the agent to + notify it that the user changed the text buffer. + """ + def __init__(self, contents): + self.type = EditorProtocolMessageType.UserChangedEditorText + self.contents = contents + + def to_payload(self): + return { + "contents": self.contents, + } + + @staticmethod + def from_payload(payload): + return UserChangedEditorText(payload["contents"]) + + +class CheckDocumentSync: + """ + Sent by the editor plugin to the agent to + check whether the editor and the crdt have their + document contents in sync + """ + def __init__(self, contents): + self.type = EditorProtocolMessageType.CheckDocumentSync + self.contents = contents + + def to_payload(self): + return { + "contents": self.contents, + } + + @staticmethod + def from_payload(payload): + return CheckDocumentSync(payload["contents"]) + + +class ApplyText: + """ + Sent by the agent to the editor plugin to + notify it that someone else edited the text buffer. + """ + def __init__(self, contents): + self.type = EditorProtocolMessageType.ApplyText + self.contents = contents + + def to_payload(self): + return { + "contents": self.contents, + } + + @staticmethod + def from_payload(payload): + return ApplyText(payload["contents"]) + + +class ConnectTo: + """ + Sent by the plugin to the agent to tell it to connect + to another agent. + """ + def __init__(self, host, port): + self.type = EditorProtocolMessageType.ConnectTo + self.host = host + self.port = port + + def to_payload(self): + return { + "host": self.host, + "port": self.port, + } + + @staticmethod + def from_payload(payload): + return ConnectTo(payload["host"], payload["port"]) + + +class WriteRequest: + """ + Sent by the agent to the plugin to request for the ability + to apply remote operations to the CRDT. + """ + def __init__(self, seq): + self.type = EditorProtocolMessageType.WriteRequest + self.seq = seq + + def to_payload(self): + return { + "seq": self.seq, + } + + @staticmethod + def from_payload(payload): + return WriteRequest(payload["seq"]) + + +class WriteRequestAck: + """ + Sent by the plugin to the agent in response to a WriteRequest + message to grant it permission to apply remote operations to the CRDT. + + By sending this message the plugin agrees to not allow users + to modify their local buffer until the remote operations have been + sent back to the plugin via an ApplyPatches message. + """ + def __init__(self, seq): + self.type = EditorProtocolMessageType.WriteRequestAck + self.seq = seq + + def to_payload(self): + return { + "seq": self.seq, + } + + @staticmethod + def from_payload(payload): + return WriteRequestAck(payload["seq"]) + + +class NewPatches: + """ + Sent by the plugin to the agent to inform it of changes made by + the user to their local text buffer. + + patch_list should be a list of dictionaries where each dictionary + represents a change that the user made to their local text buffer. + The patches should be ordered such that they are applied in the + correct order when the list is traversed from front to back. + + Each patch should have the form: + + { + "start": {"row": , "column": }, + "end": {"row": , "column": }, + "text": , + } + """ + def __init__(self, patch_list): + self.type = EditorProtocolMessageType.NewPatches + self.patch_list = patch_list + + def to_payload(self): + return { + "patch_list": self.patch_list + } + + @staticmethod + def from_payload(payload): + return NewPatches(payload["patch_list"]) + + +class ApplyPatches: + """ + Sent by the agent to the plugin to inform it of remote changes + that should be applied to their local text buffer. + + patch_list will be a list of dictionaries where each dictionary + represents a change that some remote user made to the text buffer. + The order of the patches is significant. They should applied in + the order they are found in this message. + + Each patch will have the form: + + { + "oldStart": {"row": , "column": }, + "oldEnd": {"row": , "column": }, + "oldText": , + "newStart": {"row": , "column": }, + "newEnd": {"row": , "column": }, + "newText": , + } + """ + def __init__(self, patch_list): + self.type = EditorProtocolMessageType.ApplyPatches + self.patch_list = patch_list + + def to_payload(self): + return { + "patch_list": self.patch_list + } + + @staticmethod + def from_payload(payload): + return ApplyPatches(payload["patch_list"]) + + +class HostSession: + """ + Sent by the plugin to the agent to ask it to start hosting a new + session. + """ + def __init__(self): + self.type = EditorProtocolMessageType.HostSession + + def to_payload(self): + return {} + + @staticmethod + def from_payload(payload): + return HostSession() + + +class JoinSession: + """ + Sent by the plugin to the agent to ask it to join an existing + session. + """ + def __init__(self, session_id): + self.type = EditorProtocolMessageType.JoinSession + self.session_id = session_id + + def to_payload(self): + return { + "session_id": str(self.session_id), + } + + @staticmethod + def from_payload(payload): + return JoinSession(payload["session_id"]) + + +class SessionInfo: + """ + Sent by the agent to the plugin to pass it the session ID. + """ + def __init__(self, session_id): + self.type = EditorProtocolMessageType.SessionInfo + self.session_id = session_id + + def to_payload(self): + return { + "session_id": str(self.session_id), + } + + @staticmethod + def from_payload(payload): + return SessionInfo(payload["session_id"]) + + +def serialize(message): + as_dict = { + "type": message.type.value, + "payload": message.to_payload(), + "version": 1, + } + return json.dumps(as_dict) + + +def deserialize(data): + try: + as_dict = json.loads(data) + message_type = as_dict["type"] + payload = as_dict["payload"] + + if message_type == EditorProtocolMessageType.ConnectTo.value: + return ConnectTo.from_payload(payload) + + elif message_type == EditorProtocolMessageType.WriteRequest.value: + return WriteRequest.from_payload(payload) + + elif message_type == EditorProtocolMessageType.WriteRequestAck.value: + return WriteRequestAck.from_payload(payload) + + elif message_type == \ + EditorProtocolMessageType.UserChangedEditorText.value: + return UserChangedEditorText.from_payload(payload) + + elif message_type == EditorProtocolMessageType.ApplyText.value: + return ApplyText.from_payload(payload) + + elif message_type == EditorProtocolMessageType.NewPatches.value: + return NewPatches.from_payload(payload) + + elif message_type == EditorProtocolMessageType.ApplyPatches.value: + return ApplyPatches.from_payload(payload) + + elif message_type == EditorProtocolMessageType.CheckDocumentSync.value: + return CheckDocumentSync.from_payload(payload) + + elif message_type == EditorProtocolMessageType.HostSession.value: + return HostSession.from_payload(payload) + + elif message_type == EditorProtocolMessageType.JoinSession.value: + return JoinSession.from_payload(payload) + + elif message_type == EditorProtocolMessageType.SessionInfo.value: + return SessionInfo.from_payload(payload) + + else: + raise EditorProtocolMarshalError + + except: + raise EditorProtocolMarshalError diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/editor.pyc new file mode 100644 index 0000000000000000000000000000000000000000..268db10bb65d1e3f848eb1faa2e1a7e219733790 GIT binary patch literal 15643 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 literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/interagent.py b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/interagent.py new file mode 100644 index 0000000..8a49df1 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/agent/protocol/messages/interagent.py @@ -0,0 +1,130 @@ +from tandem.shared.protocol.messages.base import ( + ProtocolMessageTypeBase, + ProtocolMessageBase, + ProtocolUtilsBase, +) +from tandem.shared.utils.static_value import static_value as staticvalue + + +class InteragentProtocolMessageType(ProtocolMessageTypeBase): + # Connection setup messages + Ping = "ia-ping" + PingBack = "ia-ping-back" + Syn = "ia-syn" + Hello = "ia-hello" + + # Regular interagent messages + NewOperations = "ia-new-operations" + Bye = "ia-bye" + + +class Ping(ProtocolMessageBase): + """ + Sent by the agent to a peer to maintain or open a connection. + """ + def __init__(self, **kwargs): + super(Ping, self).__init__( + InteragentProtocolMessageType.Ping, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ["id"] + + +class PingBack(ProtocolMessageBase): + """ + Sent in response to a Ping message to acknowledge receipt. + """ + def __init__(self, **kwargs): + super(PingBack, self).__init__( + InteragentProtocolMessageType.PingBack, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ["id"] + + +class Syn(ProtocolMessageBase): + """ + Sent by the connection initiator to indicate that it has + completed its connection set up and wishes to begin + communicating via regular protocol messages. + + The initiator should continue sending this message until + it receives a regular protocol message from the non-initiator. + """ + def __init__(self, **kwargs): + super(Syn, self).__init__( + InteragentProtocolMessageType.Syn, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return [] + + +class Hello(ProtocolMessageBase): + """ + Sent directly from one agent to another to introduce itself. + + This message is used to directly establish a connection. It + is sent after receiving a ConnectTo message from the plugin. + + The should_reply flag is set if the agent wants the remote + peer to respond with a Hello message containing its ID. + """ + def __init__(self, **kwargs): + super(Hello, self).__init__( + InteragentProtocolMessageType.Hello, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ["id", "should_reply"] + + +class Bye(ProtocolMessageBase): + def __init__(self, **kwargs): + super(Bye, self).__init__( + InteragentProtocolMessageType.Bye, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return [] + + +class NewOperations(ProtocolMessageBase): + """ + Sent to other agents to notify them of new CRDT operations to apply. + """ + def __init__(self, **kwargs): + super(NewOperations, self).__init__( + InteragentProtocolMessageType.NewOperations, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ['operations_list'] + + +class InteragentProtocolUtils(ProtocolUtilsBase): + @classmethod + @staticvalue + def _protocol_message_constructors(cls): + return { + InteragentProtocolMessageType.Ping.value: Ping, + InteragentProtocolMessageType.PingBack.value: PingBack, + InteragentProtocolMessageType.Syn.value: Syn, + InteragentProtocolMessageType.Hello.value: Hello, + InteragentProtocolMessageType.Bye.value: Bye, + InteragentProtocolMessageType.NewOperations.value: NewOperations, + } diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/stores/__init__.py b/rplugin/python/tandem_lib/agent/tandem/agent/stores/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/stores/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/stores/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8230f326b532d6977ba4ba09cda36a1c34e5752c GIT binary patch literal 163 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuK@kf;?$yI{o=&j z)YKwPBrz`~HCI0|9fMO`l3$cstREkrnU`4-AFo$X Vd5gm)H$SB`C)EyQQ!x-T0026)D#ic+ literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/stores/__pycache__/connection.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/stores/__pycache__/connection.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc3272b3c646e898168bb8775a206a3c5b6f5d0e GIT binary patch literal 1753 zcmah~&2G~`5Z+xozjabt71VM-LaIH%1{xMh_z))=vg4mQ3n{mq7EwQ!41U}BourKinufkmBHC~6g!q>O~bCoy1 zcyrkh*zZ$#6F;2M&>;*}2jP(kalju?qg{Y#IQ0}EiU3cw_WAVnqDU|oc>Aw>{gi5wy$hv;b$(Y@PC zpfbLiDX0ORK$cBe68w}a4Y-;U#XMd_w1reI_;bEq1F`ElP`gD?s}wR^SYjD2EVCca zkS>hk5joxd!fx1&Hqi!b3b8$B(wJyHa$$VRh}4n@m%K;>D&9n6E>swB2H}BpQ>LB- zy^`)@>a3)C=RVF<1Jg@kb`ZKn4O*pR^Z!1kx~nU13*Jgfd*SSqLKw7erhu#^x@HYK%@7)wj&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnFMs{e;?$yI{o=&j z)YKwPBrz`~HCI0|9fMO^l9^MiA0MBYmst`YuUAlc Ui^C>2KczG$)edA)F%UBV01DtLRsaA1 literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/agent/utils/__pycache__/hole_punching.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/agent/utils/__pycache__/hole_punching.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d48a4d97f1669e5b43af05927340b68d82204c2 GIT binary patch literal 1531 zcma)6&2AGh5VpOW>?S0oEfp0K5+D##F3F)nT&e&|eV~OQilcox@(zu=IL6&4moQBF%SYmSQ(o=Q_PB}YqzgS}n zF0?u!m}a94t6MPDT@X3x(KC8Ra+X_XY(hExN+$pfCvcE;8dS50H*8sgy}Irzk#au~ zN&zquy=qJ4@;d@n2{@Nv5lTDVAQ#8MP&%+fRzdvUZyhKhRZ9hl5VG|u3kL~6t6E%q z7ID_cpH?2ETqG?6`uxuTp>G`lEPj8AOS3=J6%$V%P`U|Dlna7Tn=<%DPZvOJ0+~Q* zE{ZhpSaUPZ!XQ>#&Dl*RUdXrxcgY(lOcqPH!c-bop*416s)JUqE?$m$g%lCCTpm5T zPCOyQ?JzD_HO#Jp0+~I8f-=0aF%jjq2G7M=H#$>#&%!3NY*D;I&)1~t0 z;Wa{7XqK0F#eEQC(jz(T0hkjyrV|G3LVD0L6FRMbz{}Rlvlybx=RqDwloa)7%JUyiwYym)xkb@4al&3ZHr<6g2x~N1{i@12OutH0TL+;!3>&=ek&P@K*9*(mzREMacWVqesN-M zYHE?bOMY@`Zfaghv3^QwS!zyx0f?htl9-p0nya6fj>IX>NGwWC(T|VM%*!l^kJl@x Uyv1RYo1apelWGUDqZo)80CT%2)&Kwi literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/__init__.py b/rplugin/python/tandem_lib/agent/tandem/shared/io/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..85a6c8a5e7de6581b2ec608bd01630dc28c6d7d0 GIT binary patch literal 160 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnFJJx8;?$yI{o=&j z)YKwPBrz`~HCI0|9f?z%kyw2KczG$)edA$F%UBV0IG~BV*mgE literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/base.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/base.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e204ed34e307894e084cd9e59b807a1673cc553 GIT binary patch literal 2920 zcmbtWTW{Mo6edO4Rvg=PF3pypV zNgU7Xje4=?m7$2yj-TGSrguAI3pnwiVMaFwVDp6aGv#%O)-A5_*=U>&OSDX!0~H(7 zu)0BD#+DP9_ZSJ3?1eM+*o{Jak*9{^_@s8T(2T9yD#J4)@~`J5RnHrS@@N3M=6NrV z{6YFhBXD{d0}h@u6=Bk_Py?)RM+9d$+!2m2-*Vs*^lukT&uGZNrG#rAuzhYo#tBpg z&u)1NvWl!ohrs8^C0V6;NiNG8%~e^K4VstbE!m{GCRgMt&2_m(o?1`pv)~#)LNprm z1pSzL^oWKOkTJosPdNV_@Dvk^M*H=~#M)y&H_pUF^h*!%dF7QkDMeN6!* zFK!ULf^gUy9hjhdhlI+3(i)K@$0F&>kECK~rOEGO%D|qA+@>GLesD-9OtYwE8%Vjg z7oO=>rW=zIh?BW+m`EU4Yp@Ok<7M8UUgf77IfuLh=?&Tnr1$wb!9Pn^ORv-Sq!f|e z@nYcX_lV;T^$hcCd9h||4(zk+x>2Ux4|}7J2>(GcB$m`p<{MBjtJ=6Fz3WlL^(KZb z3?E@YMrfeZz(#)t>MChw`K+7t13kiB;c(lkRqz^c&@cC!a!q`R5E u<=W}YX*<2+5Wu7yKJTj_PL+j-Me-A8JAK9M02SsrQsXtzT>cNAhHN+h literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/udp_gateway.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/__pycache__/udp_gateway.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73274fe0805bb540ac9cc7bdbbc51923d68a571d GIT binary patch literal 3074 zcmaJ@TaOz>7OtwkPLJ(4SICkF!vc~K%q#=}LTq-~Oo*aQCWuTF)MC}?xN1E1*ltf% zJ9An4$yxEnpV+7UOZ~JGPy7X*_)c}V#|hcBRkyD0s&l^cov-Rs zNdAa*gI};B+wF?t;=M_(^nTh?J87Q&Ej6lWt{%~_M{m|Yr|Osw(NT=7h{ca=pGz*q zi!gS|y<@lBGxmODE{lEIu?QaTd`o-u>P4uni}?cvIm`K+EyYmG`I2)s7ju?4lHqf{ z+jR@i%%)0ff~5$PWH1@zNrGM~rB=ptox(M0v|ofeJx(OHDuQ~77aK{^8>Pl1$$!|d z-)=uPN}Fwyj+N5eJ6UfwR+HRpOZ8lhvMCL=^K>HBcsuQ{4a`BRmE0a=+cP;$`f09? z)6=c#srF%1h+@b(#@qa?UHQ9JTiW9^g&(z4Zlf6baL$I6lUxTFce$pubYoRD(JMCl zDzBZTDI-uZes;OuX>I=Jqi;9*$eD+8FZ6q7AiFQ-Azx2Ga$EaPg{7Veh zSnkZ-r2x;t`oytpcXMy(q4+4llK3A=n%%HjAa=j$Mw(XCSE(ZIAZiDh$$E22jaZ~r~nx%=JM4~ihEW-lk^V3y15c#>#!G*c#5vg=vW6|P;* zwX3xckOL#7ll|GGmk+W@5lnS^vdgvGNb;dk?-TfE{e@0#(lTN&n}$fwM7fPWZz0eD88@-AzH%D>!e&_ z_ZJeht|+%Ap(3VHqE`l-=d(@ZA|$OMj(tov(At%d)d1D=D;r1=iDM5@%vY%9{0Fgc z7H*#{yps>+?hgCMI}88h#@s>g$|9J1Lpo4`xxcg&I=bAzK0lviU^s5+!2&P5kI@W z4%bUxt*|0>q;ea@oS-^+N@O)pb6#pnpZ8gx<-lU;{@^V5LM#Nd1vd`K4~72Zh*U=m zeF@T(1hZ=6KyBQgn7x;gdb<|O0mJcjcKtimQ-kM9 zb_mnXzRt#wpf909XCGB!bl%~{#W4-{!vr(d2bq*s(HRbIc%rA_6z|1KU{0jy4f zxT#etPm?^ui>WMlqBrOjis!2_jO;6Ro%@(JBugMVRL|!E|CVUDLbP!&YJ>dH!mI0% zq~-0m-Il&ct5f*(WvVDOi-<5QuW$MbdP+K=NhdYQnErq&5)%CxRez?6t~%#d)*@|B zDmpu9amzBY(G~5e5jMim38S!O(FDCerxo6$%0hRwybo@bSHP`7w)G6xV@=YfNxQA7 f<+*GR#q)|bEyAzLYtSvqFMBc&vqRo?+Kt};FVUjx literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/base.py b/rplugin/python/tandem_lib/agent/tandem/shared/io/base.py new file mode 100644 index 0000000..e3d551e --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/io/base.py @@ -0,0 +1,69 @@ +from threading import Thread +from tandem.shared.utils.proxy import ProxyUtils + + +class InterfaceDataBase(object): + def __init__(self, data): + self._data = data + + def get_data(self): + return self._data + + def is_empty(self): + return self._data is None + + +class InterfaceBase(object): + data_class = InterfaceDataBase + + def __init__(self, incoming_data_handler, proxies=[]): + self._incoming_data_handler = incoming_data_handler + self._reader = Thread(target=self._read_data) + self._proxies = proxies + for proxy in proxies: + proxy.attach_interface(self) + + def start(self): + self._reader.start() + + def stop(self): + self._reader.join() + + def generate_io_data(self, *args, **kwargs): + new_args, new_kwargs = ProxyUtils.run( + self._proxies, + 'pre_generate_io_data', + (args, kwargs), + ) + return self._generate_io_data(*new_args, **new_kwargs) + + def write_io_data(self, *args, **kwargs): + new_args, new_kwargs = ProxyUtils.run( + self._proxies, + 'pre_write_io_data', + (args, kwargs), + ) + return self._write_io_data(*new_args, **new_kwargs) + + def _generate_io_data(self, *args, **kwargs): + return self.data_class(*args, **kwargs) + + def _write_io_data(self, *args, **kwargs): + raise + + def _read_data(self): + raise + + def _received_data(self, *args, **kwargs): + def retrieve_io_data(): + new_args, new_kwargs = ProxyUtils.run( + self._proxies[::-1], + 'on_retrieve_io_data', + (args, kwargs), + ) + if new_args is not None and new_kwargs is not None: + return self.data_class(*new_args, **new_kwargs) + else: + return None + + self._incoming_data_handler(retrieve_io_data) diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__init__.py b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d054022afdd7d53bff41f17179c8d51176a5c2a7 GIT binary patch literal 168 zcmXr!<>j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuTcHa;?$yI{o=&j z)YKwPBrz`~HCI0|9f?z%kyw}^k(pYo gA0MBYmst`YuUAlci^C>2KczG$)edA`F%UBV03=8(qyPW_ literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/base.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/base.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e0e4fa906f13813821808052659828e54792c05 GIT binary patch literal 908 zcmbtSO>fjN5Ve!tY&NtKh>An2_8zIN4_r}IQ6z3F5aN=H<;K%CX!F6?3l#0G{U@CG zOS$qdI5FcDyVM~_RHPf?Ciyl;zSDZOAOkG=I3N?kK9{fg_ZYYAnxKq z+USGGT<}z|ixfGgY&;>cmWs&Ks@qkyRggoj;4(3DGt#D4_&PZBU8-iPt`Is{F10yKUtARP!e+6Z_I`u_@^G3{5ZHsPNP!XIph-@?uE zNuO%n&{xp9&$O;vyO{HQN9)fEGhdw`*Apk~I5C{E$mST9$~5L4c4zb> allJlxls>EnHv>_4%JkJL{#oE43h@UUeaiFz literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/fragment.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/fragment.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a913af078528e8a08b7aef728117ad6eab1ca812 GIT binary patch literal 1394 zcmZux&5q`xia?ebdwSB7pI$q? z^CS0Z=1n;94m= zv}#$KraSD)${nG}_(h;RhvB{iVMswWDVUyWrlV_GzymtICg42L6PRNy;E{CiFud}X zFhY)Cgb!yN-@$Mx2v0qc5IWqX3y9`{tkt=!jdN;gWM!IVcapK5J5wDO_y2$PC$R$D z{Ck)mI%8e#RBepSU$jRX%*o~2yfIa~#zo$#Mw>bZ&-;ZtQP$|ZZ1XjMQW}>Z@9>zf zFM1(m*_54>2?iXppqGzE#6p7e(c~sc%nMi>l6(V-r@yi-+eV(j%(rnzJinqHJ)PPo zo-gT^FUdAJf9ClM^4r(jNyk=^pLnw3S8OZ1u)p|Z6~k^a?p^`kub;y1#7_qOXW|nd zAG7V$PknUE20%s6yNk8yc~!d3BA@*S?xfvRT8?R2Os!AMd1o43h;h45)Orm&S!yT2 zV6CjGUC)(Wx;{BQ3$xfU<@%}8+5&CH6|=^ijTE|mvNN#<=tkffUJbMjVu}Y3*VafN z##q%ES+-KEPT`6B90Z|)3O1t&<20q5GFWlgV{{h1&jh`Ea+{O8oZaw)1%pt8BK!%I zJwKY93j5I0S7guCiXvO~hmNe6=RO8b5}(+YJG_B`0dEbr8jg2EIkQE^7K81}0onLrb^rhX literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/list_parameters.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/list_parameters.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a05c2fccb6037393c57ff4dadd5bf4ab1e1c2536 GIT binary patch literal 1026 zcmbtTO>Yx15cPOB+a#og0_qKv3sQ5*O7x1V3Mz2`Rh1AzS`}F)cGGRL*{$u6rrMm6 zf5M5sTpk`J7*Uu>;~Xdhv>Z3M*>3|R&V z0DI3ASHXg*K*KypxC%e9PVG0IVjb{}Q)*_eFV)CP%Pm(P@wpN z%^?X@puz=9YO1Cp93!=bUhDqZ$JDx0X=J9IHnwu!1N1?SoKk;>-M&WXvl(3Pm+Y8* zZO^!4C7*+XLBq65-iH}4;gZuDu&d@5Rsx2AlTP46H!ZXehZxI8vMBUO;c6{)I>eMrMkW*Qjl&sqH0BBj_OH*MnMyf@DD$k|xw?|PUQG>M%YDV@c#zc#TK(r6W@ zd0e1YskSi%7RCK5yT#NuGda+LHf)Wa*Fs=CgeJ5gf*rVdxj~uk>QWE$SvATd*_B9p zXq0fqXM7eob_pa#8JzQ_5ZsWO<`F_C^r6+mb01Z-tPiE>TOSQ3G*$vEp^1n6^TKF> zPt-;_Ez(>l>EsUXSc=Y|4mbOo=x)09-^2|`Wwr^3f_Y3p8CxT~OOYoA(|O1CDIQp{ zef2N45AK|FwtQWP5f*?Dz9~eOtMQQRmJnBCIlSFzStniE!xFy8RVA-^LVR}bNj;=j zrGb>Q5kehWje1zEQLVmMmQ;6HMcp*--d0if8-6-Nsa~dW<+HM?MAhFcDIU@nvUIi; F`~`r=`zHVZ literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/reliability.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/reliability.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..239470d246f3c84246c70d7ef9912672db0c334a GIT binary patch literal 2116 zcmZuyU60%}6t$g9lF7`@_Cu8bwXjr`&`8yc1cE1o3SFoWVyI|Wpr{aK$KKtfnUC6; zZg-VPNYe*?0`ENWm;A~Te}N~?_3UhC*e0@beSPg*-*fKuCp$aw!QP+AuWiQuW*d(W z`2m{QM<*(?1?D``2|FM~;#gq%HMvL%DpOa{tZdPyY1C#)a+ zgPmBP+xTGr@uRF#r`Z`s?Zg=n`C=9OssLDJ$%*(twJE-!~{7?6!nrStreb$eF z@Bq!+N2gfAB};_lQoLq~Cq3!o>#w{?S~8Ro#(`|ZpLpwU&WkbaMh|_dJw!7xx|-K4 z#o4irkKoj9TICa!n(>K}i&E*nu)a~{k?n2k|6-b!|NHasz$k5oCYvawhx@bfVxp#% z8A|n1m9sewhE+C|YBJ1@HwNY;(@GBW*>H|H<;o28n-C4=XEsXHe41Bj8j-K=0Q~&j zjX1iK4c1m;4`%exw}s}8_%Vcv6};w4t_L+MMB&xq7$eRw@{1N`14tnPA(lSDy$VI7 zi7Ck~b23|$GS$kcsZ8hDSvkw3jp19R^ieiewwIpjyi#dCOJ!DNI>b8L){AMH9l^$S zzI^yy`t;lHzy9IislAz=AQ+`ev+;8m>e-^|dz#K@1JdQvhU=Xu!Y<6ZL(+A0B$ysL z#$(>&0r&9=etz$2V6Oh?k_KI@571nU{(w;PXQB}eV)KkQe(e=V3@MhPY1J)rx8_gS zF>8WXhqb@Yeo30J4x6arg{Z^ATe2msZZ~m-c-C#`0_X%R!XjF-qFu*zv=lDlanZrL zE+W3;w1{{e6g|uXkr(%W!sv)Mow`$dM*>KABiq>^#y9<4>m94gfn(dCUY?ctxv~+I ztMC(3Y)nX`<1#bGc4#V-5)c|q%&=Y30@Jq=cn!)U4Uki_<}M(GwDv%>6#cu-^6Z7XG3M(yv|NnuHT@)MKn)a zu8?;eAk^*kxmMd$y9KqY4~$2kLRWwcXt5(j50c=qckPlSf!7lOKfim0DpxkXBnthV z^1u=0cL*Cq5slwa`taIEa(xC#kkkZ~SNMRvRR;vB1bJMNk=1XILb3v3LRhjQBC93> zA=*Xk`c56yt>rpN!@6|@a)DNk5JW|nqGHq+qY*H1X&U5)GAd~U8{?A8A+6to%SlhG z$?TcD6;Ix^u;18S2LOlgCo7ZOVUTwz47#ti<*iD)j30j zi`f-nNyYn{?ZZa^Cs&wxihS^HnUq;Op~rxqH3;03p~X+yDRo literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/unicode.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/io/proxies/__pycache__/unicode.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf273765cc87fd3855cb8a7f39ff343f46e088c5 GIT binary patch literal 1114 zcmah|%We}f6tz8%OhSryR9zLRNNj?{Or;8mMHN+0iCu+Mx`{xR89VLJd8GD)(rB_u ze}Hdb#s9GKmKDFy7591?HHAu8y2sa^@tNy$&+M$N^@bbYM?V=MzsSn)A>V^ATL6+s znv;T#C?#)+WYW7N(o=NdjhOTg$sqU*Z;=7D-FLeBdhuEqmEz8F0DH@}35g;jDhZsV zl3kLKCq3!I?8^X{Lfbzov$T@RF{OACM+XA)HxTAIKtn&UIh%V8gV~=4EnDO7lLeiJ zP1pou)_7wEoZdl7;RFt(2p|q2%rk&v(vXjvb6NwTBnzfD8~T|_x}YU%=r?wT*JCzR zCDt?xruF#6E7<6q2qWrRYZMCBEZ(-ALK!2D)eoX0*u8q692uoeVnm^oPWG#GTBx!% ziBw-yUQKY3)S{HCNZ{Db!kh@LWRg|M1mw(=Nv5qP@#NyFx0`3CPOD-v?BXr(N+F%S zT$L@JXvKk1X;CYlRa}Z%#JhQwirfq#Su(gn2OaAhm5*&W5n2?+`a&NY8=juKIl8`< zv0WjhhC3=Vde90Y`SPJ-Y5~i#>vxra(wiOtp%IN(2r+y5-@{2_N=|dk z`<$OmMczKq4}n^5AfN?T0GpywJT$C9fDUxs&cVR5Pg)73)@U;K W?h2#(fa`U%+g3Bg-^>}Z@BINxl&3ZHr<6g2x~N1{i@12OutH0TL+;!3>&=ek&P@K*9*(SD=1qacWVqesN-M zYHE?bOMY@`Zfaghv3^QwS!zyx0f?htl9-p0nya6fj>IX>NGwWC(a+6KNzEzNkB`sH a%PfhH*DI*J#bE;!EX_%^16frJ#0&tmj4O}; literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/base.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/base.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6da5e0aefb54313c0a4ad91883493e5045272368 GIT binary patch literal 317 zcmYjLu}Z{15S>l*h{s*$7ua3n7PcaX2v&EE*rZwZGJ_(U?75Q&){5Y#_)BU13oBm| zI30M8_jcaw%*Ap!|CEpNjEKI0Lmfe_0X+rX5)+$uK$}4mZWuVr8EUL?f*U~KCfmfHG(^b literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/fragment.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/models/__pycache__/fragment.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d66453e80da0262080d27bcf50064197f3b25156 GIT binary patch literal 2131 zcmb_d&2HO95Z)mvik9uzsU0Iug92>=v}_A0K(B%jv_W#JfWqiSK(U~;D^(7ElwGQ* z1^T4CK!LtNPkp7m_SAE4Idx{2R3yPJawq}L{?5+K_wCGnb9mSpoc%HSJz(rFw(;SZ zJ;z@^Lm`-elw~jiV6T|)g#VTaU&6{Cc{mP43&$-x_C^#R`e~m)25ePQ&1PJdf8~MT)0k(Lm2(fL6wurC|#U7sAG0{tvOf#96WAc$c zYU#x9@YiQ3DySG|m4^~4AEhT4>cTMg%`q>FGD-QQK1Jh9)93jjyOhdAe4bpVMIz$B z1X`vObF`~;w5!vuRsLjZA6zlT!6w6ypUst$RZLBGY0{gI<~BLT0^W zx-rl{CrXN5M!@M_W2QU5Mz{DZpOu`qD9{iE3quHDdH-b{Ot;xF23p0BTKX+Eq*Ae~ z8VgVaTnws>jdj|I100%@sVsMqI>H4_Kr>iAUYopSb;okwXDmUO&T5$x3jnqKDqfTfq&K zKJkm=;B^bV-Qc#VAeLJ?r*h#qKEc4RyBo-!SxqoN+{-!t(r1ht{}J z;B*ZwZV$18=pfhXQ~>fy$iao<)*JqV%f#oW*qGp|nB~^%I|@uk$cFOIQK6}+54U}K zzX@xMx{n(b)j)MZ!j_YdsYfOiA-xg2y+XE}oZ^?-Y)lK+%)w>MWVWG6K-bm||b1dU6?MYQ55T3)iURQz6iOZ^yg0$OjN9B zqEHizCYZj^O;kl=nVlJFYTw!C%xa(xrOR`PKbf6TvN15B~UW-a}M3Z>aK_HIu7Lv3(d zYvDy{sur-WxmC4$ z;dGD0K8ZsTM5?|=g5J}XpvKemCqj*CXojRW{HE{tp6}QldfX4o$2BP5)1Z7$gERa$ Z(!`kc+lp#IY)|_y$}ER;QI_4V^9y|Y7h(Va literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/models/base.py b/rplugin/python/tandem_lib/agent/tandem/shared/models/base.py new file mode 100644 index 0000000..58d21d0 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/models/base.py @@ -0,0 +1,2 @@ +class ModelBase(object): + pass diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/models/fragment.py b/rplugin/python/tandem_lib/agent/tandem/shared/models/fragment.py new file mode 100644 index 0000000..1048056 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/models/fragment.py @@ -0,0 +1,40 @@ +from tandem.shared.models.base import ModelBase + + +class Fragment(ModelBase): + def __init__( + self, + total_fragments, + fragment_number, + payload, + ): + self._total_fragments = total_fragments + self._fragment_number = fragment_number + self._payload = payload + + def get_total_fragments(self): + return self._total_fragments + + def get_fragment_number(self): + return self._fragment_number + + def get_payload(self): + return self._payload + + +class FragmentGroup(ModelBase): + def __init__(self, total_fragments): + self._total_fragments = total_fragments + self._buffer = [None for _ in range(total_fragments)] + + def add_fragment(self, fragment): + fragment_number = fragment.get_fragment_number() + if self._buffer[fragment_number] is None: + self._buffer[fragment_number] = fragment.get_payload() + + def is_complete(self): + non_empty_fragments = list(filter(lambda x: x, self._buffer)) + return len(non_empty_fragments) >= self._total_fragments + + def defragment(self): + return b"".join(self._buffer) if self.is_complete() else None diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/models/peer.py b/rplugin/python/tandem_lib/agent/tandem/shared/models/peer.py new file mode 100644 index 0000000..282ff80 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/models/peer.py @@ -0,0 +1,30 @@ +from tandem.shared.models.base import ModelBase + + +class Peer(ModelBase): + def __init__(self, id, public_address, private_address=None): + self._id = id + self._public_address = public_address + self._private_address = private_address + + def __eq__(self, other): + return ( + self._id == other._id and + self._public_address == other._public_address and + self._private_address == other._private_address + ) + + def get_id(self): + return self._id + + def get_addresses(self): + addresses = [self._public_address] + if self._private_address is not None: + addresses.append(self._private_address) + return addresses + + def get_public_address(self): + return self._public_address + + def get_private_address(self): + return self._private_address diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/protocol/__init__.py b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/protocol/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b85ecbc4df6564339ab80ecaa05e952e94b0231 GIT binary patch literal 166 zcmXr!<>l&3ZHr<6g2x~N1{i@12OutH0TL+;!3>&=ek&P@K*9*(SFnC)acWVqesN-M zYHE?bOMY@`Zfaghv3^QwS!zyx0f?htl9-p0nya6fj>IX>NGwWC(Jv^j&yTO7pz1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuNeK%;?$yI{o=&j z)YKwPBrz`~HCI0|9f?z%kywS5Z=wE7;6iHA|5?>$t8R6Dy9B_o{At|0xs*aQEPg`F?D#X??fU6`UQk|I0T6UfjUYm14iEssaRkj!qALl$#j#KFN^uvmZ~ zF2CA50x)PTLd%jklsoRr$xg^@m)tAAXLFWv!toLH-$=1kx`BsYf2a|?NTAe&hE=NSDwP*7%Us-3>S3m{refBK zz1Fwl0{2glbL*^I2fWTlp@{bziBY2}TB?ecs-hcOo^kWmkQG|@ipFtQ!C^Daz-^^& G&=WriPn-$> literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__pycache__/base.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/handlers/__pycache__/base.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c666798ac9be16fcf4f85fdef47000ed692c6eb GIT binary patch literal 1890 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 ni0yaOc-gD2gvcW^?a@_wOt4@6Z5&|AX=FX1 literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/__pycache__/rendezvous.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/__pycache__/rendezvous.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b288d2c43a799efd0c59bd4272a3b791e098bcf GIT binary patch literal 2743 zcmb_ePj4JG6u0N!PByzq+Ek>Vwu}%5RwBDXNaa$h(tMfBfE_?}2e5JW^;wy0CJ9Zs<|+E#WqI&Ioseb!vw_kX`P9>=fRw72dU8C^w1Rix4Izd9pNoa8z+T02qZig;+ zz!$f^|3c(kEayd~Is>gcXnA>C&iCUY&qb{FrFdNlrI))fri8A_{W6kKCbW=h>BFFq zvXIk}SNn|RFu_>eXDlmtmE!w=vDa0UwliDL8nrJPt@ZW+s(gN~v39>fb-xiggdLdf z&mSf)6&RZ+P%h-;>msf)k!v;KVlL96L}8*M*ibeBKraM!97(|^WefNuYY|1g~xy9`>KXe4(%iS|_N<;50aY)z$v_08} zCCjJ%0NMe#7aT5vC=ZOQfi8j;VSGI)5}3#X@l&F7lFtGKE+<7kHj85nFE$GvbZ8SE z^#OF6X!?enS|^saPv{YSLw})2YA{cyBd2y%RYIK55e!q3Cz>&{$&pn%N~A}%_v*(; z&J@sVgRwY`l!7lz5Vl~I+<|Tv9s?4ged^KW-g+>{o#Jyp(O>lQ87@TQ+(0$6!7pc8 z_uJjEgx7Aiz`yNMCi6%O7I7|N@5X}>mA7E=1tlc9R1aAhEz%<5?3Gxk_t6FdL#*Y! z%Z{ynIPxWw2O@p!Fz$7aF`GdGU zfM=B+ICf+77=W_n!)xfFpy-*}f(15cYvGM>0!{xLPJGtk(AJSHQ}eH80+tZN1T4;+ z&L}Xzn%2G6|9^_`7ig-D@NR47M&pWm-$x_n@MzK=$JXL{e*L%yGu`1KY~dia@wk8k z!{w$b)_dp4o%hq&7g=VEvr};Qhf<4O+6=$M=dxCEV_L7H-3+=R|OY`%jI~} zoIY^FI4-y}$@L-FyU42xKP7Je5juki`{0HdnT`ha$IXRc+}r`i-FC*^9bkM2cPWX? zkT!Oxpbpj-Re0wbuU@t!7N*8041vM4DTyWr(4DLNto=vLedsaLS>d;L+=0F0?QG-k EFOff57XSbN literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/base.py b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/base.py new file mode 100644 index 0000000..3a7f46f --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/base.py @@ -0,0 +1,55 @@ +import json +import enum + + +class ProtocolMarshalError(ValueError): + pass + + +class ProtocolMessageTypeBase(enum.Enum): + pass + + +class ProtocolMessageBase(object): + def __init__(self, message_type, **kwargs): + for key in self._payload_keys(): + setattr(self, key, kwargs.get(key, None)) + + self.type = message_type + + def _payload_keys(self): + return None + + def to_payload(self): + return {key: getattr(self, key, None) for key in self._payload_keys()} + + @classmethod + def from_payload(cls, **kwargs): + return cls(**kwargs) + + +class ProtocolUtilsBase(object): + @classmethod + def _protocol_message_constructors(cls): + return None + + @staticmethod + def serialize(message): + as_dict = { + "type": message.type.value, + "payload": message.to_payload(), + "version": 1, + } + return json.dumps(as_dict) + + @classmethod + def deserialize(cls, as_dict): + data_message_type = as_dict["type"] + data_payload = as_dict["payload"] + items = cls._protocol_message_constructors().items() + + for message_type, target_class in items: + if message_type == data_message_type: + return target_class.from_payload(**data_payload) + + raise ProtocolMarshalError diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/rendezvous.py b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/rendezvous.py new file mode 100644 index 0000000..416a1b3 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/protocol/messages/rendezvous.py @@ -0,0 +1,70 @@ +from tandem.shared.protocol.messages.base import ( + ProtocolMessageBase, + ProtocolMessageTypeBase, + ProtocolUtilsBase, +) +from tandem.shared.utils.static_value import static_value as staticvalue + + +class RendezvousProtocolMessageType(ProtocolMessageTypeBase): + ConnectRequest = "rv-connect-request" + SetupParameters = "rv-setup-parameters" + Error = "rv-error" + + +class ConnectRequest(ProtocolMessageBase): + """ + Sent by an agent to request to join an existing session. + """ + def __init__(self, **kwargs): + super(ConnectRequest, self).__init__( + RendezvousProtocolMessageType.ConnectRequest, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ["session_id", "my_id", "private_address"] + + +class SetupParameters(ProtocolMessageBase): + """ + Sent by the server to agents to inform them to connect. + """ + def __init__(self, **kwargs): + super(SetupParameters, self).__init__( + RendezvousProtocolMessageType.SetupParameters, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ["session_id", "peer_id", "initiate", "public", "private"] + + +class Error(ProtocolMessageBase): + """ + Sent by the server to send an error message. + """ + def __init__(self, **kwargs): + super(Error, self).__init__( + RendezvousProtocolMessageType.Error, + **kwargs, + ) + + @staticvalue + def _payload_keys(self): + return ["message"] + + +class RendezvousProtocolUtils(ProtocolUtilsBase): + @classmethod + @staticvalue + def _protocol_message_constructors(cls): + return { + RendezvousProtocolMessageType.ConnectRequest.value: + ConnectRequest, + RendezvousProtocolMessageType.SetupParameters.value: + SetupParameters, + RendezvousProtocolMessageType.Error.value: Error, + } diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/stores/__init__.py b/rplugin/python/tandem_lib/agent/tandem/shared/stores/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/__init__.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28eb90f8ba759aefaaea56a1cf7221bba1ca6969 GIT binary patch literal 164 zcmXr!<>l&3ZHr<6g2x~N1{i@12OutH0TL+;!3>&=ek&P@K*9*(SD=1qacWVqesN-M zYHE?bOMY@`Zfaghv3^QwS!zyx0f?htl9-p0nya6fj>IX>NGwWC(JwB^FG?-ekB`sH b%PfhH*DI*J#bJ}1pHiBWY6r5a7>F4FxNgG|w!WghUl3_pIye@ueNiu+cxOWC4X7zzbERUXOPc&bn>?+vzjSI2-22 zgcYx`vhoR?`fccO*$!DnXSJ|N+QEB0`Ca?BDc)-%tt)n}b=%jvtj)5Je4zEyGAp(> zZfkpx7a38B&Ac{NJlQ=7VzCoMij}`2aMmLgc!SWro5Wrl*7pm{8&`LDZjAF0U2Ij1 I+ytHi-=}|$LI3~& literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/fragment.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/stores/__pycache__/fragment.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24744e51ae8b8b9f964372deb4e0b61122377ae1 GIT binary patch literal 1244 zcma)6&2H2%5VqrFH=Cv`g0^T6m5?|^Ldl^wRH_OPy`Ys)#U&Rjtledo{4931h$=m8 zc@j>%Qm&kM1y0PwoBb(!z*5Fzdpz^aH{+b_?|WxQKjQC< z6H*Zl*x?c#0+d5OlTz`kB|<(%h#C%TEz_%?M0MfPpZC!REtQURl7V&frWnmJ9~}vK zEz@F#i>OR;A+snMZ!GkuL`e~8%vVQklyLT~b~&HqlalieLPLy%-W;_|HCcqM$q0*p z18t!|CY^VnRJ0;f^0})HFtI7DPnYw6+}f2@*>Luvq!pW53$mcMPUTdv4^|)tWXdYG zXo_hFv6TZ}6~d_>k7dcrqD;~iJv#OiAy&(vqYzQug-ys$*Iot^2;dV@yKS~{ztxuP z#=TsAHF`s@5iWY_PjVo-Tt(=?rhWi|P>*_+M_uYu*Sb00ChFd;5pskB=T-U9jp1lO zF!yB(G#Ll1$wcjejq*`o-8Xba8I}K{A;`$(nD~uhV*jXm;VRqP|2*g zmiKTy*}(N|8`nWu3YLech6eLYo8Ef+|4bwg*Ztxma$-l$2z%*x5>3<<`Cl zCticc$dwbXz=@fpq1i5~BYSLT?0oaji8W2$AP>4hb z_yFjkyakj-+{na3{}iIpZ}NgjG}F_nwC1gLCgq*Jc{Dkp?q1ShAQ1?3QlNJWiA0G$ zSszC@COg6j24u%LrJKX%$w!lC`aH|CYSZ4H5^LMo^3Ct4yO4$qYEVQ==fr|()Jkvi zQlq1V?@a#r2iP$={`nN2Ib&VybYYB*-6JF>u`C_?P2nbi5WU&DXJg+DY{Kp+t|M99UD7SEi?7r5(!( zA{5szc68bHUPKd4@RBMl&3ZHr<6g2x~N1{i@12OutH0TL+;!3>&=ek&P@K*9*(SAc$KacWVqesN-M zYHE?bOMY@`Zfaghv3^QwS!zyx0f?htl9-p0nya6fj>IX>NGwWC(Jw8@%qiB7kI&4@ aEQycTE2zB1VUwGmQks)$2ePRch#3Hfw<|mV literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/fragment.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/fragment.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2201719586035fbe1419b1521697e747a97677f GIT binary patch literal 3150 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 literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/static_value.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/static_value.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c91fea25367994b23811826a65f52c129ed92916 GIT binary patch literal 507 zcmY*WyG{c!5L`PCjwm@%A<;mCkgkaf+9;x+z6R2yi7eX*NA3~Xz93Mjh)B}#AN3R SUjy$B23aA?R1Lio)$j);!FdJ% literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/time_scheduler.cpython-36.pyc b/rplugin/python/tandem_lib/agent/tandem/shared/utils/__pycache__/time_scheduler.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73f417ce94aac251897b26ec0b9f70d18b3b1d05 GIT binary patch literal 4694 zcmcgwO>-kh8Sb7LjYeAA>-ByT5*V@+tVpn{2nCna5-J2j6=e=~KG1=w@pQ|Ub~NMd zZmnaL$i++90kT!xfI}{vIC16&kbl5st{f=-0w${QnIevS&H}?DfIOqqQ{e%f$1kafW&b)pL zzgi;1uModN!S`6N{cliVyFIT8_m7lJMb&!enJi33pX;YNvX3v4(J_5b@V+nZS@OIe zIKuswXp87M1M!aNVr+{WVhv*?*2M8P8e>j$V z*^v~JT&n3mx-p*mw~kG!pP(;eH7Vj@5t}0!4<=@!WLy?;TEy}rnN3Wow!3!cKIWfJ zeOyhQkVI}49g#D-jDcy**eDdyr z&!s%Bx{EzMzY4U>2mPpKtH1R{uKvQ*Z5;TY_jlgYQt2I?j-^yPKPt1y7~JR`0j~1$ zn11Y-v=DNtjDdLfVa2A|$r zf#P<(xUpG?^=-7Dqv>sQ2KG7QziQ1|BiLr-!G_+fCAjdOvq3QPKl5h63x38qo3+SB z)4y9ZTtYYyM35W~h5+SYQe*@~LN{TnR#%)3j8qPWwGro#Cvy7)Iwj#3g^tts2)fB7 z2&4w@^d(D+Oy-Yz@mQLpQjkZvPjpJdm6GrsZEAlrM78F~A(;-#AbRs~yzQas0G)** z-?dh}+}*)^6}_3K;{%aCen?x;*tEB*b%L6eb^Fb{%+g#x#*5nn5Vezct!UMzmd0T; zal%$N8(X{my-;n@k?YoVYn@e5El(A|S9BFVIdP*Sv&6M!xYO`+pExFptQ`R_p8bn> zz$0&Z{~8$@=(K1X8bUkM-$G~DMR>+P@y^*fKljf4^WeO79uC?TJkckui`E(A?7}11 zq`!lb8yoG-LyL<&S0D#A&t(cL+5m@u2jDzQ^I8M{Z`3r5Ta1=Pei|Q6Qk8-&Niu-l zhU3e;#)!JdTIz8v5hQyCgg9?Ou!pzQw%v|@YGP4>eo>mmaSa)J>hcCk zFB;C4W(NFSr#J#XJu41CQGueCeihw?H-ceD{KCKB7v4o{5L~d&yo-4hn7c>MQxC9K zeTh2S+H0$q>HW*peFa^$d07+c75dyo%&apDLD8X=fU3TR?t5sOVjF7!8UUcv*S-i( zE8@b%7Ib@D5z>1t>pd#QyW`7pNL*aavRi5Ai50hd;X?Mj9wu~zj@^j72i=fg8mYaFW<^7sWg||`VXGN$`q##)d&qusBlsX?6fXY$xITM?6OMAZutsJs-V9%M=4 z7y4dUlE9#>IVnC`nJELa_Vo6u3GLPuCDA&&f$(Ds2qdQwA9F147 z_Dp&FKXmp($fV>rMcyn9<6~I}8eLW9Qj%47oqDV$in5`em&GB}wW3UytQzk;Nr%Q2 zRyb;YK(oIo|5hk8)BC%?X$JzUF1pG`QgFHrM&+=$TD2%dAo9qqR)u7k?@yjt|se*Hn)b5a{9_88EdC= zIXu=tNFBjdQMzzRl2mLNT5^s>tRxT+05nC;s2%WbC)$c$iaOC+6h^nAn~GLsXw5?| z{T7-g(=P6}RJh}gYcJ4O-Eg7ku1$|Cv4MzLyWPW!yT<`!%{|WA$Af+5fna^LZtX51 z&&2~pDSSy%n-EA#zebyz1J@Ql-JDm$i>u0V+HBaOVq-ZY5QykfGG}bR7pU*kiG&cR zbSfmSfyFm5^&2!>@6+{Zx>0lC;?xSHo77GA!MOxgPC%gpuQ}n{n0i4ckN}KN@6_~{ zo%Cu0kO$TVs86!8%mZz=kSAA%SzD3)e(BT)^{JOH8oLN{-@!ul26f-1&LWr;qlkA` z+*x=oI%`8Jte&IUFpf(^D+(gtm0NAKPFv}IP<4#`zYN^i-oNeL+(OtcT>?hq0=QQ> QFMCW1ugUXGf8*o-0&IC|ng9R* literal 0 HcmV?d00001 diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/fragment.py b/rplugin/python/tandem_lib/agent/tandem/shared/utils/fragment.py new file mode 100644 index 0000000..aa1bcc6 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/utils/fragment.py @@ -0,0 +1,100 @@ +from tandem.shared.stores.fragment import FragmentStore +from tandem.shared.models.fragment import Fragment + + +class FragmentUtils(object): + HEADER = b"\x54\x01" + FRAGMENT_HEADER = b"\x46\x52" + FRAGMENT_HEADER_LENGTH = len(HEADER) + len(FRAGMENT_HEADER) + 6 + + MAX_SEQUENCE_NUMBER = int(0xFFFF) + next_sequence_number = -1 + + @classmethod + def is_fragment(cls, message): + return ( + message[0:2] == cls.HEADER and + message[2:4] == cls.FRAGMENT_HEADER + ) + + @staticmethod + def should_fragment(message, max_message_length): + return len(message) > max_message_length + + @classmethod + def get_next_sequence_number(cls): + cls.next_sequence_number += 1 + cls.next_sequence_number %= cls.MAX_SEQUENCE_NUMBER + 1 + + return cls.next_sequence_number + + @staticmethod + def serialize(fragment, sequence_number): + result = [] + result.append(FragmentUtils.HEADER) + result.append(FragmentUtils.FRAGMENT_HEADER) + result.append( + fragment.get_total_fragments().to_bytes(2, byteorder="big") + ) + result.append( + sequence_number.to_bytes(2, byteorder="big") + ) + result.append( + fragment.get_fragment_number().to_bytes(2, byteorder="big") + ) + result.append(fragment.get_payload()) + return b"".join(result) + + @staticmethod + def deserialize(message): + total_fragments = int.from_bytes(message[4:6], byteorder="big") + sequence_number = int.from_bytes(message[6:8], byteorder="big") + fragment_number = int.from_bytes(message[8:10], byteorder="big") + payload = message[10:] + + new_fragment = Fragment(total_fragments, fragment_number, payload) + return new_fragment, sequence_number + + @classmethod + def fragment(cls, payload, max_message_length): + max_payload_length = max_message_length - cls.FRAGMENT_HEADER_LENGTH + + payloads = [ + payload[i:i + max_payload_length] + for i in range(0, len(payload), max_payload_length) + ] + + fragments = [ + Fragment(len(payloads), index, payload) + for index, payload in enumerate(payloads) + ] + + sequence_number = FragmentUtils.get_next_sequence_number() + messages = [ + FragmentUtils.serialize(fragment, sequence_number) + for fragment in fragments + ] + + return messages + + @staticmethod + def defragment(raw_data, sender_address): + fragment_store = FragmentStore.get_instance() + fragment, sequence_number = FragmentUtils.deserialize(raw_data) + fragment_store.insert_fragment( + sender_address, + sequence_number, + fragment + ) + fragment_group = fragment_store.get_fragment_group( + sender_address, + sequence_number, + ) + + defragmented_data = fragment_group.defragment() + if fragment_group.is_complete(): + fragment_store.remove_fragment_group( + sender_address, + sequence_number, + ) + return defragmented_data diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/proxy.py b/rplugin/python/tandem_lib/agent/tandem/shared/utils/proxy.py new file mode 100644 index 0000000..8c05a9a --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/utils/proxy.py @@ -0,0 +1,7 @@ +class ProxyUtils(object): + @staticmethod + def run(proxies, method, data): + for proxy in proxies: + data = getattr(proxy, method, lambda x: x)(data) + + return data diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/relay.py b/rplugin/python/tandem_lib/agent/tandem/shared/utils/relay.py new file mode 100644 index 0000000..0b4cae0 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/utils/relay.py @@ -0,0 +1,42 @@ +class RelayUtils(object): + HEADER = b"\x54\x01" + RELAY_HEADER = b"\x52\x45" + + @classmethod + def is_relay(cls, raw_data): + return ( + raw_data[0:2] == cls.HEADER and + raw_data[2:4] == cls.RELAY_HEADER + ) + + @staticmethod + def serialize(payload, address): + result = [] + ip, port = address + ip_binary = map( + lambda x: (int(x)).to_bytes(1, byteorder="big"), + ip.split("."), + ) + + result.append(RelayUtils.HEADER) + result.append(RelayUtils.RELAY_HEADER) + result.extend(ip_binary) + result.append(port.to_bytes(2, byteorder="big")) + result.append(payload) + + return b"".join(result) + + @staticmethod + def deserialize(raw_data): + ip = ".".join([ + str(int.from_bytes(raw_data[4:5], byteorder="big")), + str(int.from_bytes(raw_data[5:6], byteorder="big")), + str(int.from_bytes(raw_data[6:7], byteorder="big")), + str(int.from_bytes(raw_data[7:8], byteorder="big")), + ]) + port = int.from_bytes(raw_data[8:10], byteorder="big") + + address = (ip, port) + payload = raw_data[10:] + + return payload, address diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/reliability.py b/rplugin/python/tandem_lib/agent/tandem/shared/utils/reliability.py new file mode 100644 index 0000000..1be1421 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/utils/reliability.py @@ -0,0 +1,64 @@ +from tandem.shared.stores.reliability import ReliabilityStore + + +class ReliabilityUtils(object): + HEADER = b"\x54\x01" + RELIABILITY_HEADER = b"\x52\x4C" + ACK_HEADER = b"\x41\x43" + ACK_TIMEOUT = 3 + + MAX_ACK_NUMBER = int(0xFFFF) + next_ack_number = -1 + + @classmethod + def get_next_ack_number(cls): + cls.next_ack_number += 1 + cls.next_ack_number %= cls.MAX_ACK_NUMBER + 1 + + return cls.next_ack_number + + @classmethod + def is_ack(cls, raw_data): + return ( + raw_data[0:2] == cls.HEADER and + raw_data[2:4] == cls.ACK_HEADER + ) + + @classmethod + def is_ackable(cls, raw_data): + return ( + raw_data[0:2] == cls.HEADER and + raw_data[2:4] == cls.RELIABILITY_HEADER + ) + + @staticmethod + def should_resend_payload(ack_id): + return ReliabilityStore.get_instance().get_payload(ack_id) + + @staticmethod + def generate_ack(ack_id): + result = [] + result.append(ReliabilityUtils.HEADER) + result.append(ReliabilityUtils.ACK_HEADER) + result.append((ack_id).to_bytes(2, byteorder="big")) + return b"".join(result) + + @staticmethod + def parse_ack(raw_data): + return int.from_bytes(raw_data[4:6], byteorder="big") + + @staticmethod + def serialize(payload): + result = [] + ack_number = ReliabilityUtils.get_next_ack_number() + result.append(ReliabilityUtils.HEADER) + result.append(ReliabilityUtils.RELIABILITY_HEADER) + result.append(ack_number.to_bytes(2, byteorder="big")) + result.append(payload) + return b"".join(result), ack_number + + @staticmethod + def deserialize(raw_data): + ack_id = int.from_bytes(raw_data[4:6], byteorder="big") + payload = raw_data[6:] + return payload, ack_id diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/static_value.py b/rplugin/python/tandem_lib/agent/tandem/shared/utils/static_value.py new file mode 100644 index 0000000..582de9c --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/utils/static_value.py @@ -0,0 +1,11 @@ +def static_value(inner_function): + # Using dictionary to workaround Python2's lack of `nonlocal` + dict_value = {} + + def outer_function(*args, **kwargs): + if dict_value.get('value', None) is None: + dict_value['value'] = inner_function(*args, **kwargs) + + return dict_value['value'] + + return outer_function diff --git a/rplugin/python/tandem_lib/agent/tandem/shared/utils/time_scheduler.py b/rplugin/python/tandem_lib/agent/tandem/shared/utils/time_scheduler.py new file mode 100644 index 0000000..1cec602 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/tandem/shared/utils/time_scheduler.py @@ -0,0 +1,146 @@ +import sched +import time +from threading import Thread, Event + + +class TimeScheduler: + """ + Schedules tasks to run in the future on an executor. + + The queue of tasks to execute is inspected every + resolution_seconds. So the minimal delay for a task + is the resolution of this scheduler. + """ + def __init__(self, executor, resolution_seconds=0.1): + self._executor = executor + self._resolution_seconds = resolution_seconds + + self._shutting_down = False + self._shut_down_event = Event() + self._runner = Thread(target=self._run_scheduler) + self._scheduler = sched.scheduler(time.time, time.sleep) + + def run_after(self, delay_seconds, function, *args, **kwargs): + """ + Schedules the specified function on the executor after delay_seconds. + + This returns a handle that has a cancel() method to cancel the + request to run this function. + """ + handle = _Handle(self) + handle.set_event_handle(self._schedule_after( + delay_seconds, + function, + handle, + lambda: None, + *args, + **kwargs, + )) + return handle + + def run_every(self, interval_seconds, function, *args, **kwargs): + """ + Schedules the specified function at least every interval_seconds. + + This returns a handle that has a cancel() method to cancel the + request to run this function. + + This only guarantees that at least interval_seconds elapses + between each invocation of the function. It does not guarantee + that the function runs exactly every interval_seconds. + """ + handle = _Handle(self) + + def reschedule(): + handle.set_event_handle(self._schedule_after( + interval_seconds, + function, + handle, + reschedule, + *args, + **kwargs, + )) + + reschedule() + return handle + + def start(self): + """ + Starts this scheduler. + + Until this is called, no tasks will actually be scheduled. + However the scheduler will still accept schedule requests. + """ + self._runner.start() + + def stop(self): + """ + Stops this scheduler. + + All remaining pending tasks after this returns will no + longer be scheduled. This does not wait for all pending + tasks to be scheduled. + """ + self._shutting_down = True + self._shut_down_event.set() + self._runner.join() + + def _cancel(self, event_handle): + try: + self._scheduler.cancel(event_handle) + except ValueError: + pass + + def _schedule_after( + self, + delay_seconds, + function, + handle, + epilogue, + *args, + **kwargs, + ): + return self._scheduler.enter( + delay_seconds, + 0, + self._executor.submit, + (self._run_if_not_cancelled, function, handle, epilogue, *args), + kwargs, + ) + + def _run_if_not_cancelled( + self, + function, + handle, + epilogue, + *args, + **kwargs, + ): + if handle.is_cancelled(): + return + try: + function(*args, **kwargs) + finally: + epilogue() + + def _run_scheduler(self): + while not self._shutting_down: + self._scheduler.run(blocking=False) + self._shut_down_event.wait(timeout=self._resolution_seconds) + + +class _Handle: + def __init__(self, scheduler): + self._scheduler = scheduler + self._event_handle = None + self._cancelled = False + + def cancel(self): + self._cancelled = True + self._scheduler._cancel(self._event_handle) + + def is_cancelled(self): + return self._cancelled + + def set_event_handle(self, new_handle): + self._event_handle = new_handle diff --git a/rplugin/python/tandem_lib/agent/test_client.py b/rplugin/python/tandem_lib/agent/test_client.py new file mode 100644 index 0000000..93ad386 --- /dev/null +++ b/rplugin/python/tandem_lib/agent/test_client.py @@ -0,0 +1,317 @@ +import os +import time +import random +from subprocess import Popen, PIPE +import tandem.agent.protocol.messages.editor as m +from tandem.agent.configuration import BASE_DIR + + +def start_agent(extra_args=None): + if extra_args is None: + extra_args = [] + return Popen( + ["python3", os.path.join(BASE_DIR, "main.py")] + extra_args, + stdin=PIPE, + stdout=PIPE, + encoding="utf-8", + ) + + +def send_user_changed(agent_stdin, text): + message = m.UserChangedEditorText(text) + agent_stdin.write(m.serialize(message)) + agent_stdin.write("\n") + agent_stdin.flush() + + +def send_new_patches(agent_stdin, start, end, text): + patches = m.NewPatches([{ + "start": start, + "end": end, + "text": text, + }]) + agent_stdin.write(m.serialize(patches)) + agent_stdin.write("\n") + agent_stdin.flush() + + +def send_request_write_ack(agent_stdin, seq): + agent_stdin.write(m.serialize(m.WriteRequestAck(seq))) + agent_stdin.write("\n") + agent_stdin.flush() + + +def print_raw_message(agent_stdout): + resp = agent_stdout.readline() + print("Received: " + resp) + + +def extract_message(agent_stdout): + resp = agent_stdout.readline() + return m.deserialize(resp) + + +def get_string_ports(): + starting_port = random.randint(60600, 62600) + port1 = str(starting_port) + port2 = str(starting_port + 1) + return port1, port2 + + +def ping_test(): + """ + Starts 2 agents and checks that they can establish + a connection to eachother and exchange a ping message. + """ + agent1_port, agent2_port = get_string_ports() + + agent1 = start_agent(["--port", agent1_port]) + agent2 = start_agent([ + "--port", + agent2_port, + "--log-file", + "/tmp/tandem-agent-2.log", + ]) + + # Wait for the agents to start accepting connections + time.sleep(1) + + message = m.ConnectTo("localhost", int(agent1_port)) + agent2.stdin.write(m.serialize(message)) + agent2.stdin.write("\n") + agent2.stdin.flush() + + # Wait for the pings + time.sleep(2) + + agent1.stdin.close() + agent1.terminate() + agent2.stdin.close() + agent2.terminate() + + agent1.wait() + agent2.wait() + + +def text_transfer_test(): + """ + Tests the Milestone 1 flow by starting 2 agents and + transfering text data from one agent to the other. + + 1. Instruct agent 2 to connect to agent 1 + 2. Send a "text changed" message to agent 1 + (simulating what the plugin would do) + 3. Expect an "apply text" message to be "output" by agent 2 + (this would be an instruction to the plugin to change + the editor's text buffer) + """ + agent1_port, agent2_port = get_string_ports() + + agent1 = start_agent(["--port", agent1_port]) + agent2 = start_agent([ + "--port", + agent2_port, + "--log-file", + "/tmp/tandem-agent-2.log", + ]) + + # Wait for the agents to start accepting connections + time.sleep(1) + + message = m.ConnectTo("localhost", int(agent1_port)) + agent2.stdin.write(m.serialize(message)) + agent2.stdin.write("\n") + agent2.stdin.flush() + + # Wait for the pings + time.sleep(2) + + # Simulate a text buffer change - the plugin notifes agent1 that + # the text buffer has changed + send_user_changed(agent1.stdin, ["Hello world!"]) + + # Expect agent2 to receive a ApplyText message + print_raw_message(agent2.stdout) + + # Repeat + send_user_changed(agent1.stdin, ["Hello world again!"]) + print_raw_message(agent2.stdout) + + # Shut down the agents + agent1.stdin.close() + agent1.terminate() + agent2.stdin.close() + agent2.terminate() + + agent1.wait() + agent2.wait() + + +def crdt_test(): + """ + Tests the Milestone 2 flow. + 1. Agent 1 makes a local change + 2. Check that Agent 2 received the changes + 3. Repeat + 4. Agent 2 makes a local change + 5. Check that Agent 1 received the changes + """ + agent1_port, agent2_port = get_string_ports() + + agent1 = start_agent(["--port", agent1_port]) + agent2 = start_agent([ + "--port", + agent2_port, + "--log-file", + "/tmp/tandem-agent-2.log", + ]) + + # Wait for the agents to start accepting connections + time.sleep(1) + + message = m.ConnectTo("localhost", int(agent1_port)) + agent2.stdin.write(m.serialize(message)) + agent2.stdin.write("\n") + agent2.stdin.flush() + + # Wait for connection + time.sleep(1) + + # Simulate a text buffer change - the plugin notifes agent1 that + # the text buffer has changed + send_new_patches( + agent1.stdin, + {"row": 0, "column": 0}, + {"row": 0, "column": 0}, + "Hello world!", + ) + print("Agent 1 made an edit") + + # Simulate a text buffer change - the plugin notifes agent1 that + # the text buffer has changed + send_new_patches( + agent1.stdin, + {"row": 0, "column": 12}, + {"row": 0, "column": 0}, + " Hello world again!", + ) + print("Agent 1 made a second edit") + + # The agent should not resend the request write message + time.sleep(1) + + # Expect agent2 to receive a "Request Write" message + print_raw_message(agent2.stdout) + + # Allow the plugin to apply the remote changes + send_request_write_ack(agent2.stdin, 0) + + # Expect agent2 to get the changes + print_raw_message(agent2.stdout) + + # Simulate an edit that occurs on agent2's machine + send_new_patches( + agent2.stdin, + {"row": 0, "column": 0}, + {"row": 0, "column": 0}, + "Agent 2 says hi! ", + ) + print("Agent 2 made an edit") + + # Expect agent1 to receive a RequestWrite message + print_raw_message(agent1.stdout) + + # Allow changes to be applied + send_request_write_ack(agent1.stdin, 0) + + # Expect to receive the text patches + print_raw_message(agent1.stdout) + + # Simulate an edit that occurs on agent2's machine + send_new_patches( + agent2.stdin, + {"row": 0, "column": 0}, + {"row": 0, "column": 0}, + "Agent 2 says hi again! ", + ) + print("Agent 2 made a second edit!") + + # Expect agent1 to receive a RequestWrite message + print_raw_message(agent1.stdout) + + # Allow changes to be applied + send_request_write_ack(agent1.stdin, 1) + + # Expect to receive the text patches + print_raw_message(agent1.stdout) + + time.sleep(2) + + # Shut down the agents + agent1.stdin.close() + agent1.terminate() + agent2.stdin.close() + agent2.terminate() + + agent1.wait() + agent2.wait() + + +def hole_punch_test(): + agent1_port, agent2_port = get_string_ports() + agent3_port = str(int(agent2_port) + 1) + + agent1 = start_agent(["--port", agent1_port]) + agent2 = start_agent([ + "--port", + agent2_port, + "--log-file", + "/tmp/tandem-agent-2.log", + ]) + agent3 = start_agent([ + "--port", + agent3_port, + "--log-file", + "/tmp/tandem-agent-3.log", + ]) + + # Wait for the agents to start up + time.sleep(1) + + host_session = m.HostSession() + agent1.stdin.write(m.serialize(host_session)) + agent1.stdin.write("\n") + agent1.stdin.flush() + + session_info = extract_message(agent1.stdout) + print("Session ID: {}".format(session_info.session_id)) + + join_session = m.JoinSession(session_id=session_info.session_id) + agent2.stdin.write(m.serialize(join_session)) + agent2.stdin.write("\n") + agent2.stdin.flush() + agent3.stdin.write(m.serialize(join_session)) + agent3.stdin.write("\n") + agent3.stdin.flush() + + time.sleep(5) + + # Shut down the agents + agent1.stdin.close() + agent1.terminate() + agent2.stdin.close() + agent2.terminate() + agent3.stdin.close() + agent3.terminate() + + agent1.wait() + agent2.wait() + agent3.wait() + + +def main(): + hole_punch_test() + + +if __name__ == "__main__": + main() diff --git a/rplugin/python/tandem_lib/crdt/build/bundle.js b/rplugin/python/tandem_lib/crdt/build/bundle.js new file mode 100644 index 0000000..b164814 --- /dev/null +++ b/rplugin/python/tandem_lib/crdt/build/bundle.js @@ -0,0 +1,16615 @@ +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 44); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* + * Displays a helpful message and the source of + * the format when it is invalid. + */ +class InvalidFormatError extends Error { + constructor(formatFn) { + super(`Format functions must be synchronous taking a two arguments: (info, opts) +Found: ${formatFn.toString().split('\n')[0]}\n`); + + Error.captureStackTrace(this, InvalidFormatError); + } +} + +/* + * function format (formatFn) + * Returns a create function for the `formatFn`. + */ +module.exports = function (formatFn) { + if (formatFn.length > 2) { + throw new InvalidFormatError(formatFn); + } + + /* + * function Format (options) + * Base prototype which calls a `_format` + * function and pushes the result. + */ + function Format(options) { this.options = options || {}; } + Format.prototype.transform = formatFn; + + // + // Create a function which returns new instances of + // FormatWrap for simple syntax like: + // + // require('winston').formats.json(); + // + function createFormatWrap(opts) { + return new Format(opts); + } + + // + // Expose the FormatWrap through the create function + // for testability. + // + createFormatWrap.Format = Format; + return createFormatWrap; +}; + + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + +module.exports = require("util"); + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/* + * @api public + * @property {function} format + * Both the construction method and set of exposed + * formats. + */ +const format = exports.format = __webpack_require__(0); + +/* + * @api public + * @method {function} levels + * Registers the specified levels with logform. + */ +exports.levels = __webpack_require__(15); + +/* + * @api private + * method {function} exposeFormat + * Exposes a sub-format on the main format object + * as a lazy-loaded getter. + */ +function exposeFormat(name, path) { + path = path || name; + Object.defineProperty(format, name, { + get: function () { return __webpack_require__(99)("./" + path); } + }); +} + +// +// Setup all transports as lazy-loaded getters. +// +exposeFormat('align'); +exposeFormat('cli'); +exposeFormat('combine'); +exposeFormat('colorize'); +exposeFormat('json'); +exposeFormat('label'); +exposeFormat('logstash'); +exposeFormat('padLevels', 'pad-levels'); +exposeFormat('prettyPrint', 'pretty-print'); +exposeFormat('printf'); +exposeFormat('simple'); +exposeFormat('splat'); +exposeFormat('timestamp'); +exposeFormat('uncolorize'); + + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + +module.exports = require("stream"); + +/***/ }), +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +let localDocument = null; + +/* harmony default export */ __webpack_exports__["a"] = ({ + getDocument: () => localDocument, + setDocument: newDocument => { + localDocument = newDocument; + } +}); + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const stream = __webpack_require__(3); +const util = __webpack_require__(1); +const LEVEL = Symbol.for('level'); + +/** + * Constructor function for the TransportStream. This is the base prototype + * that all `winston >= 3` transports should inherit from. + * + * @param {Object} opts Options for this TransportStream instance + * @param {String} opts.level HIGHEST level according to RFC5424. + * @param {Boolean} opts.handleExceptions If true, info with { exception: true } will be written. + * @param {Function} opts.log Custom log function for simple Transport creation + * @param {Function} opts.close Called on "unpipe" from parent + */ +var TransportStream = module.exports = function TransportStream(opts) { + stream.Writable.call(this, { objectMode: true }); + opts = opts || {}; + + this.format = opts.format; + this.level = opts.level; + this.handleExceptions = opts.handleExceptions; + + if (opts.log) this.log = opts.log; + if (opts.logv) this.logv = opts.logv; + if (opts.close) this.close = opts.close; + + var self = this; + + // + // Get the levels from the source we are piped from. + // + this.once('pipe', function (logger) { + // + // Remark (indexzero): this bookkeeping can only support multiple + // Logger parents with the same `levels`. This comes into play in + // the `winston.Container` code in which `container.add` takes + // a fully realized set of options with pre-constructed TransportStreams. + // + self.levels = logger.levels; + self.level = self.level || logger.level; + self.parent = logger; + }); + + // + // If and/or when the transport is removed from this instance + // + this.once('unpipe', function (src) { + // + // Remark (indexzero): this bookkeeping can only support multiple + // Logger parents with the same `levels`. This comes into play in + // the `winston.Container` code in which `container.add` takes + // a fully realized set of options with pre-constructed TransportStreams. + // + if (src === self.parent) { + this.parent = null; + if (self.close) { + self.close(); + } + } + }); +}; + +util.inherits(TransportStream, stream.Writable); + +/* + * @private function _write(info) + * Writes the info object to our transport instance. + */ +TransportStream.prototype._write = function (info, enc, callback) { + if (info.exception === true && !this.handleExceptions) { + return callback(null); + } + + // + // Remark: This has to be handled in the base transport now because we cannot + // conditionally write to our pipe targets as stream. + // + if (!this.level || this.levels[this.level] >= this.levels[info[LEVEL]]) { + if (this.format) { + return this.log( + this.format.transform(Object.assign({}, info), this.format.options), + callback + ); + } + + return this.log(info, callback); + } + + return callback(null); +}; + +/* + * @private function _writev(chunks, callback) + * Writes the batch of info objects (i.e. "object chunks") to our transport instance + * after performing any necessary filtering. + */ +TransportStream.prototype._writev = function (chunks, callback) { + const infos = chunks.filter(this._accept, this); + if (this.logv) { + return this.logv(infos, callback); + } + + for (var i = 0; i < infos.length; i++) { + this.log(infos[i].chunk, infos[i].callback); + } + + return callback(null); +}; + +/** + * Predicate function that returns true if the specfied `info` on the WriteReq, `write`, should + * be passed down into the derived TransportStream's I/O via `.log(info, callback)`. + * @param {WriteReq} write winston@3 Node.js WriteReq for the `info` object representing the log message. + * @returns {Boolean} Value indicating if the `write` should be accepted & logged. + */ +TransportStream.prototype._accept = function (write) { + const info = write.chunk; + + // + // Immediately check the average case: log level filtering. + // + if (info.exception === true || !this.level || this.levels[this.level] >= this.levels[info[LEVEL]]) { + // + // Ensure the info object is valid based on `{ exception }`: + // 1. { handleExceptions: true }: all `info` objects are valid + // 2. { exception: false }: accepted by all transports. + // + if (this.handleExceptions || info.exception !== true) { + return true; + } + } + + return false; +}; + +/** + * _nop is short for "No operation" + * @return {Boolean} Intentionally false. + */ +TransportStream.prototype._nop = function () { + return void undefined; +}; + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * A shareable symbol constant that can be used + * as a non-enumerable / semi-hidden level identifier + * to allow the readable level property to be mutable for + * operations like colorization + * + * @type {Symbol} + */ +Object.defineProperty(exports, 'LEVEL', { + value: Symbol.for('level') +}); + +/** + * A shareable symbol constant that can be used + * as a non-enumerable / semi-hidden message identifier + * to allow the final message property to not have + * side effects on another. + * + * @type {Symbol} + */ +Object.defineProperty(exports, 'MESSAGE', { + value: Symbol.for('message') +}); + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +/* + +The MIT License (MIT) + +Original Library + - Copyright (c) Marak Squires + +Additional functionality + - Copyright (c) Sindre Sorhus (sindresorhus.com) + +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. + +*/ + +var colors = {}; +module['exports'] = colors; + +colors.themes = {}; + +var ansiStyles = colors.styles = __webpack_require__(90); +var defineProps = Object.defineProperties; + +colors.supportsColor = __webpack_require__(91); + +if (typeof colors.enabled === "undefined") { + colors.enabled = colors.supportsColor; +} + +colors.stripColors = colors.strip = function(str){ + return ("" + str).replace(/\x1B\[\d+m/g, ''); +}; + + +var stylize = colors.stylize = function stylize (str, style) { + if (!colors.enabled) { + return str+''; + } + + return ansiStyles[style].open + str + ansiStyles[style].close; +} + +var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; +var escapeStringRegexp = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + return str.replace(matchOperatorsRe, '\\$&'); +} + +function build(_styles) { + var builder = function builder() { + return applyStyle.apply(builder, arguments); + }; + builder._styles = _styles; + // __proto__ is used because we must return a function, but there is + // no way to create a function with a different prototype. + builder.__proto__ = proto; + return builder; +} + +var styles = (function () { + var ret = {}; + ansiStyles.grey = ansiStyles.gray; + Object.keys(ansiStyles).forEach(function (key) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + ret[key] = { + get: function () { + return build(this._styles.concat(key)); + } + }; + }); + return ret; +})(); + +var proto = defineProps(function colors() {}, styles); + +function applyStyle() { + var args = arguments; + var argsLen = args.length; + var str = argsLen !== 0 && String(arguments[0]); + if (argsLen > 1) { + for (var a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } + + if (!colors.enabled || !str) { + return str; + } + + var nestedStyles = this._styles; + + var i = nestedStyles.length; + while (i--) { + var code = ansiStyles[nestedStyles[i]]; + str = code.open + str.replace(code.closeRe, code.open) + code.close; + } + + return str; +} + +function applyTheme (theme) { + for (var style in theme) { + (function(style){ + colors[style] = function(str){ + if (typeof theme[style] === 'object'){ + var out = str; + for (var i in theme[style]){ + out = colors[theme[style][i]](out); + } + return out; + } + return colors[theme[style]](str); + }; + })(style) + } +} + +colors.setTheme = function (theme) { + if (typeof theme === 'string') { + try { + colors.themes[theme] = !(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()); + applyTheme(colors.themes[theme]); + return colors.themes[theme]; + } catch (err) { + console.log(err); + return err; + } + } else { + applyTheme(theme); + } +}; + +function init() { + var ret = {}; + Object.keys(styles).forEach(function (name) { + ret[name] = { + get: function () { + return build([name]); + } + }; + }); + return ret; +} + +var sequencer = function sequencer (map, str) { + var exploded = str.split(""), i = 0; + exploded = exploded.map(map); + return exploded.join(""); +}; + +// custom formatter methods +colors.trap = __webpack_require__(93); +colors.zalgo = __webpack_require__(94); + +// maps +colors.maps = {}; +colors.maps.america = __webpack_require__(95); +colors.maps.zebra = __webpack_require__(96); +colors.maps.rainbow = __webpack_require__(97); +colors.maps.random = __webpack_require__(98) + +for (var map in colors.maps) { + (function(map){ + colors[map] = function (str) { + return sequencer(colors.maps[map], str); + } + })(map) +} + +defineProps(colors, init()); + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const colors = __webpack_require__(25); + +// +// Fix colors not appearing in non-tty environments +// +colors.enabled = true; + +/** + * @property {RegExp} hasSpace + * Simple regex to check for presence of spaces. + */ +const hasSpace = /\s+/; + +/* + * function colorize (info) + * Returns a new instance of the colorize Format that applies + * level colors to `info` objects. This was previously exposed + * as { colorize: true } to transports in `winston < 3.0.0`. + */ +module.exports = function createColorize(opts) { + return new Colorizer(opts); +}; + +// +// Attach the Colorizer for registration purposes +// +module.exports.Colorizer + = module.exports.Format + = Colorizer; + +/* + * function setupColors(info) + * Attaches a Colorizer instance to the format. + */ +function Colorizer(opts) { + opts = opts || {}; + if (opts.colors) { + this.addColors(opts.colors); + } + + this.options = opts; +} + +/* + * Adds the colors Object to the set of allColors + * known by the Colorizer + * + * @param {Object} colors Set of color mappings to add. + */ +Colorizer.addColors = function (colors) { + const nextColors = Object.keys(colors).reduce((acc, level) => { + acc[level] = hasSpace.test(colors[level]) + ? colors[level].split(hasSpace) + : colors[level]; + + return acc; + }, {}); + + Colorizer.allColors = Object.assign({}, Colorizer.allColors || {}, nextColors); + return Colorizer.allColors; +}; + +/* + * Adds the colors Object to the set of allColors + * known by the Colorizer + * + * @param {Object} colors Set of color mappings to add. + */ +Colorizer.prototype.addColors = function addColors(colors) { + return Colorizer.addColors(colors); +}; + +/* + * function colorize (level, message) + * Performs multi-step colorization using colors/safe + */ +Colorizer.prototype.colorize = function colorize(level, message) { + if (typeof message === 'undefined') { + message = level; + } + + // + // If the color for the level is just a string + // then attempt to colorize the message with it. + // + if (!Array.isArray(Colorizer.allColors[level])) { + return colors[Colorizer.allColors[level]](message); + } + + // + // If it is an Array then iterate over that Array, applying + // the colors function for each item. + // + for (var i = 0, len = Colorizer.allColors[level].length; i < len; i++) { + message = colors[Colorizer.allColors[level][i]](message); + } + + return message; +}; + +/* + * function transform (info, opts) + * Attempts to colorize the { level, message } of the given + * `logform` info object. + */ +Colorizer.prototype.transform = function transform(info, opts) { + var level = info.level; + + if (opts.level || opts.all || !opts.message) { + info.level = this.colorize(level); + } + + if (opts.all || opts.message) { + info.message = this.colorize(level, info.message); + } + + return info; +}; + + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +const assert = __webpack_require__(21) + +exports.ZERO_POINT = Object.freeze({row: 0, column: 0}) + +exports.compare = function (a, b) { + return primitiveCompare(a.row, a.column, b.row, b.column) +} + +function primitiveCompare (rowA, columnA, rowB, columnB) { + if (rowA === rowB) { + return columnA - columnB + } else { + return rowA - rowB + } +} + +exports.traverse = function (start, distance) { + if (distance.row === 0) + return {row: start.row, column: start.column + distance.column} + else { + return {row: start.row + distance.row, column: distance.column} + } +} + +exports.traversal = function (end, start) { + if (end.row === start.row) { + return {row: 0, column: end.column - start.column} + } else { + return {row: end.row - start.row, column: end.column} + } +} + +exports.extentForText = function (text) { + let row = 0 + let column = 0 + let index = 0 + while (index < text.length) { + const char = text[index] + if (char === '\n') { + column = 0 + row++ + } else { + column++ + } + index++ + } + + return {row, column} +} + +exports.characterIndexForPosition = function (text, target) { + // Previously we instantiated a point object here and mutated its fields, so + // that we could use the `compare` function we already export. However, this + // seems to trigger a weird optimization bug on v8 5.6.326.50 which causes + // this function to return unpredictable results, so we use primitive-valued + // variables instead. + let row = 0 + let column = 0 + let index = 0 + while (primitiveCompare(row, column, target.row, target.column) < 0 && index <= text.length) { + if (text[index] === '\n') { + row++ + column = 0 + } else { + column++ + } + + index++ + } + + assert(primitiveCompare(row, column, target.row, target.column) <= 0, 'Target position should not exceed the extent of the given text') + + return index +} + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +/* + * common.js: Internal helper and utility functions for winston + * + * (C) 2010 Charlie Robbins + * MIT LICENCE + * + */ + +const { format } = __webpack_require__(1); +const fs = __webpack_require__(23); +const StringDecoder = __webpack_require__(66).StringDecoder; +const Stream = __webpack_require__(3).Stream; + +/** + * @property {RegExp} formatRegExp + * Captures the number of format (i.e. %s strings) in a given string. + * Based on `util.format`, see Node.js source: + * https://github.com/nodejs/node/blob/b1c8f15c5f169e021f7c46eb7b219de95fe97603/lib/util.js#L201-L230 + */ +exports.formatRegExp = /%[sdjifoO%]/g; + +/** + * @property {RegExp} escapedPercent + * Captures the number of escaped % signs in a format string (i.e. %s strings). + */ +exports.escapedPercent = /%%/g; + +// +// ### function tailFile (options, iter) +// #### @options {Object} Options for tail. +// #### @iter {function} Iterator function to execute on every line. +// `tail -f` a file. Options must include file. +// +exports.tailFile = function (options, iter) { + const buffer = new Buffer(64 * 1024); + const decode = new StringDecoder('utf8'); + const stream = new Stream(); + let buff = ''; + let pos = 0; + let row = 0; + + if (options.start === -1) { + delete options.start; + } + + stream.readable = true; + stream.destroy = function () { + stream.destroyed = true; + stream.emit('end'); + stream.emit('close'); + }; + + fs.open(options.file, 'a+', '0644', function (err, fd) { + if (err) { + if (!iter) { + stream.emit('error', err); + } else { + iter(err); + } + stream.destroy(); + return; + } + + (function read() { + if (stream.destroyed) { + fs.close(fd); + return; + } + + return fs.read(fd, buffer, 0, buffer.length, pos, function (err, bytes) { + if (err) { + if (!iter) { + stream.emit('error', err); + } else { + iter(err); + } + stream.destroy(); + return; + } + + if (!bytes) { + if (buff) { + // eslint-disable-next-line eqeqeq + if (options.start == null || row > options.start) { + if (!iter) { + stream.emit('line', buff); + } else { + iter(null, buff); + } + } + row++; + buff = ''; + } + return setTimeout(read, 1000); + } + + var data = decode.write(buffer.slice(0, bytes)); + if (!iter) { + stream.emit('data', data); + } + + data = (buff + data).split(/\n+/); + + const l = data.length - 1; + let i = 0; + + for (; i < l; i++) { + // eslint-disable-next-line eqeqeq + if (options.start == null || row > options.start) { + if (!iter) { + stream.emit('line', data[i]); + } else { + iter(null, data[i]); + } + } + row++; + } + + buff = data[l]; + pos += bytes; + return read(); + }); + }()); + }); + + if (!iter) { + return stream; + } + + return stream.destroy; +}; + +/** + * @property {Object} warn + * Set of simple deprecation notices and a way + * to expose them for a set of properties. + * + * @api private + */ +exports.warn = { + deprecated: function warnDeprecated(prop) { + return function () { + throw new Error(format('{ %s } was removed in winston@3.0.0.', prop)); + }; + }, + useFormat: function warnFormat(prop) { + return function () { + throw new Error([ + format('{ %s } was removed in winston@3.0.0.', prop), + 'Use a custom winston.format = winston.format(function) instead.' + ].join('\n')); + }; + }, + forFunctions: function (obj, type, props) { + props.forEach(function (prop) { + obj[prop] = exports.warn[type](prop); + }); + }, + moved: function (obj, movedTo, prop) { + function movedNotice() { + return function () { + throw new Error([ + format('winston.%s was moved in winston@3.0.0.', prop), + format('Use a winston.%s instead.', movedTo) + ].join('\n')); + }; + } + + Object.defineProperty(obj, prop, { + get: movedNotice, + set: movedNotice + }); + }, + forProperties: function (obj, type, props) { + props.forEach(function (prop) { + var notice = exports.warn[type](prop); + Object.defineProperty(obj, prop, { + get: notice, + set: notice + }); + }); + } +}; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + +module.exports = require("os"); + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! + * async + * https://github.com/caolan/async + * + * Copyright 2010-2014 Caolan McMahon + * Released under the MIT license + */ +(function () { + + var async = {}; + function noop() {} + function identity(v) { + return v; + } + function toBool(v) { + return !!v; + } + function notId(v) { + return !v; + } + + // global on the server, window in the browser + var previous_async; + + // Establish the root object, `window` (`self`) in the browser, `global` + // on the server, or `this` in some virtual machines. We use `self` + // instead of `window` for `WebWorker` support. + var root = typeof self === 'object' && self.self === self && self || + typeof global === 'object' && global.global === global && global || + this; + + if (root != null) { + previous_async = root.async; + } + + async.noConflict = function () { + root.async = previous_async; + return async; + }; + + function only_once(fn) { + return function() { + if (fn === null) throw new Error("Callback was already called."); + fn.apply(this, arguments); + fn = null; + }; + } + + function _once(fn) { + return function() { + if (fn === null) return; + fn.apply(this, arguments); + fn = null; + }; + } + + //// cross-browser compatiblity functions //// + + var _toString = Object.prototype.toString; + + var _isArray = Array.isArray || function (obj) { + return _toString.call(obj) === '[object Array]'; + }; + + // Ported from underscore.js isObject + var _isObject = function(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + }; + + function _isArrayLike(arr) { + return _isArray(arr) || ( + // has a positive integer length property + typeof arr.length === "number" && + arr.length >= 0 && + arr.length % 1 === 0 + ); + } + + function _arrayEach(arr, iterator) { + var index = -1, + length = arr.length; + + while (++index < length) { + iterator(arr[index], index, arr); + } + } + + function _map(arr, iterator) { + var index = -1, + length = arr.length, + result = Array(length); + + while (++index < length) { + result[index] = iterator(arr[index], index, arr); + } + return result; + } + + function _range(count) { + return _map(Array(count), function (v, i) { return i; }); + } + + function _reduce(arr, iterator, memo) { + _arrayEach(arr, function (x, i, a) { + memo = iterator(memo, x, i, a); + }); + return memo; + } + + function _forEachOf(object, iterator) { + _arrayEach(_keys(object), function (key) { + iterator(object[key], key); + }); + } + + function _indexOf(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] === item) return i; + } + return -1; + } + + var _keys = Object.keys || function (obj) { + var keys = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + keys.push(k); + } + } + return keys; + }; + + function _keyIterator(coll) { + var i = -1; + var len; + var keys; + if (_isArrayLike(coll)) { + len = coll.length; + return function next() { + i++; + return i < len ? i : null; + }; + } else { + keys = _keys(coll); + len = keys.length; + return function next() { + i++; + return i < len ? keys[i] : null; + }; + } + } + + // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html) + // This accumulates the arguments passed into an array, after a given index. + // From underscore.js (https://github.com/jashkenas/underscore/pull/2140). + function _restParam(func, startIndex) { + startIndex = startIndex == null ? func.length - 1 : +startIndex; + return function() { + var length = Math.max(arguments.length - startIndex, 0); + var rest = Array(length); + for (var index = 0; index < length; index++) { + rest[index] = arguments[index + startIndex]; + } + switch (startIndex) { + case 0: return func.call(this, rest); + case 1: return func.call(this, arguments[0], rest); + } + // Currently unused but handle cases outside of the switch statement: + // var args = Array(startIndex + 1); + // for (index = 0; index < startIndex; index++) { + // args[index] = arguments[index]; + // } + // args[startIndex] = rest; + // return func.apply(this, args); + }; + } + + function _withoutIndex(iterator) { + return function (value, index, callback) { + return iterator(value, callback); + }; + } + + //// exported async module functions //// + + //// nextTick implementation with browser-compatible fallback //// + + // capture the global reference to guard against fakeTimer mocks + var _setImmediate = typeof setImmediate === 'function' && setImmediate; + + var _delay = _setImmediate ? function(fn) { + // not a direct alias for IE10 compatibility + _setImmediate(fn); + } : function(fn) { + setTimeout(fn, 0); + }; + + if (typeof process === 'object' && typeof process.nextTick === 'function') { + async.nextTick = process.nextTick; + } else { + async.nextTick = _delay; + } + async.setImmediate = _setImmediate ? _delay : async.nextTick; + + + async.forEach = + async.each = function (arr, iterator, callback) { + return async.eachOf(arr, _withoutIndex(iterator), callback); + }; + + async.forEachSeries = + async.eachSeries = function (arr, iterator, callback) { + return async.eachOfSeries(arr, _withoutIndex(iterator), callback); + }; + + + async.forEachLimit = + async.eachLimit = function (arr, limit, iterator, callback) { + return _eachOfLimit(limit)(arr, _withoutIndex(iterator), callback); + }; + + async.forEachOf = + async.eachOf = function (object, iterator, callback) { + callback = _once(callback || noop); + object = object || []; + + var iter = _keyIterator(object); + var key, completed = 0; + + while ((key = iter()) != null) { + completed += 1; + iterator(object[key], key, only_once(done)); + } + + if (completed === 0) callback(null); + + function done(err) { + completed--; + if (err) { + callback(err); + } + // Check key is null in case iterator isn't exhausted + // and done resolved synchronously. + else if (key === null && completed <= 0) { + callback(null); + } + } + }; + + async.forEachOfSeries = + async.eachOfSeries = function (obj, iterator, callback) { + callback = _once(callback || noop); + obj = obj || []; + var nextKey = _keyIterator(obj); + var key = nextKey(); + function iterate() { + var sync = true; + if (key === null) { + return callback(null); + } + iterator(obj[key], key, only_once(function (err) { + if (err) { + callback(err); + } + else { + key = nextKey(); + if (key === null) { + return callback(null); + } else { + if (sync) { + async.setImmediate(iterate); + } else { + iterate(); + } + } + } + })); + sync = false; + } + iterate(); + }; + + + + async.forEachOfLimit = + async.eachOfLimit = function (obj, limit, iterator, callback) { + _eachOfLimit(limit)(obj, iterator, callback); + }; + + function _eachOfLimit(limit) { + + return function (obj, iterator, callback) { + callback = _once(callback || noop); + obj = obj || []; + var nextKey = _keyIterator(obj); + if (limit <= 0) { + return callback(null); + } + var done = false; + var running = 0; + var errored = false; + + (function replenish () { + if (done && running <= 0) { + return callback(null); + } + + while (running < limit && !errored) { + var key = nextKey(); + if (key === null) { + done = true; + if (running <= 0) { + callback(null); + } + return; + } + running += 1; + iterator(obj[key], key, only_once(function (err) { + running -= 1; + if (err) { + callback(err); + errored = true; + } + else { + replenish(); + } + })); + } + })(); + }; + } + + + function doParallel(fn) { + return function (obj, iterator, callback) { + return fn(async.eachOf, obj, iterator, callback); + }; + } + function doParallelLimit(fn) { + return function (obj, limit, iterator, callback) { + return fn(_eachOfLimit(limit), obj, iterator, callback); + }; + } + function doSeries(fn) { + return function (obj, iterator, callback) { + return fn(async.eachOfSeries, obj, iterator, callback); + }; + } + + function _asyncMap(eachfn, arr, iterator, callback) { + callback = _once(callback || noop); + arr = arr || []; + var results = _isArrayLike(arr) ? [] : {}; + eachfn(arr, function (value, index, callback) { + iterator(value, function (err, v) { + results[index] = v; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + + async.map = doParallel(_asyncMap); + async.mapSeries = doSeries(_asyncMap); + async.mapLimit = doParallelLimit(_asyncMap); + + // reduce only has a series version, as doing reduce in parallel won't + // work in many situations. + async.inject = + async.foldl = + async.reduce = function (arr, memo, iterator, callback) { + async.eachOfSeries(arr, function (x, i, callback) { + iterator(memo, x, function (err, v) { + memo = v; + callback(err); + }); + }, function (err) { + callback(err, memo); + }); + }; + + async.foldr = + async.reduceRight = function (arr, memo, iterator, callback) { + var reversed = _map(arr, identity).reverse(); + async.reduce(reversed, memo, iterator, callback); + }; + + async.transform = function (arr, memo, iterator, callback) { + if (arguments.length === 3) { + callback = iterator; + iterator = memo; + memo = _isArray(arr) ? [] : {}; + } + + async.eachOf(arr, function(v, k, cb) { + iterator(memo, v, k, cb); + }, function(err) { + callback(err, memo); + }); + }; + + function _filter(eachfn, arr, iterator, callback) { + var results = []; + eachfn(arr, function (x, index, callback) { + iterator(x, function (v) { + if (v) { + results.push({index: index, value: x}); + } + callback(); + }); + }, function () { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + } + + async.select = + async.filter = doParallel(_filter); + + async.selectLimit = + async.filterLimit = doParallelLimit(_filter); + + async.selectSeries = + async.filterSeries = doSeries(_filter); + + function _reject(eachfn, arr, iterator, callback) { + _filter(eachfn, arr, function(value, cb) { + iterator(value, function(v) { + cb(!v); + }); + }, callback); + } + async.reject = doParallel(_reject); + async.rejectLimit = doParallelLimit(_reject); + async.rejectSeries = doSeries(_reject); + + function _createTester(eachfn, check, getResult) { + return function(arr, limit, iterator, cb) { + function done() { + if (cb) cb(getResult(false, void 0)); + } + function iteratee(x, _, callback) { + if (!cb) return callback(); + iterator(x, function (v) { + if (cb && check(v)) { + cb(getResult(true, x)); + cb = iterator = false; + } + callback(); + }); + } + if (arguments.length > 3) { + eachfn(arr, limit, iteratee, done); + } else { + cb = iterator; + iterator = limit; + eachfn(arr, iteratee, done); + } + }; + } + + async.any = + async.some = _createTester(async.eachOf, toBool, identity); + + async.someLimit = _createTester(async.eachOfLimit, toBool, identity); + + async.all = + async.every = _createTester(async.eachOf, notId, notId); + + async.everyLimit = _createTester(async.eachOfLimit, notId, notId); + + function _findGetResult(v, x) { + return x; + } + async.detect = _createTester(async.eachOf, identity, _findGetResult); + async.detectSeries = _createTester(async.eachOfSeries, identity, _findGetResult); + async.detectLimit = _createTester(async.eachOfLimit, identity, _findGetResult); + + async.sortBy = function (arr, iterator, callback) { + async.map(arr, function (x, callback) { + iterator(x, function (err, criteria) { + if (err) { + callback(err); + } + else { + callback(null, {value: x, criteria: criteria}); + } + }); + }, function (err, results) { + if (err) { + return callback(err); + } + else { + callback(null, _map(results.sort(comparator), function (x) { + return x.value; + })); + } + + }); + + function comparator(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + } + }; + + async.auto = function (tasks, concurrency, callback) { + if (typeof arguments[1] === 'function') { + // concurrency is optional, shift the args. + callback = concurrency; + concurrency = null; + } + callback = _once(callback || noop); + var keys = _keys(tasks); + var remainingTasks = keys.length; + if (!remainingTasks) { + return callback(null); + } + if (!concurrency) { + concurrency = remainingTasks; + } + + var results = {}; + var runningTasks = 0; + + var hasError = false; + + var listeners = []; + function addListener(fn) { + listeners.unshift(fn); + } + function removeListener(fn) { + var idx = _indexOf(listeners, fn); + if (idx >= 0) listeners.splice(idx, 1); + } + function taskComplete() { + remainingTasks--; + _arrayEach(listeners.slice(0), function (fn) { + fn(); + }); + } + + addListener(function () { + if (!remainingTasks) { + callback(null, results); + } + }); + + _arrayEach(keys, function (k) { + if (hasError) return; + var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]]; + var taskCallback = _restParam(function(err, args) { + runningTasks--; + if (args.length <= 1) { + args = args[0]; + } + if (err) { + var safeResults = {}; + _forEachOf(results, function(val, rkey) { + safeResults[rkey] = val; + }); + safeResults[k] = args; + hasError = true; + + callback(err, safeResults); + } + else { + results[k] = args; + async.setImmediate(taskComplete); + } + }); + var requires = task.slice(0, task.length - 1); + // prevent dead-locks + var len = requires.length; + var dep; + while (len--) { + if (!(dep = tasks[requires[len]])) { + throw new Error('Has nonexistent dependency in ' + requires.join(', ')); + } + if (_isArray(dep) && _indexOf(dep, k) >= 0) { + throw new Error('Has cyclic dependencies'); + } + } + function ready() { + return runningTasks < concurrency && _reduce(requires, function (a, x) { + return (a && results.hasOwnProperty(x)); + }, true) && !results.hasOwnProperty(k); + } + if (ready()) { + runningTasks++; + task[task.length - 1](taskCallback, results); + } + else { + addListener(listener); + } + function listener() { + if (ready()) { + runningTasks++; + removeListener(listener); + task[task.length - 1](taskCallback, results); + } + } + }); + }; + + + + async.retry = function(times, task, callback) { + var DEFAULT_TIMES = 5; + var DEFAULT_INTERVAL = 0; + + var attempts = []; + + var opts = { + times: DEFAULT_TIMES, + interval: DEFAULT_INTERVAL + }; + + function parseTimes(acc, t){ + if(typeof t === 'number'){ + acc.times = parseInt(t, 10) || DEFAULT_TIMES; + } else if(typeof t === 'object'){ + acc.times = parseInt(t.times, 10) || DEFAULT_TIMES; + acc.interval = parseInt(t.interval, 10) || DEFAULT_INTERVAL; + } else { + throw new Error('Unsupported argument type for \'times\': ' + typeof t); + } + } + + var length = arguments.length; + if (length < 1 || length > 3) { + throw new Error('Invalid arguments - must be either (task), (task, callback), (times, task) or (times, task, callback)'); + } else if (length <= 2 && typeof times === 'function') { + callback = task; + task = times; + } + if (typeof times !== 'function') { + parseTimes(opts, times); + } + opts.callback = callback; + opts.task = task; + + function wrappedTask(wrappedCallback, wrappedResults) { + function retryAttempt(task, finalAttempt) { + return function(seriesCallback) { + task(function(err, result){ + seriesCallback(!err || finalAttempt, {err: err, result: result}); + }, wrappedResults); + }; + } + + function retryInterval(interval){ + return function(seriesCallback){ + setTimeout(function(){ + seriesCallback(null); + }, interval); + }; + } + + while (opts.times) { + + var finalAttempt = !(opts.times-=1); + attempts.push(retryAttempt(opts.task, finalAttempt)); + if(!finalAttempt && opts.interval > 0){ + attempts.push(retryInterval(opts.interval)); + } + } + + async.series(attempts, function(done, data){ + data = data[data.length - 1]; + (wrappedCallback || opts.callback)(data.err, data.result); + }); + } + + // If a callback is passed, run this as a controll flow + return opts.callback ? wrappedTask() : wrappedTask; + }; + + async.waterfall = function (tasks, callback) { + callback = _once(callback || noop); + if (!_isArray(tasks)) { + var err = new Error('First argument to waterfall must be an array of functions'); + return callback(err); + } + if (!tasks.length) { + return callback(); + } + function wrapIterator(iterator) { + return _restParam(function (err, args) { + if (err) { + callback.apply(null, [err].concat(args)); + } + else { + var next = iterator.next(); + if (next) { + args.push(wrapIterator(next)); + } + else { + args.push(callback); + } + ensureAsync(iterator).apply(null, args); + } + }); + } + wrapIterator(async.iterator(tasks))(); + }; + + function _parallel(eachfn, tasks, callback) { + callback = callback || noop; + var results = _isArrayLike(tasks) ? [] : {}; + + eachfn(tasks, function (task, key, callback) { + task(_restParam(function (err, args) { + if (args.length <= 1) { + args = args[0]; + } + results[key] = args; + callback(err); + })); + }, function (err) { + callback(err, results); + }); + } + + async.parallel = function (tasks, callback) { + _parallel(async.eachOf, tasks, callback); + }; + + async.parallelLimit = function(tasks, limit, callback) { + _parallel(_eachOfLimit(limit), tasks, callback); + }; + + async.series = function(tasks, callback) { + _parallel(async.eachOfSeries, tasks, callback); + }; + + async.iterator = function (tasks) { + function makeCallback(index) { + function fn() { + if (tasks.length) { + tasks[index].apply(null, arguments); + } + return fn.next(); + } + fn.next = function () { + return (index < tasks.length - 1) ? makeCallback(index + 1): null; + }; + return fn; + } + return makeCallback(0); + }; + + async.apply = _restParam(function (fn, args) { + return _restParam(function (callArgs) { + return fn.apply( + null, args.concat(callArgs) + ); + }); + }); + + function _concat(eachfn, arr, fn, callback) { + var result = []; + eachfn(arr, function (x, index, cb) { + fn(x, function (err, y) { + result = result.concat(y || []); + cb(err); + }); + }, function (err) { + callback(err, result); + }); + } + async.concat = doParallel(_concat); + async.concatSeries = doSeries(_concat); + + async.whilst = function (test, iterator, callback) { + callback = callback || noop; + if (test()) { + var next = _restParam(function(err, args) { + if (err) { + callback(err); + } else if (test.apply(this, args)) { + iterator(next); + } else { + callback.apply(null, [null].concat(args)); + } + }); + iterator(next); + } else { + callback(null); + } + }; + + async.doWhilst = function (iterator, test, callback) { + var calls = 0; + return async.whilst(function() { + return ++calls <= 1 || test.apply(this, arguments); + }, iterator, callback); + }; + + async.until = function (test, iterator, callback) { + return async.whilst(function() { + return !test.apply(this, arguments); + }, iterator, callback); + }; + + async.doUntil = function (iterator, test, callback) { + return async.doWhilst(iterator, function() { + return !test.apply(this, arguments); + }, callback); + }; + + async.during = function (test, iterator, callback) { + callback = callback || noop; + + var next = _restParam(function(err, args) { + if (err) { + callback(err); + } else { + args.push(check); + test.apply(this, args); + } + }); + + var check = function(err, truth) { + if (err) { + callback(err); + } else if (truth) { + iterator(next); + } else { + callback(null); + } + }; + + test(check); + }; + + async.doDuring = function (iterator, test, callback) { + var calls = 0; + async.during(function(next) { + if (calls++ < 1) { + next(null, true); + } else { + test.apply(this, arguments); + } + }, iterator, callback); + }; + + function _queue(worker, concurrency, payload) { + if (concurrency == null) { + concurrency = 1; + } + else if(concurrency === 0) { + throw new Error('Concurrency must not be zero'); + } + function _insert(q, data, pos, callback) { + if (callback != null && typeof callback !== "function") { + throw new Error("task callback must be a function"); + } + q.started = true; + if (!_isArray(data)) { + data = [data]; + } + if(data.length === 0 && q.idle()) { + // call drain immediately if there are no tasks + return async.setImmediate(function() { + q.drain(); + }); + } + _arrayEach(data, function(task) { + var item = { + data: task, + callback: callback || noop + }; + + if (pos) { + q.tasks.unshift(item); + } else { + q.tasks.push(item); + } + + if (q.tasks.length === q.concurrency) { + q.saturated(); + } + }); + async.setImmediate(q.process); + } + function _next(q, tasks) { + return function(){ + workers -= 1; + + var removed = false; + var args = arguments; + _arrayEach(tasks, function (task) { + _arrayEach(workersList, function (worker, index) { + if (worker === task && !removed) { + workersList.splice(index, 1); + removed = true; + } + }); + + task.callback.apply(task, args); + }); + if (q.tasks.length + workers === 0) { + q.drain(); + } + q.process(); + }; + } + + var workers = 0; + var workersList = []; + var q = { + tasks: [], + concurrency: concurrency, + payload: payload, + saturated: noop, + empty: noop, + drain: noop, + started: false, + paused: false, + push: function (data, callback) { + _insert(q, data, false, callback); + }, + kill: function () { + q.drain = noop; + q.tasks = []; + }, + unshift: function (data, callback) { + _insert(q, data, true, callback); + }, + process: function () { + while(!q.paused && workers < q.concurrency && q.tasks.length){ + + var tasks = q.payload ? + q.tasks.splice(0, q.payload) : + q.tasks.splice(0, q.tasks.length); + + var data = _map(tasks, function (task) { + return task.data; + }); + + if (q.tasks.length === 0) { + q.empty(); + } + workers += 1; + workersList.push(tasks[0]); + var cb = only_once(_next(q, tasks)); + worker(data, cb); + } + }, + length: function () { + return q.tasks.length; + }, + running: function () { + return workers; + }, + workersList: function () { + return workersList; + }, + idle: function() { + return q.tasks.length + workers === 0; + }, + pause: function () { + q.paused = true; + }, + resume: function () { + if (q.paused === false) { return; } + q.paused = false; + var resumeCount = Math.min(q.concurrency, q.tasks.length); + // Need to call q.process once per concurrent + // worker to preserve full concurrency after pause + for (var w = 1; w <= resumeCount; w++) { + async.setImmediate(q.process); + } + } + }; + return q; + } + + async.queue = function (worker, concurrency) { + var q = _queue(function (items, cb) { + worker(items[0], cb); + }, concurrency, 1); + + return q; + }; + + async.priorityQueue = function (worker, concurrency) { + + function _compareTasks(a, b){ + return a.priority - b.priority; + } + + function _binarySearch(sequence, item, compare) { + var beg = -1, + end = sequence.length - 1; + while (beg < end) { + var mid = beg + ((end - beg + 1) >>> 1); + if (compare(item, sequence[mid]) >= 0) { + beg = mid; + } else { + end = mid - 1; + } + } + return beg; + } + + function _insert(q, data, priority, callback) { + if (callback != null && typeof callback !== "function") { + throw new Error("task callback must be a function"); + } + q.started = true; + if (!_isArray(data)) { + data = [data]; + } + if(data.length === 0) { + // call drain immediately if there are no tasks + return async.setImmediate(function() { + q.drain(); + }); + } + _arrayEach(data, function(task) { + var item = { + data: task, + priority: priority, + callback: typeof callback === 'function' ? callback : noop + }; + + q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item); + + if (q.tasks.length === q.concurrency) { + q.saturated(); + } + async.setImmediate(q.process); + }); + } + + // Start with a normal queue + var q = async.queue(worker, concurrency); + + // Override push to accept second parameter representing priority + q.push = function (data, priority, callback) { + _insert(q, data, priority, callback); + }; + + // Remove unshift function + delete q.unshift; + + return q; + }; + + async.cargo = function (worker, payload) { + return _queue(worker, 1, payload); + }; + + function _console_fn(name) { + return _restParam(function (fn, args) { + fn.apply(null, args.concat([_restParam(function (err, args) { + if (typeof console === 'object') { + if (err) { + if (console.error) { + console.error(err); + } + } + else if (console[name]) { + _arrayEach(args, function (x) { + console[name](x); + }); + } + } + })])); + }); + } + async.log = _console_fn('log'); + async.dir = _console_fn('dir'); + /*async.info = _console_fn('info'); + async.warn = _console_fn('warn'); + async.error = _console_fn('error');*/ + + async.memoize = function (fn, hasher) { + var memo = {}; + var queues = {}; + var has = Object.prototype.hasOwnProperty; + hasher = hasher || identity; + var memoized = _restParam(function memoized(args) { + var callback = args.pop(); + var key = hasher.apply(null, args); + if (has.call(memo, key)) { + async.setImmediate(function () { + callback.apply(null, memo[key]); + }); + } + else if (has.call(queues, key)) { + queues[key].push(callback); + } + else { + queues[key] = [callback]; + fn.apply(null, args.concat([_restParam(function (args) { + memo[key] = args; + var q = queues[key]; + delete queues[key]; + for (var i = 0, l = q.length; i < l; i++) { + q[i].apply(null, args); + } + })])); + } + }); + memoized.memo = memo; + memoized.unmemoized = fn; + return memoized; + }; + + async.unmemoize = function (fn) { + return function () { + return (fn.unmemoized || fn).apply(null, arguments); + }; + }; + + function _times(mapper) { + return function (count, iterator, callback) { + mapper(_range(count), iterator, callback); + }; + } + + async.times = _times(async.map); + async.timesSeries = _times(async.mapSeries); + async.timesLimit = function (count, limit, iterator, callback) { + return async.mapLimit(_range(count), limit, iterator, callback); + }; + + async.seq = function (/* functions... */) { + var fns = arguments; + return _restParam(function (args) { + var that = this; + + var callback = args[args.length - 1]; + if (typeof callback == 'function') { + args.pop(); + } else { + callback = noop; + } + + async.reduce(fns, args, function (newargs, fn, cb) { + fn.apply(that, newargs.concat([_restParam(function (err, nextargs) { + cb(err, nextargs); + })])); + }, + function (err, results) { + callback.apply(that, [err].concat(results)); + }); + }); + }; + + async.compose = function (/* functions... */) { + return async.seq.apply(null, Array.prototype.reverse.call(arguments)); + }; + + + function _applyEach(eachfn) { + return _restParam(function(fns, args) { + var go = _restParam(function(args) { + var that = this; + var callback = args.pop(); + return eachfn(fns, function (fn, _, cb) { + fn.apply(that, args.concat([cb])); + }, + callback); + }); + if (args.length) { + return go.apply(this, args); + } + else { + return go; + } + }); + } + + async.applyEach = _applyEach(async.eachOf); + async.applyEachSeries = _applyEach(async.eachOfSeries); + + + async.forever = function (fn, callback) { + var done = only_once(callback || noop); + var task = ensureAsync(fn); + function next(err) { + if (err) { + return done(err); + } + task(next); + } + next(); + }; + + function ensureAsync(fn) { + return _restParam(function (args) { + var callback = args.pop(); + args.push(function () { + var innerArgs = arguments; + if (sync) { + async.setImmediate(function () { + callback.apply(null, innerArgs); + }); + } else { + callback.apply(null, innerArgs); + } + }); + var sync = true; + fn.apply(this, args); + sync = false; + }); + } + + async.ensureAsync = ensureAsync; + + async.constant = _restParam(function(values) { + var args = [null].concat(values); + return function (callback) { + return callback.apply(this, args); + }; + }); + + async.wrapSync = + async.asyncify = function asyncify(func) { + return _restParam(function (args) { + var callback = args.pop(); + var result; + try { + result = func.apply(this, args); + } catch (e) { + return callback(e); + } + // if result is Promise object + if (_isObject(result) && typeof result.then === "function") { + result.then(function(value) { + callback(null, value); + })["catch"](function(err) { + callback(err.message ? err : new Error(err)); + }); + } else { + callback(null, result); + } + }); + }; + + // Node.js + if (typeof module === 'object' && module.exports) { + module.exports = async; + } + // AMD / RequireJS + else if (true) { + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = (function () { + return async; + }).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + } + // included directly via