diff --git a/poetry.lock b/poetry.lock index e19f7eb98f3ac1db6df475ce1daaf9fdcde89278..620d92b860d166f816d2ac5fd76043a88268ea95 100644 --- a/poetry.lock +++ b/poetry.lock @@ -65,7 +65,7 @@ IDNA = ["idna (>=2.1)"] [[package]] name = "eventlet" -version = "0.30.1" +version = "0.30.2" description = "Highly concurrent networking library" category = "main" optional = false @@ -145,7 +145,7 @@ tornado = ["tornado (>=0.2)"] [[package]] name = "humanize" -version = "3.2.0" +version = "3.3.0" description = "Python humanize utilities" category = "main" optional = false @@ -156,7 +156,7 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "importlib-metadata" -version = "3.7.0" +version = "3.7.3" description = "Read metadata from Python packages" category = "main" optional = false @@ -288,7 +288,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "python-engineio" -version = "4.0.0" +version = "4.0.1" description = "Engine.IO server" category = "main" optional = false @@ -300,7 +300,7 @@ client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] [[package]] name = "python-socketio" -version = "5.0.4" +version = "5.1.0" description = "Socket.IO server" category = "main" optional = false @@ -360,20 +360,20 @@ watchdog = ["watchdog"] [[package]] name = "zipp" -version = "3.4.0" +version = "3.4.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "c85d7f118a0506a12f1e24414d14f8b35d38b37188f4267153d31c66914c8fbe" +content-hash = "2c083a90dcfc1c3855b4b8fe82631620fa1ae1cfbb2707955f144d205715193e" [metadata.files] atomicwrites = [ @@ -401,8 +401,8 @@ dnspython = [ {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, ] eventlet = [ - {file = "eventlet-0.30.1-py2.py3-none-any.whl", hash = "sha256:a58caa60ea936c967d16983f3fe7831583b1dc57ed58778cb9b329d1484b918e"}, - {file = "eventlet-0.30.1.tar.gz", hash = "sha256:d00649a7e17de0bcddff1a96311ed3baf1b295b3223d4b71aceafe7b45e6d6f8"}, + {file = "eventlet-0.30.2-py2.py3-none-any.whl", hash = "sha256:89cc6dbfef47c4629cefead5fde21c5f2b33464d57f7df5fc5148f8b4de3fbb5"}, + {file = "eventlet-0.30.2.tar.gz", hash = "sha256:1811b122d9a45eb5bafba092d36911bca825f835cb648a862bbf984030acff9d"}, ] flask = [ {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, @@ -465,12 +465,12 @@ gunicorn = [ {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"}, ] humanize = [ - {file = "humanize-3.2.0-py3-none-any.whl", hash = "sha256:d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22"}, - {file = "humanize-3.2.0.tar.gz", hash = "sha256:ab69004895689951b79f2ae4fdd6b8127ff0c180aff107856d5d98119a33f026"}, + {file = "humanize-3.3.0-py3-none-any.whl", hash = "sha256:0ebeb71e0b8f5d1cbb2f8b19cc0f5f6e6abfcdb8e3d152424b20effbab68ace5"}, + {file = "humanize-3.3.0.tar.gz", hash = "sha256:8bf7abd672b867f38b8b04593829b85b9b6199a61ef6586bf3629cc06458ff35"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.7.0-py3-none-any.whl", hash = "sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614"}, - {file = "importlib_metadata-3.7.0.tar.gz", hash = "sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa"}, + {file = "importlib_metadata-3.7.3-py3-none-any.whl", hash = "sha256:b74159469b464a99cb8cc3e21973e4d96e05d3024d337313fedb618a6e86e6f4"}, + {file = "importlib_metadata-3.7.3.tar.gz", hash = "sha256:742add720a20d0467df2f444ae41704000f50e1234f46174b51f9c6031a1bd71"}, ] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, @@ -503,39 +503,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ @@ -563,12 +544,12 @@ pytest = [ {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] python-engineio = [ - {file = "python-engineio-4.0.0.tar.gz", hash = "sha256:9f34afa4170f5ba6e3d9ff158752ccf8fbb2145f16554b2f0fc84646675be99a"}, - {file = "python_engineio-4.0.0-py2.py3-none-any.whl", hash = "sha256:33f7a214be5db35c867e97027bfe63676cb003d82aa17a607612b25ba5d84e5b"}, + {file = "python-engineio-4.0.1.tar.gz", hash = "sha256:bb575c1a3512b4b5d4706f3071d5cc36e592459e99a47d9a4b7faabeba941377"}, + {file = "python_engineio-4.0.1-py2.py3-none-any.whl", hash = "sha256:759ec99d91b3d36b29596124c77ba7c063c7ab97685d318fb23a166d9a4b9187"}, ] python-socketio = [ - {file = "python-socketio-5.0.4.tar.gz", hash = "sha256:f53fd0d5bd9f75a70492062f4ae6195ab5d34d67a29024d740f25e468392893e"}, - {file = "python_socketio-5.0.4-py2.py3-none-any.whl", hash = "sha256:870f8b00a63ef7c9a1f85fd70028624867bf246115e82625f28ef79def8847bb"}, + {file = "python-socketio-5.1.0.tar.gz", hash = "sha256:338cc29abb6f3ca14c88f1f8d05ed27c690df4648f62062b299f92625bbf7093"}, + {file = "python_socketio-5.1.0-py2.py3-none-any.whl", hash = "sha256:8a7ed43bfdbbb266eb8a661a0c9648dc94bcd9689566ae3ee08bf98eca8987af"}, ] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, @@ -592,6 +573,6 @@ werkzeug = [ {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, ] diff --git a/pyproject.toml b/pyproject.toml index b8b69db6ca29d16f4ecef0b263315f7568e64e19..dfa73bf7ba9983edb5cee279212ad79adf3a5fe4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ toml = "^0.10.2" Flask-Markdown = "^0.3" gunicorn = "^20.0.4" humanize = "^3.2.0" -Flask-SocketIO = "4.3.2" +Flask-SocketIO = "5.0.1" eventlet = "^0.30.1" [tool.poetry.dev-dependencies] diff --git a/requirements.txt b/requirements.txt index 24cf7991dd4b698efc33e298ae8b79167f8ee9a5..d97cb6a95b36f5623992e182257cdc2af03bd5e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,9 @@ click==7.1.2; python_version >= "2.7" and python_full_version < "3.0.0" or pytho dnspython==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" \ --hash=sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d \ --hash=sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01 -eventlet==0.30.1 \ - --hash=sha256:a58caa60ea936c967d16983f3fe7831583b1dc57ed58778cb9b329d1484b918e \ - --hash=sha256:d00649a7e17de0bcddff1a96311ed3baf1b295b3223d4b71aceafe7b45e6d6f8 +eventlet==0.30.2 \ + --hash=sha256:89cc6dbfef47c4629cefead5fde21c5f2b33464d57f7df5fc5148f8b4de3fbb5 \ + --hash=sha256:1811b122d9a45eb5bafba092d36911bca825f835cb648a862bbf984030acff9d flask-markdown==0.3 \ --hash=sha256:d8c3f02545b395ad525b86760ad721a2c50efa3b8e602a8d486a1f47f68c6250 flask-socketio==5.0.1 \ @@ -65,12 +65,12 @@ greenlet==1.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or py gunicorn==20.0.4; python_version >= "3.4" \ --hash=sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \ --hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626 -humanize==3.2.0; python_version >= "3.6" \ - --hash=sha256:d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22 \ - --hash=sha256:ab69004895689951b79f2ae4fdd6b8127ff0c180aff107856d5d98119a33f026 -importlib-metadata==3.7.0; python_version < "3.8" and python_version >= "3.6" \ - --hash=sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614 \ - --hash=sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa +humanize==3.3.0; python_version >= "3.6" \ + --hash=sha256:0ebeb71e0b8f5d1cbb2f8b19cc0f5f6e6abfcdb8e3d152424b20effbab68ace5 \ + --hash=sha256:8bf7abd672b867f38b8b04593829b85b9b6199a61ef6586bf3629cc06458ff35 +importlib-metadata==3.7.3; python_version < "3.8" and python_version >= "3.6" \ + --hash=sha256:b74159469b464a99cb8cc3e21973e4d96e05d3024d337313fedb618a6e86e6f4 \ + --hash=sha256:742add720a20d0467df2f444ae41704000f50e1234f46174b51f9c6031a1bd71 itsdangerous==1.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \ --hash=sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749 \ --hash=sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19 @@ -99,46 +99,27 @@ markupsafe==1.1.1; python_version >= "2.7" and python_full_version < "3.0.0" or --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ - --hash=sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5 \ --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ - --hash=sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f \ - --hash=sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0 \ - --hash=sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7 \ --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ - --hash=sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193 \ --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ - --hash=sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1 \ - --hash=sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1 \ - --hash=sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f \ --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ --hash=sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15 \ --hash=sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2 \ --hash=sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42 \ - --hash=sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2 \ - --hash=sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032 \ - --hash=sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b \ --hash=sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b \ --hash=sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be \ - --hash=sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c \ - --hash=sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb \ - --hash=sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014 \ - --hash=sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850 \ - --hash=sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85 \ - --hash=sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621 \ - --hash=sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39 \ - --hash=sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8 \ --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b -python-engineio==4.0.0 \ - --hash=sha256:9f34afa4170f5ba6e3d9ff158752ccf8fbb2145f16554b2f0fc84646675be99a \ - --hash=sha256:33f7a214be5db35c867e97027bfe63676cb003d82aa17a607612b25ba5d84e5b -python-socketio==5.0.4 \ - --hash=sha256:f53fd0d5bd9f75a70492062f4ae6195ab5d34d67a29024d740f25e468392893e \ - --hash=sha256:870f8b00a63ef7c9a1f85fd70028624867bf246115e82625f28ef79def8847bb +python-engineio==4.0.1 \ + --hash=sha256:bb575c1a3512b4b5d4706f3071d5cc36e592459e99a47d9a4b7faabeba941377 \ + --hash=sha256:759ec99d91b3d36b29596124c77ba7c063c7ab97685d318fb23a166d9a4b9187 +python-socketio==5.1.0 \ + --hash=sha256:338cc29abb6f3ca14c88f1f8d05ed27c690df4648f62062b299f92625bbf7093 \ + --hash=sha256:8a7ed43bfdbbb266eb8a661a0c9648dc94bcd9689566ae3ee08bf98eca8987af six==1.15.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 @@ -152,6 +133,6 @@ typing-extensions==3.7.4.3; python_version < "3.8" and python_version >= "3.6" \ werkzeug==1.0.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \ --hash=sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43 \ --hash=sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c -zipp==3.4.0; python_version < "3.8" and python_version >= "3.6" \ - --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ - --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb +zipp==3.4.1; python_version < "3.8" and python_version >= "3.6" \ + --hash=sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098 \ + --hash=sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76 diff --git a/scripts/create_env.sh b/scripts/create_env.sh new file mode 100755 index 0000000000000000000000000000000000000000..79facaaca4605d8dff26e48d8996e975828667cf --- /dev/null +++ b/scripts/create_env.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +python3 -m venv env +source env/bin/activate +pip3 install -r requirements.txt + diff --git a/scripts/update_dependecies.sh b/scripts/update_dependecies.sh new file mode 100755 index 0000000000000000000000000000000000000000..26292581aa3c1843b9c28f35d655fc536da6326f --- /dev/null +++ b/scripts/update_dependecies.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +poetry export -f requirements.txt --output requirements.txt diff --git a/static/style.css b/static/style.css index d755cc479b4aef218235117a20eeb0c647304000..c0edc32ff2e525e763e19c37e0741a6b55c8223a 100644 --- a/static/style.css +++ b/static/style.css @@ -245,6 +245,13 @@ header h1 a:not(:first-of-type) { margin-top: 24%; } +body.inactive #stream{ + background-color: black; + border: 1em solid black; + box-sizing: border-box; + min-height: 26em; +} + @keyframes slideInFromLeft { 0% { transform: translateX(-100%); diff --git a/static/sync-stream.js b/static/sync-stream.js index 7946c09c740270d7141e40c04c18ec06907d658e..9e989d3c412cad04033114a5091b32f0545c38c9 100644 --- a/static/sync-stream.js +++ b/static/sync-stream.js @@ -1,4 +1,6 @@ var socket = io(); +let hasEverRun = false; +let player = null; // Extract foobar from the .stream-foobar key of an element function extractStreamKey(e) { @@ -15,10 +17,28 @@ function getStreamKey() { return extractStreamKey(stream); } +// Returns true if the key k is in the streamlist +function hasStream(streamlist, k) { + return streamlist.some(({ key }) => key === k); +} + +// Returns the Stream from the streamlist +function getStream(streamlist, k) { + return streamlist.find(({ key }) => key === k); +} + // Send a message to the server when the socket is established socket.on('connect', function() { let key = getStreamKey() socket.emit('join', {"key" : key}); + // Ask about the state of the stream + socket.emit('stream_info', {"key" : key}); +}); + +// After initial connect, receive a streamlist +socket.on('stream_info', function(data) { + var stream = JSON.parse(data) + updateStream(stream, "update"); }); // Send a message to the server when the socket is established @@ -34,13 +54,36 @@ window.onbeforeunload = function(event) { console.log("Sent leave message"); }; - // After initial connect, receive a streamlist socket.on('viewercount', function(viewercount) { updateViewCount(viewercount); }); +// New streamlist arrives here when webserver gets notivied of an stream addition +socket.on('stream_added', function(data) { + console.log('Stream ' + data['key'] + ' added.'); + var streamlist = JSON.parse(data["list"]) + let streamkey = getStreamKey(); + if (hasStream(streamlist, streamkey)) { + let stream = getStream(streamlist, streamkey); + updateStream(stream, "added"); + }else{ + console.log("..but "+streamkey+" was not in streamlist"); + console.log(streamlist); + } +}); +// New streamlist arrives here when webserver gets notivied of a stream removal +socket.on('stream_removed', function(data) { + console.log('Stream ' + data['key'] + ' removed.'); + let streamkey = getStreamKey(); + if (streamkey == data['key']) { + updateStream(streamkey, "removed"); + } +}); + + +// Update the count of viewers function updateViewCount(viewercount) { if (document.getElementById("viewcount") !== null) { let count = viewercount['count']; @@ -50,4 +93,230 @@ function updateViewCount(viewercount) { } document.getElementById("viewcount").textContent = viewercount['count']; } +} + +// Update the stream when it has been added +function updateStream(stream, what) { + if (what == "update") { + console.log("Stream "+stream.key+" updated") + if (!stream.active) { + deactivateStream(stream.key); + }else{ + hasEverRun = true; + } + } else if (what == "added") { + console.log("Stream "+stream.key+" has started"); + hasEverRun = true; + activateStream(stream); + }else if (what == "removed") { + console.log("Stream "+stream+" has stopped"); + deactivateStream(stream); + } +} + + +function deactivateStream(streamkey) { + // Add inactive class to body + document.body.classList.add("inactive"); + + // Pause the player + if (player !== null) { + player.pause(); + } + + // Get width and height from player + var videoPlayer = document.getElementById("stream"); + var w = videoPlayer.offsetWidth; + var h = videoPlayer.offsetHeight; + + // Remove video player + if (document.getElementById("stream") !== null) { + document.querySelectorAll('#stream').forEach(e => e.remove()); + } + + if (player !== null) { + player.dispose(); + } + + // Get parent element + let content = document.getElementsByTagName("content")[0]; + + // Create a div + let div = document.createElement("div"); + div.id = "stream"; + div.classList.add("stream-"+streamkey) + div.style.height = h+"px"; + div.style.width = w+"px"; + + // Add a notice to it + let h2 = document.createElement("h2"); + if (hasEverRun) { + h2.textContent = "The stream has ended" + h2.classList.add("stopped"); + document.body.classList.add("stopped"); + }else{ + h2.textContent = "The stream hasn't started yet (or it doesn't exist)" + h2.classList.add("not_started"); + document.body.classList.add("not-started"); + } + h2.id = "no-stream-notice"; + div.appendChild(h2); + + content.prepend(div); +} + +function activateStream(stream) { + document.body.classList.remove("inactive"); + document.body.classList.remove("not-started"); + document.body.classList.remove("stopped"); + + // Remove div placeholder + if (document.getElementById("stream") !== null) { + document.querySelectorAll('#stream').forEach(e => e.remove()); + } + + // TODO: Add description, ... + addPlayer(stream.key); + player = initializePlayer(); + player.load(); + player.play(); + + updateDescription(stream); +} + + +function updateDescription(stream) { + if (stream.description !== null && stream.description !== "") { + let descriptions = document.querySelectorAll('.description'); + if (descriptions.length === 0) { + buildDescriptionBlock(); + } + let description = document.querySelectorAll('.description')[0]; + + // TODO: handle markdown...? + description.textContent = stream.description; + }else{ + // There was formerly a description which is now gone, so destroy description + document.querySelectorAll('.description').forEach(e => e.remove()); + } +} + +function buildDescriptionBlock() { + let content = document.getElementsByTagName("content")[0]; + let section = document.createElement("section"); + section.classList.add("description"); + content.appendChild(section); +} + + +// When the window is loaded check for errors +window.onload = function() { + if (!document.body.classList.contains("inactive")){ + player = initializePlayer(); + } + // Run this block with a delay + setTimeout(function() { + var videoPlayer = document.getElementById("stream"); + + if (!document.body.classList.contains("inactive")){ + // If there is an error try every two seconds if the player now finds the + // video. Once it is found, remove the interval + if (videoPlayer.classList.contains("vjs-error")) { + var intervalId = setInterval(function() { + checkIfStillErrored(intervalId) ; + }, 2000); + } + + // Autoplay with delay if possible + player.play(); + }else{ + deactivateStream(getStreamKey()); + } + + }, 200); +} + + + +function checkIfStillErrored(intervalId) { + var videoPlayer = document.getElementById("stream"); + if (videoPlayer.classList.contains("vjs-error")) { + console.log("Stream still errored, trying to reload it"); + player.pause(); + player.load(); + }else{ + console.log("Error seems resolved"); + player.load(); + player.play(); + clearInterval(intervalId); + } +} + + +function addPlayer(streamkey) { + // Get parent element + let content = document.getElementsByTagName("content")[0]; + + // Create player + let videojs = document.createElement("video-js"); + videojs.id = "stream"; + videojs.classList.add("vjs-default-skin", "stream-"+streamkey, ); + videojs.setAttribute("data-setup", '{"fluid": true, "liveui": true}'); + videojs.toggleAttribute('controls'); + + let source = document.createElement("source"); + source.src = "../hls/"+streamkey+".m3u8"; + source.type = "application/x-mpegURL" + + videojs.prepend(source); + content.prepend(videojs) +} + + +function initializePlayer() { + var player = videojs('stream'); + player.autoplay('any'); + + function displayMuteifNeeded(player) { + if (player.muted()){ + let title = document.querySelector("#page_title"); + if (title.querySelector(".muted") == null) { + let a = document.createElement("a"); + let img = document.createElement("img"); + img.src = "/static/mute.svg"; + a.classList.add("muted"); + a.onclick = function() { + player.muted(false); + displayMuteifNeeded(player); + }; + a.appendChild(img); + title.appendChild(a); + } + }else{ + let title = document.querySelector("#page_title"); + title.querySelectorAll('.muted').forEach(e => e.remove()); + } + } + + player.on('play', () => { + displayMuteifNeeded(player); + }); + + player.on("volumechange",function(){ + displayMuteifNeeded(player); + }); + + // player.on('error', () => { + // player.createModal('Retrying connection'); + // if (player.error().code === 4) { + // this.player.retryLock = setTimeout(() => { + // player.src({ + // src: data.url + // }); + // player.load(); + // }, 2000); + // } + // }); + + return player; } \ No newline at end of file diff --git a/static/sync-streamlist.js b/static/sync-streamlist.js index c986572fbd6f3023c031ed760d96e65ac26e9f68..4a3ddf866e3b0d25dbf2979398da745dbe31ec6f 100644 --- a/static/sync-streamlist.js +++ b/static/sync-streamlist.js @@ -120,7 +120,9 @@ function updateViewcounts(streams, streamlist) { for (const li of active_streams) { let key = extractStreamKey(li); let match = streamlist.find(stream => stream.key === key); - if (li.querySelector(".viewcount") == null && match.viewcount != 0) { + if (match === undefined) { + // Do nothing if stream gone + } else if (li.querySelector(".viewcount") == null && match.viewcount != 0) { let p = document.createElement("p"); let div = document.createElement("div"); div.classList.add("viewcount"); diff --git a/streamviewer/server.py b/streamviewer/server.py index 860c5f2d5c9b2f0a334ad1a72fba9396e76b4b2a..6679fc239299371f570c1460680fabecbf9ded16 100644 --- a/streamviewer/server.py +++ b/streamviewer/server.py @@ -10,7 +10,7 @@ from flaskext.markdown import Markdown from flask_socketio import SocketIO, join_room, leave_room from .config import initialize_config, APPLICATION_NAME, DEFAULT_CONFIG -from .streams import Stream, StreamList, value_to_flag +from .streams import Stream, StreamList, value_to_flag, key_if_not_None # Initialization @@ -67,17 +67,21 @@ def stream(streamkey): # Strip potential trailing slashes streamkey = streamkey.rstrip("/") stream = streamlist.get_stream(streamkey) + streamkey = key_if_not_None(stream, "key", that=streamkey) + description = key_if_not_None(stream, "description") # Render a different Template if the stream is missing if stream is None: + existed = False # Stream was Missing, log warning - app.logger.warning("Looking for stream {}, but it didn't exist".format(streamkey)) - return render_template("stream_missing.html", application_name=APPLICATION_NAME, page_title=config["application"]["page_title"], streamkey=streamkey, list_streams=config["application"]["list_streams"]), 404 + running_since = None + app.logger.info("Client {} looked for non-existent stream {}".format(request.remote_addr, streamkey)) else: - app.logger.debug("Looking for {}/{}.m3u8".format(config["application"]["hls_path"], streamkey)) + existed = True + app.logger.debug("Client requests stream {} ({}/{}.m3u8)".format(streamkey, config["application"]["hls_path"], streamkey)) running_since = humanize.naturaldelta(dt.timedelta(seconds=stream.active_since())) # Everything ok, return Stream - return render_template('stream.html', application_name=APPLICATION_NAME, page_title=config["application"]["page_title"], hls_path=config["application"]["hls_path"], streamkey=stream.key, description=stream.description, running_since=running_since) + return render_template('stream.html', application_name=APPLICATION_NAME, page_title=config["application"]["page_title"], hls_path=config["application"]["hls_path"], streamkey=streamkey, description=description, running_since=running_since, existed=existed) @app.route('/', methods = ['GET']) @@ -168,17 +172,26 @@ def client_list_connected(): socketio.emit('stream_list', {'list': json_list}) -@socketio.on('connect_single') -def client_single_connected(data): - if data is not None: - app.logger.info('Client (single page > {}) connected via socket.io'.format(data)) - @socketio.on('stream_list') def send_streamlist(): json_list = streamlist.json_list() socketio.emit('stream_list', {'list': json_list}) +@socketio.on('stream_info') +def send_streaminfo(data): + if type(data) is dict and "key" in data.keys(): + app.logger.info('Client wants info about stream {}'.format(data['key'])) + key = data["key"] + stream = streamlist.get_stream(key) + if stream is not None: + json = stream.to_json() + app.logger.debug('Sending Stream info\n{}'.format(json)) + socketio.emit('stream_info', json) + else: + app.logger.warning('Client {} asked for info on non-existing stream {}'.format(request.remote_addr, data['key'])) + + @socketio.on('join') def on_join(data): app.logger.info('Client connected to stream {}'.format(data['key'])) @@ -187,6 +200,7 @@ def on_join(data): count = streamlist.add_viewer(key) socketio.emit('viewercount', {'count': count, 'direction': 'up'}, room=key) + @socketio.on('leave') def on_leave(data): app.logger.info('Client left to stream {}'.format(data['key'])) @@ -195,5 +209,6 @@ def on_leave(data): count = streamlist.remove_viewer(key) socketio.emit('viewercount', {'count': count, 'direction': 'down'}, room=key) + if __name__ == '__main__': socketio.run(app) \ No newline at end of file diff --git a/streamviewer/streams.py b/streamviewer/streams.py index af1ddaacecacad98a19b0bd99abe637ead4cbe87..51c6a8c71b4905e6e416b8554d6f1a41ff16e71c 100644 --- a/streamviewer/streams.py +++ b/streamviewer/streams.py @@ -1,6 +1,7 @@ #!/usr/bin/env python #-*- coding: utf-8 -*- import json +import copy from typing import Optional, NewType, List, Any import datetime as dt @@ -38,6 +39,25 @@ def none_if_no_key_value_otherwise(d: dict, key: str, that=None) -> Optional[Any return d[key] +def key_if_not_None(o, key: str, that=None): + """ + Return the object.key or object["key"] if it exists, + otherwise return that (per default None) + """ + if o is None: + return that + elif type(o) is dict: + if key in o.keys(): + return o[key] + else: + return that + else: + try: + return getattr(o, key) + except TypeError: + return that + + def value_to_flag(value) -> bool: """ Return False if the value was None, otherwise return wether it was in the list @@ -94,8 +114,19 @@ class Stream(): return "{} ({})".format(self.key, ", ".join(attributes)) return "{}".format(self.key) + def __iter__(self): + clone = copy.deepcopy(self) + del clone.protected + del clone.password + del clone.unlisted + for key in clone.__dict__: + yield key, getattr(clone, key) + + def to_dict(self) -> dict: + return dict(self) + def to_json(self): - return json.dumps(self, default=jsonconverter, + return json.dumps(self.to_dict(), default=jsonconverter, sort_keys=True, indent=4) @property @@ -480,10 +511,12 @@ class StreamList(): return self - - def jsonconverter(o): - if isinstance(o, dt.datetime): + o = copy.deepcopy(o) + if isinstance(o, Stream): + # We use to_dict() here, to keep fields like password private : ) + return o.to_dict() + elif isinstance(o, dt.datetime): return o.__str__() else: return o.__dict__ \ No newline at end of file diff --git a/templates/stream.html b/templates/stream.html index 7d34234ecc13f1764a4d6d446f64af48a74d9e22..afaea2f328bcfd16c8c598bfa89c48d8026ee645 100644 --- a/templates/stream.html +++ b/templates/stream.html @@ -14,52 +14,30 @@ {% endblock %} {% block content %} - <video-js id="stream" class="vjs-default-skin stream-{{ streamkey }}" data-setup='{"fluid": true, "liveui": true}' controls> - <source src="../hls/{{ streamkey }}.m3u8" type="application/x-mpegURL"> - </video-js> - {% if description %} - <section class="description"> - {{ description|markdown }} - </section> + {% if existed %} + <video-js id="stream" class="vjs-default-skin stream-{{ streamkey }}" data-setup='{"fluid": true, "liveui": true}' controls> + <source src="../hls/{{ streamkey }}.m3u8" type="application/x-mpegURL"> + </video-js> + {% if description %} + <section class="description"> + {{ description|markdown }} + </section> + {% endif %} + {% else %} + <div id="stream" class="stream-{{ streamkey }}"> + <h2 class="stopped" id="no-stream-notice">The stream hasn't started yet (or it doesn't exist)</h2> + </div> {% endif %} {% endblock %} {% block footer %} {{ super() }} - <script src="{{ url_for('static', filename='video.min.js') }}"></script> - <script src="{{ url_for('static', filename='videojs-http-streaming.min.js') }}"></script> + {% if not existed %} <script> - let player = videojs('stream'); - player.autoplay('any'); - - function displayMuteifNeeded(player) { - if (player.muted()){ - let title = document.querySelector("#page_title"); - if (title.querySelector(".muted") == null) { - let a = document.createElement("a"); - let img = document.createElement("img"); - img.src = "{{ url_for('static', filename='mute.svg') }}"; - a.classList.add("muted"); - a.onclick = function() { - player.muted(false); - displayMuteifNeeded(player); - }; - a.appendChild(img); - title.appendChild(a); - } - }else{ - let title = document.querySelector("#page_title"); - title.querySelectorAll('.muted').forEach(e => e.remove()); - } - } - - player.on('play', () => { - displayMuteifNeeded(player); - }); - - player.on("volumechange",function(){ - displayMuteifNeeded(player); - }); + document.body.classList.add("inactive"); </script> + {% endif %} + <script src="{{ url_for('static', filename='video.min.js') }}"></script> + <script src="{{ url_for('static', filename='videojs-http-streaming.min.js') }}"></script> <script src="{{ url_for('static', filename='sync-stream.js') }}"></script> {% endblock %} diff --git a/templates/stream_missing.html b/templates/stream_missing.html deleted file mode 100644 index 6ad08e4a13aee4482b91cf93266b024aced95d8b..0000000000000000000000000000000000000000 --- a/templates/stream_missing.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "layout.html" %} -{% block title %}{{ page_title }}/{{ streamkey }}{% endblock %} -{% block head %} - <link href="{{ url_for('static', filename='video-js.css') }}" rel="stylesheet"> - {{ super() }} -{% endblock %} - -{% block header %} - <h1 id="page_title"><a href="/">{{ page_title }}</a>/<s>{{ streamkey }}</s></h1> -{% endblock %} - -{% block content %} - <h2 class="404">Uh-oh!</h2> - <h2>The stream {{ streamkey }} doesn't seem to be here (anymore?)</h2> - {% if list_streams %} - <p>Have a look at the <a href="/streams">list of current streams</a></p> - {% endif %} -{% endblock %} - -{% block footer %} - {{ super() }} -{% endblock %} - - -