From aeed68d692d184266554b9fe6a86a183d4029f71 Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Thu, 21 Apr 2022 17:18:06 +0300 Subject: [PATCH] - --- LICENSE | 20 +- .../INSTALLER | 0 .../METADATA | 15 +- .../pyOpenRPA-1.2.7.dist-info}/RECORD | 60 +- .../REQUESTED | 0 .../WHEEL | 0 .../top_level.txt | 0 .../Lib/site-packages/pyOpenRPA/Agent/O2A.py | 46 +- .../pyOpenRPA/Agent/Processor.py | 30 + .../pyOpenRPA/Agent/__Agent__.py | 87 +- .../Orchestrator/BackwardCompatibility.py | 48 +- .../pyOpenRPA/Orchestrator/Core.py | 2 +- .../Orchestrator/Managers/ControlPanel.py | 348 +++++++ .../pyOpenRPA/Orchestrator/Managers/Git.py | 249 +++++ .../Orchestrator/Managers/Process.py | 692 ++++++++++++ .../Orchestrator/Managers/__init__.py | 3 + .../pyOpenRPA/Orchestrator/Processor.py | 38 +- .../Orchestrator/RobotRDPActive/Connector.py | 4 +- .../Orchestrator/RobotRDPActive/Processor.py | 3 +- .../Orchestrator/RobotRDPActive/Template.rdp | Bin 2426 -> 2462 bytes .../Orchestrator/RobotScreenActive/Monitor.py | 2 +- .../pyOpenRPA/Orchestrator/Server.py | 49 +- .../pyOpenRPA/Orchestrator/ServerSettings.py | 173 ++- .../Orchestrator/SettingsTemplate.py | 97 +- .../pyOpenRPA/Orchestrator/Web/Index.js | 100 +- .../pyOpenRPA/Orchestrator/Web/Index.xhtml | 61 ++ .../Orchestrator/__Orchestrator__.py | 986 ++++++++++++++---- .../pyOpenRPA/Orchestrator/__init__.py | 1 + .../Tools/{Terminator.py => StopSafe.py} | 42 +- .../Lib/site-packages/pyOpenRPA/__init__.py | 2 +- .../schedule-1.1.0.dist-info/AUTHORS.rst | 35 + .../schedule-1.1.0.dist-info}/INSTALLER | 0 .../schedule-1.1.0.dist-info/LICENSE.txt | 21 + .../schedule-1.1.0.dist-info/METADATA | 91 ++ .../schedule-1.1.0.dist-info/RECORD | 9 + .../schedule-1.1.0.dist-info/WHEEL | 6 + .../schedule-1.1.0.dist-info/top_level.txt | 1 + .../Lib/site-packages/schedule/__init__.py | 839 +++++++++++++++ .../pyOpenRPA-1.2.7.dist-info/INSTALLER | 1 + .../METADATA | 15 +- .../pyOpenRPA-1.2.7.dist-info}/RECORD | 60 +- .../REQUESTED | 0 .../WHEEL | 0 .../top_level.txt | 0 .../Lib/site-packages/pyOpenRPA/Agent/O2A.py | 46 +- .../pyOpenRPA/Agent/Processor.py | 30 + .../pyOpenRPA/Agent/__Agent__.py | 87 +- .../Orchestrator/BackwardCompatibility.py | 48 +- .../pyOpenRPA/Orchestrator/Core.py | 2 +- .../Orchestrator/Managers/ControlPanel.py | 348 +++++++ .../pyOpenRPA/Orchestrator/Managers/Git.py | 249 +++++ .../Orchestrator/Managers/Process.py | 692 ++++++++++++ .../Orchestrator/Managers/__init__.py | 3 + .../pyOpenRPA/Orchestrator/Processor.py | 38 +- .../Orchestrator/RobotRDPActive/Connector.py | 4 +- .../Orchestrator/RobotRDPActive/Processor.py | 3 +- .../Orchestrator/RobotRDPActive/Template.rdp | Bin 2426 -> 2462 bytes .../Orchestrator/RobotScreenActive/Monitor.py | 2 +- .../pyOpenRPA/Orchestrator/Server.py | 49 +- .../pyOpenRPA/Orchestrator/ServerSettings.py | 173 ++- .../Orchestrator/SettingsTemplate.py | 97 +- .../pyOpenRPA/Orchestrator/Web/Index.js | 100 +- .../pyOpenRPA/Orchestrator/Web/Index.xhtml | 61 ++ .../Orchestrator/__Orchestrator__.py | 986 ++++++++++++++---- .../pyOpenRPA/Orchestrator/__init__.py | 1 + .../Tools/{Terminator.py => StopSafe.py} | 42 +- .../Lib/site-packages/pyOpenRPA/__init__.py | 2 +- .../GuideSphinx/03_Copyrights_Contacts.rst | 2 +- Sources/GuideSphinx/index.rst | 12 +- Sources/pyOpenRPA/__init__.py | 2 +- Sources/setup.py | 12 +- .../html/03_Copyrights_Contacts.html | 2 +- Wiki/ENG_Guide/html/Agent/02_Defs.html | 33 +- Wiki/ENG_Guide/html/Orchestrator/02_Defs.html | 347 +++--- .../Orchestrator/03_gSettingsTemplate.html | 40 +- .../html/Orchestrator/04_HowToUse.html | 33 +- .../html/Orchestrator/06_Defs Managers.html | 676 ++++++++---- .../_modules/pyOpenRPA/Agent/__Agent__.html | 87 +- .../Orchestrator/Managers/ControlPanel.html | 5 + .../Orchestrator/Managers/Process.html | 238 ++++- .../Orchestrator/__Orchestrator__.html | 286 +++-- .../_sources/03_Copyrights_Contacts.rst.txt | 2 +- Wiki/ENG_Guide/html/_sources/index.rst.txt | 12 +- Wiki/ENG_Guide/html/genindex.html | 115 +- Wiki/ENG_Guide/html/index.html | 9 +- Wiki/ENG_Guide/html/objects.inv | Bin 2169 -> 2272 bytes Wiki/ENG_Guide/html/searchindex.js | 2 +- .../markdown/03_Copyrights_Contacts.md | 2 +- Wiki/ENG_Guide/markdown/Agent/02_Defs.md | 36 +- .../markdown/Orchestrator/02_Defs.md | 296 ++++-- .../Orchestrator/03_gSettingsTemplate.md | 40 +- .../markdown/Orchestrator/04_HowToUse.md | 33 +- .../markdown/Orchestrator/06_Defs Managers.md | 504 +++++++-- Wiki/ENG_Guide/markdown/index.md | 11 +- v1.2.6 => v1.2.7 | 0 95 files changed, 8400 insertions(+), 1736 deletions(-) rename Resources/WPy32-3720/python-3.7.2/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/INSTALLER (100%) rename Resources/WPy32-3720/python-3.7.2/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/METADATA (90%) rename Resources/{WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info => WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info}/RECORD (92%) rename Resources/WPy32-3720/python-3.7.2/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/REQUESTED (100%) rename Resources/WPy32-3720/python-3.7.2/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/WHEEL (100%) rename Resources/WPy32-3720/python-3.7.2/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/top_level.txt (100%) create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py rename Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/{Terminator.py => StopSafe.py} (67%) create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/AUTHORS.rst rename Resources/{WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info => WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info}/INSTALLER (100%) create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/LICENSE.txt create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/METADATA create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/RECORD create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/WHEEL create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/top_level.txt create mode 100644 Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule/__init__.py create mode 100644 Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/INSTALLER rename Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/METADATA (90%) rename Resources/{WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info => WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info}/RECORD (92%) rename Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/REQUESTED (100%) rename Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/WHEEL (100%) rename Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/{pyOpenRPA-1.2.6.dist-info => pyOpenRPA-1.2.7.dist-info}/top_level.txt (100%) create mode 100644 Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py create mode 100644 Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py create mode 100644 Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py create mode 100644 Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py rename Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/{Terminator.py => StopSafe.py} (67%) rename v1.2.6 => v1.2.7 (100%) diff --git a/LICENSE b/LICENSE index a0e4f711..bc4274a8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,4 @@ Лицензия-оферта -MIT License +pyOpenRPA License Copyright (c) 2019 Ivan Maslov - -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. diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/INSTALLER b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/INSTALLER similarity index 100% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/INSTALLER rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/INSTALLER diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/METADATA b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/METADATA similarity index 90% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/METADATA rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/METADATA index c6e28f2c..2d9287f9 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/METADATA +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/METADATA @@ -1,15 +1,17 @@ Metadata-Version: 2.1 Name: pyOpenRPA -Version: 1.2.6 +Version: 1.2.7 Summary: First open source RPA platform for business -Home-page: https://gitlab.com/UnicodeLabs/OpenRPA +Home-page: https://pyopenrpa.ru/ Author: Ivan Maslov -Author-email: Ivan.Maslov@unicodelabs.ru -License: MIT -Keywords: OpenRPA RPA Robot Automation Robotization OpenSource +Author-email: Ivan.Maslov@pyopenrpa.ru +License: PYOPENRPA +Keywords: pyOpenRPA OpenRPA RPA Robot Automation Robotization OpenSource IT4Business Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable -Classifier: License :: OSI Approved :: MIT License +Classifier: License :: Free For Educational Use +Classifier: License :: Free For Home Use +Classifier: License :: Free for non-commercial use Classifier: Intended Audience :: Developers Classifier: Environment :: Win32 (MS Windows) Classifier: Environment :: X11 Applications @@ -25,6 +27,7 @@ Requires-Dist: pillow (>=6.0.0) Requires-Dist: keyboard (>=0.13.3) Requires-Dist: pyautogui (<=0.9.52) Requires-Dist: crypto (>=1.4.1) +Requires-Dist: schedule (>=1.1.0) Requires-Dist: pywinauto (>=0.6.8) ; platform_system == "win32" and python_version >= "3.0" Requires-Dist: WMI (>=1.4.9) ; platform_system == "win32" and python_version >= "3.0" Requires-Dist: pywin32 (>=224) ; platform_system == "win32" and python_version >= "3.0" diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/RECORD b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/RECORD similarity index 92% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/RECORD rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/RECORD index 6f519764..23916d31 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/RECORD +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/RECORD @@ -1,9 +1,9 @@ -pyOpenRPA-1.2.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pyOpenRPA-1.2.6.dist-info/METADATA,sha256=pU-EpQuvMPYjhD1o2BYjfpOfsruNrxm9s7mfWX9mF1M,3612 -pyOpenRPA-1.2.6.dist-info/RECORD,, -pyOpenRPA-1.2.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pyOpenRPA-1.2.6.dist-info/WHEEL,sha256=qB97nP5e4MrOsXW5bIU5cUn_KSVr10EV0l-GCHG9qNs,97 -pyOpenRPA-1.2.6.dist-info/top_level.txt,sha256=RPzwQXgYBRo_m5L3ZLs6Voh8aEkMeT29Xsul1w1qE0g,10 +pyOpenRPA-1.2.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyOpenRPA-1.2.7.dist-info/METADATA,sha256=BzhjdELqdQkNJuXEIKiYjzPFg1-cNp2PHBfF9sVzBT4,3744 +pyOpenRPA-1.2.7.dist-info/RECORD,, +pyOpenRPA-1.2.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyOpenRPA-1.2.7.dist-info/WHEEL,sha256=qB97nP5e4MrOsXW5bIU5cUn_KSVr10EV0l-GCHG9qNs,97 +pyOpenRPA-1.2.7.dist-info/top_level.txt,sha256=RPzwQXgYBRo_m5L3ZLs6Voh8aEkMeT29Xsul1w1qE0g,10 pyOpenRPA/.idea/inspectionProfiles/profiles_settings.xml,sha256=YXLFmX7rPNGcnKK1uX1uKYPN0fpgskYNe7t0BV7cqkY,174 pyOpenRPA/.idea/misc.xml,sha256=V-fQnOz-bYEZULgfbFgm-8mURphZrKfXMSd0wKjeEyA,188 pyOpenRPA/.idea/modules.xml,sha256=Q__U1JIA2cjxbLRXAv-SfYY00fZA0TNlpkkbY4s3ncg,277 @@ -11,9 +11,9 @@ pyOpenRPA/.idea/pyOpenRPA.iml,sha256=EXh41F8lqRiSBMVg-n2tKaEaHC6_3gGSuKkPJA12Na0 pyOpenRPA/.idea/vcs.xml,sha256=2HygA1oRAwc3VBf-irxHrX5JJG9DXuQwrN0BlubhoKY,191 pyOpenRPA/.idea/workspace.xml,sha256=6tJZehshdK4And6tEoUvkIB0KE7waL_NnTSkTYYAeFA,3802 pyOpenRPA/Agent/A2O.py,sha256=PlIZZCTnVrYF2i6DSAi_KbzZfc2gtcBPmOerrEZq68U,1718 -pyOpenRPA/Agent/O2A.py,sha256=vu7UgiB0qY6-1i9qVWEBGyXWSi68TTNfkvnpMIZH7Vo,4458 -pyOpenRPA/Agent/Processor.py,sha256=Co8nWpffgsnEE_TpG9WrpKxz3N0sDF7eFnKxDOk1sd8,4653 -pyOpenRPA/Agent/__Agent__.py,sha256=JcMFvSC3_M94HEdZe8AK2IHuJOxeDJi4RpnY_LivWpM,10639 +pyOpenRPA/Agent/O2A.py,sha256=XHl5nytUoUqfPvmYWh5auYo-s0GIThNmkOA9ON-JCis,5535 +pyOpenRPA/Agent/Processor.py,sha256=xNZfQ_HcV-qm_x90tBLKYJqvnENiTqHSoUk2LhDfqWQ,6346 +pyOpenRPA/Agent/__Agent__.py,sha256=RLy7YQyTm_IF9kjZ22k7hd5E7wRTBSau_zsxBtW17l0,12554 pyOpenRPA/Agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Agent/__pycache__/A2O.cpython-37.pyc,, pyOpenRPA/Agent/__pycache__/O2A.cpython-37.pyc,, @@ -22,20 +22,28 @@ pyOpenRPA/Agent/__pycache__/__Agent__.cpython-37.pyc,, pyOpenRPA/Agent/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Agent/readme.md,sha256=QF_Bnv204OK3t1JUEhjfICkxFmSdX6bvaRl_HI6lH9I,19 pyOpenRPA/Info.md,sha256=u4Nv-PjniSF0Zlbtr6jEJX2vblK3_1zhSLNUgOdtDaA,85 -pyOpenRPA/Orchestrator/BackwardCompatibility.py,sha256=a2UZINDnHCKZVvHtOOPMyFZmDynzfcyQhFJCEEMhadY,34599 +pyOpenRPA/Orchestrator/BackwardCompatibility.py,sha256=CpJtOc_WnV14AGIU7FKVRuemlf9bSr4Eo5_67wuyi_k,37506 pyOpenRPA/Orchestrator/ControlPanel.py,sha256=OzS8HjG__8OZgqhajr8L8owyugXPuSLWHLtXuKdEP78,103 -pyOpenRPA/Orchestrator/Core.py,sha256=Kjphtu0g6iaS4D_fIWmzRaLLgBQ9fcwccpQJhOETTAc,521 -pyOpenRPA/Orchestrator/Processor.py,sha256=Z1SglmX6ykTLifD3M1mzWEJQUgweWo6HjjCjHldjGyM,8594 +pyOpenRPA/Orchestrator/Core.py,sha256=OHa3mSC3_wRAizqrWBVjlR6ln4-xVVvBpOSnWl6qVvY,529 +pyOpenRPA/Orchestrator/Managers/ControlPanel.py,sha256=BgtLjb6PR6kTlOjPLCg2YGP458LS9JOaYEfNurhS0nk,16544 +pyOpenRPA/Orchestrator/Managers/Git.py,sha256=dgXx2UzSwiEev4ov2hBbb-5MhXVhFKWZo2lmr19QSCQ,12582 +pyOpenRPA/Orchestrator/Managers/Process.py,sha256=7T_qofdkRJHdPQbaiEsTDOboImSf2N6d_Ku513rURkw,41369 +pyOpenRPA/Orchestrator/Managers/__init__.py,sha256=4my0XiwmI_QLRQVhOzNvWTggCosF3tb2yRxGkehOCq0,71 +pyOpenRPA/Orchestrator/Managers/__pycache__/ControlPanel.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Managers/__pycache__/Git.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Managers/__pycache__/Process.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Managers/__pycache__/__init__.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Processor.py,sha256=FtNmdIsBStkLHLlOe6MDWzSmZv9m7ntlQs-NirA6OgQ,10264 pyOpenRPA/Orchestrator/ProcessorOld.py,sha256=Vh5zLRpWWf-vt9CCYI8nDY7oaefiufnu6Pnl4tp27pY,13749 pyOpenRPA/Orchestrator/RobotRDPActive/CMDStr.py,sha256=6otw1WnR2_evvQ5LGyOVh0BLk_nTdilViGub7p56fXQ,1531 pyOpenRPA/Orchestrator/RobotRDPActive/Clipboard.py,sha256=YB5HJL-Qf4IlVrFHyRv_ZMJ0Vo4vjyYqWKjvrTnf1k4,1564 -pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py,sha256=Qwf194uO_wcO3kBe2hTI0py90ZC1ejDUGeh6UWEfavc,27789 +pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py,sha256=_rTktGLcPaQoPUi482vnUWoeuB0zZcyS3k5kwEbvnM8,28021 pyOpenRPA/Orchestrator/RobotRDPActive/ConnectorExceptions.py,sha256=wwH9JOoMFFxDKQ7IyNyh1OkFkZ23o1cD8Jm3n31ycII,657 -pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py,sha256=AQ_u9QVSLpce9hhSacm3UT98bErc636MXza4Q6mHsSc,12264 +pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py,sha256=bMahu6wRznmoLYsopgbNOLqufcQnEDanIepuGvXIsac,12405 pyOpenRPA/Orchestrator/RobotRDPActive/Recovery.py,sha256=jneD474V-ZBYnmQFxWoY_feGNMSL0lGaRK6TEfQ6gOc,2954 pyOpenRPA/Orchestrator/RobotRDPActive/RobotRDPActive.py,sha256=5FX48HlIn8NKfs7q_rp3lpumWtNcwdHq7J8ygnOwU_g,12284 pyOpenRPA/Orchestrator/RobotRDPActive/Scheduler.py,sha256=21N0ilFzWI1mj3X5S9tPMgwvG7BviuBxfTuqBY85Hy4,9144 -pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp,sha256=JEMVYkEmNcfg_p8isdIyvj9E-2ZB5mj-R3MkcNMKxkA,2426 +pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp,sha256=YjIxCXyIvDtZx-MPpyHPj3quT9dlUZPuuILiB21eRpU,2462 pyOpenRPA/Orchestrator/RobotRDPActive/Timer.py,sha256=y8--fUvg10qEFomecl_cmdWpdGjarZBlFpMbs_GvzoQ,1077 pyOpenRPA/Orchestrator/RobotRDPActive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Orchestrator/RobotRDPActive/__main__.py,sha256=z9PaUK4_nBiGd0YJdYVHV_rFx6VjZaxrrmKxSyoTFwY,2508 @@ -51,7 +59,7 @@ pyOpenRPA/Orchestrator/RobotRDPActive/__pycache__/Timer.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotRDPActive/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotRDPActive/__pycache__/__main__.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/ConsoleStart.bat,sha256=_HNadUKHOYI5y6foG3srh8wjSzhX33xaKNylFtDjOJk,114 -pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py,sha256=TV-YisVqa_uGiyJLG9oK4u-5aDjGiFYZFh1dPjOgYc8,492 +pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py,sha256=6gIiWFyacMvudv9t3D7jxz6uVHUt53b-SS3Ebqo2_lo,492 pyOpenRPA/Orchestrator/RobotScreenActive/Screen.py,sha256=VnYcvCVymrD35l2J4ln_tlVn7CilZhxE4Ggw9P-OhIw,606 pyOpenRPA/Orchestrator/RobotScreenActive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Orchestrator/RobotScreenActive/__main__.py,sha256=JASxDDVKWUU7DAbDkRrGTrPk-P7LZchTZFh8usp6b4U,593 @@ -59,21 +67,21 @@ pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/Monitor.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/Screen.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/__main__.cpython-37.pyc,, -pyOpenRPA/Orchestrator/Server.py,sha256=Ke89zh5iZezhA_qyQ3sfWJmL9bfc1rBBYeq-WzznfsE,30432 -pyOpenRPA/Orchestrator/ServerSettings.py,sha256=zVI-brV_58uKJ-MWESTZGYv89nN_0iW_-HfVNhip4jE,32890 -pyOpenRPA/Orchestrator/SettingsTemplate.py,sha256=-LIyHRKVnbrALAyss6r6L56jBX_yOAdMEhnj8N2fN9A,20532 +pyOpenRPA/Orchestrator/Server.py,sha256=DbvHZTTItOBbYiykn2GMjG2r6iUsXUqQZZjjnYPnZ8Q,32455 +pyOpenRPA/Orchestrator/ServerSettings.py,sha256=YaZem-osX1bD8gnJyuYU0PWDOnhqkacmXjXXHYLqW3g,31731 +pyOpenRPA/Orchestrator/SettingsTemplate.py,sha256=ij1quU5ENu43QjccHYsy8SwPCGibqJNGwcDaoF7cAPo,21340 pyOpenRPA/Orchestrator/Timer.py,sha256=HvYtEeH2Q5WVVjgds9XaBpWRmvZgwgBXurJDdVVq_T0,2097 pyOpenRPA/Orchestrator/Utils/LoggerHandlerDumpLogList.py,sha256=hD47TiOuKR-G8IWu9lJD2kG28qlH7YZV63i3qv1N5Dk,681 pyOpenRPA/Orchestrator/Utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Orchestrator/Utils/__pycache__/LoggerHandlerDumpLogList.cpython-37.pyc,, pyOpenRPA/Orchestrator/Utils/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Orchestrator/Web/Basic.py,sha256=pPH55rPwZz1ktpzNIcC51jeV2MgZI10Zf0Q0DncihDw,7757 -pyOpenRPA/Orchestrator/Web/Index.js,sha256=Blo3LHe_a3zrW7MqYo4BSIwoOx7nlO7Ko9LWxfeqU_o,39090 -pyOpenRPA/Orchestrator/Web/Index.xhtml,sha256=a4N_reLA6_Zb2KXiL73a7cWtJwO0W0Dr5lZ-RpUwuI0,16428 +pyOpenRPA/Orchestrator/Web/Index.js,sha256=YACiZAvjr6NmFlDhQu6urkJp49BX7L8WJU9p-MeIlCI,43508 +pyOpenRPA/Orchestrator/Web/Index.xhtml,sha256=XGPCG-qaFsAcoaXnZe1mEjPEqwcVQZ3NVPjtKX8gV4c,19192 pyOpenRPA/Orchestrator/Web/__pycache__/Basic.cpython-37.pyc,, pyOpenRPA/Orchestrator/Web/favicon.ico,sha256=6S8XwSQ_3FXPpaX6zYkf8uUewVXO9bHnrrDHEoWrEgw,112922 -pyOpenRPA/Orchestrator/__Orchestrator__.py,sha256=n-PrWqEDql1POF4bM-r9cTun9WZUqzIYRjxDEmV7xAM,119846 -pyOpenRPA/Orchestrator/__init__.py,sha256=f1RFDzOkL3IVorCtqogjGdXYPtHH-P-y-5CqT7PGy7A,183 +pyOpenRPA/Orchestrator/__Orchestrator__.py,sha256=wIRuMdoOwudlchdoMua0u2jO5AxCeATw0RSsQ9bKreo,150251 +pyOpenRPA/Orchestrator/__init__.py,sha256=nJhjYtBXKOUNX_yNu1rRFk5y9cDz6AFiL0M6KgX_utQ,207 pyOpenRPA/Orchestrator/__main__.py,sha256=czJrc7_57WiO3EPIYfPeF_LG3pZsQVmuAYgbl_YXcVU,273 pyOpenRPA/Orchestrator/__pycache__/BackwardCompatibility.cpython-37.pyc,, pyOpenRPA/Orchestrator/__pycache__/ControlPanel.cpython-37.pyc,, @@ -337,10 +345,10 @@ pyOpenRPA/Tools/SafeSource/__pycache__/Crypter.cpython-37.pyc,, pyOpenRPA/Tools/SafeSource/__pycache__/DistrCreate.cpython-37.pyc,, pyOpenRPA/Tools/SafeSource/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Tools/SafeSource/__pycache__/__main__.cpython-37.pyc,, -pyOpenRPA/Tools/Terminator.py,sha256=VcjX3gFXiCGu3MMCidhrTNsmC9wsAqfjRJdTSU9fLnU,2178 +pyOpenRPA/Tools/StopSafe.py,sha256=BNTtMmvsRE1Wtri3EkwhoBi6gGOjEPRQnJSV1C03c84,2176 pyOpenRPA/Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pyOpenRPA/Tools/__pycache__/Terminator.cpython-37.pyc,, +pyOpenRPA/Tools/__pycache__/StopSafe.cpython-37.pyc,, pyOpenRPA/Tools/__pycache__/__init__.cpython-37.pyc,, -pyOpenRPA/__init__.py,sha256=fI-2Npv3ZqIEhm1omXoocfYZw7PY1Ccf_pHXi_bvI0w,174 +pyOpenRPA/__init__.py,sha256=thBwsh1ouqe_mKoJCCECIcKbo7oF6WPz9ZV52uvuPQM,174 pyOpenRPA/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/test.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/REQUESTED b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/REQUESTED similarity index 100% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/REQUESTED rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/REQUESTED diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/WHEEL b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/WHEEL similarity index 100% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/WHEEL rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/WHEEL diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/top_level.txt b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/top_level.txt similarity index 100% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/top_level.txt rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/top_level.txt diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/O2A.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/O2A.py index 0994a0c0..853df131 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/O2A.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/O2A.py @@ -1,4 +1,5 @@ import requests, time, json +from . import Processor # O2A - Data flow Orchestrator to Agent # f"{lProtocolStr}://{lHostStr}:{lPortInt}/pyOpenRPA/Agent/O2A" @@ -20,6 +21,11 @@ def O2A_Loop(inGSettings): while inGSettings["O2ADict"]["IsOnlineBool"]: # Send request to the orchestrator server lRequestBody = None + # ConnectionError - CE + lCEPhaseFastTimeLastGoodFloat = time.time() + lCEPhaseFastDurationSecFloat = inGSettings['O2ADict']['ConnectionTimeoutSecFloat'] + lCEPhaseFastRetrySecFloat = inGSettings['O2ADict']['RetryTimeoutSecFloat']/5.0 + lCEPhaseLongRetrySecFloat = inGSettings['O2ADict']['RetryTimeoutSecFloat']*12.0 try: lProtocolStr= "https" if inGSettings["OrchestratorDict"]["IsHTTPSBool"] else "http" lHostStr = inGSettings["OrchestratorDict"]["HostStr"] @@ -27,6 +33,7 @@ def O2A_Loop(inGSettings): lURLStr=f"{lProtocolStr}://{lHostStr}:{lPortInt}/pyOpenRPA/Agent/O2A" lDataDict = { "HostNameUpperStr": inGSettings["AgentDict"]["HostNameUpperStr"], "UserUpperStr": inGSettings["AgentDict"]["UserUpperStr"], "ActivityLastGUIDStr": lActivityLastGUIDStr} lResponse = requests.post(url= lURLStr, cookies = {"AuthToken":inGSettings["OrchestratorDict"]["SuperTokenStr"]}, json=lDataDict, timeout=inGSettings["O2ADict"]["ConnectionTimeoutSecFloat"]) + lCEPhaseFastTimeLastGoodFloat = time.time() if lResponse.status_code != 200: if lL: lL.warning(f"Agent can not connect to Orchestrator. Below the response from the orchestrator:{lResponse}") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) @@ -34,30 +41,39 @@ def O2A_Loop(inGSettings): lRequestBody = lResponse.text lBodyLenInt = len(lRequestBody) if lBodyLenInt != 0: # CHeck if not empty result when close the connection from orch - lQueueItem = lResponse.json() # Try to get JSON - # Append QUEUE item in ProcessorDict > ActivityList - lActivityLastGUIDStr = lQueueItem["GUIDStr"] - inGSettings["ProcessorDict"]["ActivityList"].append(lQueueItem) - # Log full version if bytes size is less than limit . else short - lAgentLimitLogSizeBytesInt = 500 - if lBodyLenInt <= lAgentLimitLogSizeBytesInt: - if lL: lL.info(f"ActivityItem was received from orchestrator: {lQueueItem}"); - else: - if lL: lL.info(f"ActivityItem was received from orchestrator: Was supressed because of big size. Max is {lAgentLimitLogSizeBytesInt} bytes"); + lQueueList = lResponse.json() # Try to get JSON + for lQueueItem in lQueueList: + # Append QUEUE item in ProcessorDict > ActivityList + lActivityLastGUIDStr = lQueueItem["GUIDStr"] + # Check if ActivityItem ["ThreadBool"] = False > go sync mode in processor queue; Else: New thread + if lQueueItem.get("ThreadBool",False) == False: + inGSettings["ProcessorDict"]["ActivityList"].append(lQueueItem) + else: + Processor.ProcessorRunAsync(inGSettings=inGSettings,inActivityList=[lQueueItem]) + # Log full version if bytes size is less than limit . else short + lAgentLimitLogSizeBytesInt = 500 + if lBodyLenInt <= lAgentLimitLogSizeBytesInt: + if lL: lL.info(f"ActivityItem from orchestrator: {lQueueItem}"); + else: + if lL: lL.info(f"ActivityItem from orchestrator: Supressed - big size. Size is {lBodyLenInt} bytes"); else: if lL: lL.debug(f"Empty response from the orchestrator - loop when refresh the connection between Orc and Agent"); except requests.exceptions.ConnectionError as e: - if lL: lL.error(f"O2A Connection error - orchestrator is not available. Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") - time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) + if time.time() - lCEPhaseFastTimeLastGoodFloat <= lCEPhaseFastDurationSecFloat: + if lL: lL.error(f"O2A Connection error - orchestrator is not available. Sleep for {lCEPhaseFastRetrySecFloat} s.") + time.sleep(lCEPhaseFastRetrySecFloat) + else: + if lL: lL.error(f"O2A Connection error - orchestrator is not available. Sleep for {lCEPhaseLongRetrySecFloat} s.") + time.sleep(lCEPhaseLongRetrySecFloat) except ConnectionResetError as e: - if lL: lL.error(f"O2A Connection reset error - orchestrator is not available. Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") + if lL: lL.error(f"O2A Connection reset error - orchestrator is not available. Sleep for {inGSettings['O2ADict']['RetryTimeoutSecFloat']} s.") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) except json.decoder.JSONDecodeError as e: if lL: lL.error(f"O2A JSON decode error - See body of the recieved content from the Orchestrator: {lRequestBody}") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) except requests.exceptions.Timeout as e: - if lL: lL.exception(f"O2A requests timeout error (no response for long time). Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") + if lL: lL.exception(f"O2A requests timeout error (no response for long time). Sleep for {inGSettings['O2ADict']['RetryTimeoutSecFloat']} s.") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) except Exception as e: - if lL: lL.exception(f"O2A Error handler. Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") + if lL: lL.exception(f"O2A Error handler. Sleep for {inGSettings['O2ADict']['RetryTimeoutSecFloat']} s.") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/Processor.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/Processor.py index a96a973f..0fe9a965 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/Processor.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/Processor.py @@ -13,6 +13,7 @@ def ProcessorRunSync(inGSettings): # "ArgGSettings": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) # "ArgLogger": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) # "GUIDStr": "sadasd-asdas-d-asdasd", # ActivityItem GUID which identify the Activity + # "ThreadBool": False # }, ], "AliasDefDict": {}, # Storage for def with Str alias. To use it see pyOpenRPA.Orchestrator.ControlPanel @@ -36,6 +37,35 @@ def ProcessorRunSync(inGSettings): else: time.sleep(inGSettings["ProcessorDict"]["CheckIntervalSecFloat"]) # Sleep when list is empty +# Run processor Async +def ProcessorRunAsync(inGSettings, inActivityList): + """ + "inActivityList": [ # List of the activities + # { + # "Def":"DefAliasTest", # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + # "ArgList":[1,2,3], # Args list + # "ArgDict":{"ttt":1,"222":2,"dsd":3}, # Args dictionary + # "ArgGSettings": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "ArgLogger": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "GUIDStr": "sadasd-asdas-d-asdasd", # ActivityItem GUID which identify the Activity + # "ThreadBool": True + # }, + """ + def __process__(inGSettings, inActivityList): + for lActivityItem in inActivityList: + lL = inGSettings["Logger"] # Logger alias + if lL: lL.debug(f'ActivityItem in new thread') + lResultList = ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item + #Some help code + if len(lResultList) == 0: lResultList= [None] + # Send result to Orc if we have GUIDStr + if "GUIDStr" in lActivityItem: + # Def to send to Orc + A2O.ActivityReturnDictSend(inGSettings=inGSettings, inActivityItemGUIDStr=lActivityItem["GUIDStr"],inReturn=lResultList[0]) + # Start in new thread + lThread = threading.Thread(target=__process__,kwargs={"inGSettings": inGSettings, "inActivityList": inActivityList}) + lThread.start() + # Execute ActivityItem list # return the def result def ActivityListExecute(inGSettings, inActivityList): diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py index 9ac93c19..f7d448de 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py @@ -2,6 +2,9 @@ import threading, socket, getpass, sys, uuid, subprocess, base64, psutil, getpas from . import O2A, A2O # Data flow Orchestrator To Agent from . import Processor # Processor Queue from subprocess import CREATE_NEW_CONSOLE # Flag to create new process in another CMD +import os + +gSettings = None # Create binary file by the base64 string (safe for JSON transmition) def OSFileBinaryDataBase64StrCreate(inFilePathStr, inFileDataBase64Str,inGSettings = None): @@ -58,16 +61,37 @@ def OSFileBinaryDataBase64StrReceive(inFilePathStr, inGSettings=None): :param inGSettings: global settings of the Agent (singleton) :return: File content in string base64 format (use base64.b64decode to decode data). Return None if file is not exist """ - lFile = open(inFilePathStr, "rb") - lFileDataBytes = lFile.read() - lFile.close() - lFileDataBase64Str = base64.b64encode(lFileDataBytes).decode("utf-8") lL = inGSettings.get("Logger", None) if type(inGSettings) is dict else None - lMessageStr = f"AGENT Binary file {inFilePathStr} has been read." - if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + lFileDataBase64Str = None + if os.path.exists(inFilePathStr): + lFile = open(inFilePathStr, "rb") + lFileDataBytes = lFile.read() + lFile.close() + lFileDataBase64Str = base64.b64encode(lFileDataBytes).decode("utf-8") + lMessageStr = f"OSFileBinaryDataBase64StrReceive: file {inFilePathStr} has been read" + if lL: lL.debug(lMessageStr) + #A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + else: + if lL: lL.debug(f"OSFileBinaryDataBase64StrReceive: file {inFilePathStr} is not exists - return None") return lFileDataBase64Str +def OSFileMTimeGet(inFilePathStr: str) -> float or None: + """ + Read file modification time timestamp format (float) + + :param inFilePathStr: File path to read + :return: timestamp (float) Return None if file is not exist + """ + global gSettings + lL = gSettings.get("Logger", None) if type(gSettings) is dict else None + lFileMTimeFloat = None + if os.path.exists(inFilePathStr): + lFileMTimeFloat = os.path.getmtime(inFilePathStr) + if lL: lL.debug(f"OSFileMTimeGet: file {inFilePathStr} has been read") + else: + if lL: lL.debug(f"OSFileMTimeGet: file {inFilePathStr} is not exists - return None") + return lFileMTimeFloat + def OSFileTextDataStrReceive(inFilePathStr, inEncodingStr="utf-8", inGSettings=None): """ Read text file in the agent GUI session @@ -77,17 +101,21 @@ def OSFileTextDataStrReceive(inFilePathStr, inEncodingStr="utf-8", inGSettings=N :param inGSettings: global settings of the Agent (singleton) :return: File text content in string format (use base64.b64decode to decode data). Return None if file is not exist """ - lFile = open(inFilePathStr, "r", encoding=inEncodingStr) - lFileDataStr = lFile.read() - lFile.close() + lFileDataStr = None lL = inGSettings.get("Logger", None) if type(inGSettings) is dict else None - lMessageStr = f"AGENT Text file {inFilePathStr} has been read." - if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if os.path.exists(inFilePathStr): + lFile = open(inFilePathStr, "r", encoding=inEncodingStr) + lFileDataStr = lFile.read() + lFile.close() + lMessageStr = f"OSFileTextDataStrReceive: file {inFilePathStr} has been read" + if lL: lL.info(lMessageStr) + #A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + else: + if lL: lL.info(f"OSFileTextDataStrReceive: file {inFilePathStr} is not exists - return None") return lFileDataStr # Send CMD to OS. Result return to log + Orchestrator by the A2O connection -def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251"): +def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251", inCaptureBool = True): """ Execute CMD on the Agent daemonic process @@ -95,18 +123,21 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inGSettings: Agent global settings dict :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True - !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: """ lResultStr = "" + # New feature + if inSendOutputToOrchestratorLogsBool == False and inCaptureBool == False: + inCMDStr = f"start {inCMDStr}" # Subdef to listen OS result - def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None): + def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None, inCaptureBool = True): lL = inGSettings.get("Logger",None) if type(inGSettings) is dict else None lResultStr = "" lOSCMDKeyStr = str(uuid.uuid4())[0:4].upper() lCMDProcess = None - if inSendOutputToOrchestratorLogsBool == True: + if inCaptureBool == True: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=None, stderr=None, @@ -114,12 +145,15 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche lListenBool = True lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been STARTED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) lMessageStr = f"{lOSCMDKeyStr}: {inCMDStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) while lListenBool: - if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + #if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + if inCaptureBool == True: # Capturing can be turned on! lOutputLineBytes = lCMDProcess.stdout.readline() if lOutputLineBytes == b"": lListenBool = False @@ -127,7 +161,8 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche if lStr.endswith("\n"): lStr = lStr[:-1] lMessageStr = f"{lOSCMDKeyStr}: {lStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) lResultStr+=lStr else: #Capturing is not turned on - wait until process will be closed lCMDProcessPoll = lCMDProcess.poll() @@ -137,15 +172,16 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche lListenBool = False lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been FINISHED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) return lResultStr # New call if inRunAsyncBool: - lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr }) + lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr, "inCaptureBool": inCaptureBool }) lThread.start() lResultStr="ActivityList has been started in async mode - no output is available here." else: - lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr) + lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr, inCaptureBool=inCaptureBool) #lCMDCode = "cmd /c " + inCMDStr #subprocess.Popen(lCMDCode) #lResultCMDRun = 1 # os.system(lCMDCode) @@ -180,7 +216,8 @@ def ProcessWOExeUpperUserListGet(): # Main def def Agent(inGSettings): lL = inGSettings["Logger"] - + global gSettings + gSettings = inGSettings # Append Orchestrator def to ProcessorDictAlias lModule = sys.modules[__name__] lModuleDefList = dir(lModule) diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py index 727376ee..b8f7052b 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py @@ -2,7 +2,7 @@ # !!! ATTENTION: Backward compatibility has been started from v1.1.13 !!! # So you can use config of the orchestrator 1.1.13 in new Orchestrator versions and all will be ok :) (hope it's true) import win32security, json, datetime, time, copy - +import schedule # # # # # # # # # # # # # # # # # # # # Backward compatibility Web defs up to v1.2.0 # # # # # # # # # # # # # # # # # # # @@ -488,4 +488,48 @@ def Update(inGSettings): if "AgentLimitLogSizeBytesInt" not in inGSettings["ServerDict"]: inGSettings["ServerDict"]["AgentLimitLogSizeBytesInt"] = 300 if lL: lL.warning( - f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > AgentLimitLogSizeBytesInt") # Log about compatibility \ No newline at end of file + f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > AgentLimitLogSizeBytesInt") # Log about compatibility + # Remove ControlPanelDict and CPDict > go to ServerDict > ControlPanelDict + if "ControlPanelDict" in inGSettings: + del inGSettings["ControlPanelDict"] + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Remove old key: ControlPanelDict") # Log about compatibility + if "CPDict" in inGSettings: + for lCPKeyStr in inGSettings["CPDict"]: + lCPItemDict = inGSettings["CPDict"][lCPKeyStr] + __Orchestrator__.WebCPUpdate(inCPKeyStr=lCPKeyStr,inHTMLRenderDef=lCPItemDict["HTMLRenderDef"], + inJSONGeneratorDef=lCPItemDict["JSONGeneratorDef"], + inJSInitGeneratorDef=lCPItemDict["JSInitGeneratorDef"]) + del inGSettings["CPDict"] + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Remove old key: CPDict") # Log about compatibility + if "ControlPanelDict" not in inGSettings["ServerDict"]: + inGSettings["ServerDict"]["ControlPanelDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ServerDict > ControlPanelDict") # Log about compatibility + # ManagersProcessDict + if "ManagersProcessDict" not in inGSettings: + inGSettings["ManagersProcessDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ManagersProcessDict") # Log about compatibility + # Check "SchedulerDict": { "Schedule": schedule, # https://schedule.readthedocs.io/en/stable/examples.html + if inGSettings.get("SchedulerDict",{}).get("Schedule",None) is None: + inGSettings["SchedulerDict"]["Schedule"] = schedule + if lL: lL.warning(f"Backward compatibility (v1.2.4 to v1.2.7): Create new module schedule (schedule.readthedocs.io)") # Log about compatibility + # ManagersGitDict + if "ManagersGitDict" not in inGSettings: + inGSettings["ManagersGitDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ManagersGitDict") # Log about compatibility + # ProcessorDict > ActivityItemNowDict + if "ActivityItemNowDict" not in inGSettings["ProcessorDict"]: + inGSettings["ProcessorDict"]["ActivityItemNowDict"]=None + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ProcessorDict > ActivityItemNowDict") # Log about compatibility + + # # "UACBool": True # True - check user access before do this URL item + for lURLItemDict in inGSettings["ServerDict"]["URLList"]: + if "UACBool" not in lURLItemDict: + lURLItemDict["UACBool"]=None + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): ServerDict > URLList > item: add UACBool = None") # Log about compatibility diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py index 329386fe..f92f7442 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py @@ -4,7 +4,7 @@ import threading def IsProcessorThread(inGSettings): return inGSettings["ProcessorDict"]["ThreadIdInt"] == threading.get_ident() -def IsOrchestratorInitialized(inGSettings): +def IsOrchestratorInitialized(inGSettings) -> bool: """ Check if Orchestrator will be successfully initialized diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py new file mode 100644 index 00000000..cef4acd4 --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py @@ -0,0 +1,348 @@ +from ... import Orchestrator +import jinja2 +import os +from inspect import signature # For detect count of def args +from ..Web import Basic +import operator +import math + +class ControlPanel(): + """ + Manage your control panel on the orchestrator + + Control panel has 3 events types: + - onRefreshHTML - run every n (see settings) second to detect changes in HTML control panel. + - onRefreshJSON - run every n (see settings) second to detect changes in JSON data container to client side. + - onInitJS - run when client reload the Orchestrator web page + + .. code-block:: python + + # Usage example: + lCPManager = Orchestrator.Managers.ControlPanel(inControlPanelNameStr="TestControlPanel", + inRefreshHTMLJinja2TemplatePathStr="ControlPanel\\test.html", inJinja2TemplateRefreshBool = True) + + + + If you use Jinja2 you can use next data context: + StorageRobotDict: Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr), + ControlPanelInstance: self, + OrchestratorModule:Orchestrator, + RequestInstance: inRequest, + UserInfoDict: Orchestrator.WebUserInfoGet(inRequest=inRequest), + UserUACDict: Orchestrator.UACUserDictGet(inRequest=inRequest), + UserUACCheckDef: inRequest.UACClientCheck, + EnumerateDef: enumerate, + OperatorModule: operator, + MathModule: math + + You can modify jinja context by use the function: + Jinja2DataUpdateDictSet + + .. code-block:: html + Hello my control panel! + You can use any def from Orchestrator module here in Jinja2 HTML template: + Example: OrchestratorModule.OSCMD(inCMDStr="notepad") + {{MathModule.pi}} + {% if UserInfoDict['UserNameUpperStr']=="ND" %} + YES - IT IS ND + {% endif %} + + """ + mControlPanelNameStr = None + # Jinja2 consolidated + mJinja2TemplateRefreshBool = None + mJinja2DataUpdateDict = None + + # RefreshHTML block + mRefreshHTMLJinja2TemplatePathStr = None + mRefreshHTMLJinja2TemplateFileNameStr = None + mRefreshHTMLJinja2Loader = None + mRefreshHTMLJinja2Env = None + mRefreshHTMLJinja2Template = None + + # InitJS block + mInitJSJinja2TemplatePathStr = None + mInitJSJinja2TemplateFileNameStr = None + mInitJSJinja2Loader = None + mInitJSJinja2Env = None + mInitJSJinja2Template = None + + mBackwardCompatibilityHTMLDef = None + mBackwardCompatibilityJSDef = None + mBackwardCompatibilityJSONDef = None + + mRobotNameStr = None + + def __init__(self, inControlPanelNameStr, inRefreshHTMLJinja2TemplatePathStr = None, inJinja2TemplateRefreshBool = False, inRobotNameStr = None): + """ + Constructor of the control panel manager + + :param inControlPanelNameStr: + :param inJinja2TemplatePathStr: + """ + # Connect self witch pyOpenRPA via ControlPanelNameStr + if inControlPanelNameStr in Orchestrator.GSettingsGet()["ServerDict"]["ControlPanelDict"]: + raise Exception(f"Another control panel with name {inControlPanelNameStr} is already exists. Please resolve the error and restart") + Orchestrator.GSettingsGet()["ServerDict"]["ControlPanelDict"][inControlPanelNameStr] = self + self.RefreshHTMLJinja2TemplatePathSet(inJinja2TemplatePathStr = inRefreshHTMLJinja2TemplatePathStr) + self.mJinja2TemplateRefreshBool = inJinja2TemplateRefreshBool + self.mControlPanelNameStr = inControlPanelNameStr # Set the name of the control panel + self.mRobotNameStr = inRobotNameStr # Set the robot name for robot it execute + + def Jinja2DataUpdateDictSet(self, inJinja2DataUpdateDict): + """ + Set the data dict from the Jinja2 context (you can add some new params) + + :param inJinja2DataUpdateDict: dict, which will be appended to main data context + :return: None + """ + self.mJinja2DataUpdateDict = inJinja2DataUpdateDict + + def RefreshHTMLJinja2TemplatePathSet(self, inJinja2TemplatePathStr): + """ + Create Jinja2 env and load the template html + + :param inJinja2TemplatePathStr: + :return: + """ + try: + if inJinja2TemplatePathStr is not None: + lSystemLoaderPathStr = "/".join(inJinja2TemplatePathStr.split("\\")[0:-1]) + lTemplateFileNameStr = inJinja2TemplatePathStr.split("\\")[-1] + self.mRefreshHTMLJinja2TemplateFileNameStr = lTemplateFileNameStr + self.mRefreshHTMLJinja2Loader = jinja2.FileSystemLoader(lSystemLoaderPathStr) + self.mRefreshHTMLJinja2Env = jinja2.Environment(loader=self.mRefreshHTMLJinja2Loader, trim_blocks=True) + self.mRefreshHTMLJinja2Template = self.mRefreshHTMLJinja2Env.get_template(lTemplateFileNameStr) + except Exception as e: + Orchestrator.OrchestratorLoggerGet().exception("EXCEPTION WHEN INIT Jinja2") + + def RefreshHTMLJinja2StrGenerate(self, inDataDict): + """ + Generate the HTML str from the Jinja2. Pass the context inDataDict + :param inDataDict: + :return: + """ + if self.mJinja2TemplateRefreshBool == True: + self.mRefreshHTMLJinja2Template = self.mRefreshHTMLJinja2Env.get_template(self.mRefreshHTMLJinja2TemplateFileNameStr) + lHTMLStr = self.mRefreshHTMLJinja2Template.render(**inDataDict) # Render the template into str + return lHTMLStr + + def InitJSJinja2TemplatePathSet(self, inJinja2TemplatePathStr): + """ + Create Jinja2 env and load the template html + + :param inJinja2TemplatePathStr: + :return: + """ + try: + if inJinja2TemplatePathStr is not None: + lSystemLoaderPathStr = "/".join(inJinja2TemplatePathStr.split("\\")[0:-1]) + lTemplateFileNameStr = inJinja2TemplatePathStr.split("\\")[-1] + self.mInitJSJinja2TemplateFileNameStr = lTemplateFileNameStr + self.mInitJSJinja2Loader = jinja2.FileSystemLoader(lSystemLoaderPathStr) + self.mInitJSJinja2Env = jinja2.Environment(loader=self.mInitJSJinja2Loader, trim_blocks=True) + self.mInitJSJinja2Template = self.mInitJSJinja2Env.get_template(lTemplateFileNameStr) + except Exception as e: + Orchestrator.OrchestratorLoggerGet().exception("EXCEPTION WHEN INIT Jinja2") + + def InitJSJinja2StrGenerate(self, inDataDict): + """ + Generate the HTML str from the Jinja2. Pass the context inDataDict + :param inDataDict: + :return: + """ + if self.mJinja2TemplateRefreshBool == True: + self.mInitJSJinja2Template = self.mInitJSJinja2Env.get_template(self.mInitJSJinja2TemplateFileNameStr) + lHTMLStr = self.mInitJSJinja2Template.render(**inDataDict) # Render the template into str + return lHTMLStr + + def DataDictGenerate(self, inRequest): + """ + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: + """ + lData = { + "StorageRobotDict": None, + "ControlPanelInstance":self, + "OrchestratorModule":Orchestrator, + "RequestInstance": inRequest, + "UserInfoDict": Orchestrator.WebUserInfoGet(inRequest=inRequest), + "UserUACDict": Orchestrator.UACUserDictGet(inRequest=inRequest), + "UserUACCheckDef": inRequest.UACClientCheck, + "EnumerateDef": enumerate, + "OperatorModule": operator, + "MathModule": math + } + # Get the robot storage by the robot name (if you set robot name when init) + if self.mRobotNameStr is not None: + lData["StorageRobotDict"] = Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr) + # Checkj Jinja2DataUpdateDict + if self.mJinja2DataUpdateDict is not None: + lData.update(self.mJinja2DataUpdateDict) + return lData + + def OnRefreshHTMLStr(self, inRequest): + """ + Event to generate HTML code of the control panel when refresh time is over. + Support backward compatibility for previous versions. + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: + """ + lHTMLStr = None + lL = Orchestrator.OrchestratorLoggerGet() + if self.mBackwardCompatibilityHTMLDef is None: + if self.mRefreshHTMLJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mRefreshHTMLJinja2TemplateFileNameStr is not None): + lDataDict = self.OnRefreshHTMLDataDict(inRequest = inRequest) + # Jinja code + lHTMLStr = self.RefreshHTMLJinja2StrGenerate(inDataDict=lDataDict) + else: + lHTMLStr = self.BackwardAdapterHTMLDef(inRequest=inRequest) + # return the str + return lHTMLStr + + def OnRefreshHTMLDataDict(self, inRequest): + """ + Event to prepare data context for the futher Jinja2 HTML generation. You can override this def if you want some thing more data + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: dict + """ + return self.DataDictGenerate(inRequest=inRequest) + + def OnRefreshHTMLHashStr(self, inRequest): + """ + Generate the hash the result output HTML. You can override this function if you know how to optimize HTML rendering. + TODO NEED TO MODIFY ServerSettings to work with Hash because of all defs are need do use Hash + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: None - default, hash function is not determined. Str - hash function is working on! + """ + return None + + def OnRefreshJSONDict(self, inRequest): + """ + Event to transmit some data from server side to the client side in JSON format. Call when page refresh is initialized + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: Dict type + """ + lResultDict = None + if self.mBackwardCompatibilityJSONDef is None: + pass + else: + lResultDict = self.BackwardAdapterJSONDef(inRequest=inRequest) + return lResultDict + + def OnInitJSStr(self, inRequest): + """ + Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once. + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: "" + """ + lJSStr = "" + if self.mBackwardCompatibilityJSDef is None: + if self.mInitJSJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mInitJSJinja2TemplateFileNameStr is not None): + lDataDict = self.OnInitJSDataDict(inRequest = inRequest) + # Jinja code + lJSStr = self.InitJSJinja2StrGenerate(inDataDict=lDataDict) + else: + lJSStr = self.BackwardAdapterJSDef(inRequest=inRequest) + return lJSStr + + def OnInitJSDataDict(self, inRequest): + """ + Event to prepare data context for the futher Jinja2 JS init generation. You can override this def if you want some thing more data + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: dict + """ + return self.DataDictGenerate(inRequest=inRequest) + + def BackwardAdapterHTMLDef(self,inRequest): + lGS = Orchestrator.GSettingsGet() + lL = Orchestrator.OrchestratorLoggerGet() + # HTMLRenderDef + lItemHTMLRenderDef = self.mBackwardCompatibilityHTMLDef + lResultStr = "" + if lItemHTMLRenderDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) + lHTMLResult = None + lDEFSignature = signature(lItemHTMLRenderDef) # Get signature of the def + lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args + try: + if lDEFARGLen == 1: # def (inGSettings) + lHTMLResult = lItemHTMLRenderDef(lGS) + elif lDEFARGLen == 2: # def (inRequest, inGSettings) + lHTMLResult = lItemHTMLRenderDef(inRequest, lGS) + elif lDEFARGLen == 0: # def () + lHTMLResult = lItemHTMLRenderDef() + # RunFunction + # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" + if type(lHTMLResult) is str: + lResultStr = lHTMLResult + elif "HTMLStr" in lHTMLResult or "JSONDict" in lHTMLResult: + lResultStr = lHTMLResult["HTMLStr"] + else: + # Call backward compatibility HTML generator + lResultStr = Basic.HTMLControlPanelBC(inCPDict=lHTMLResult) + except Exception as e: + if lL: lL.exception(f"Error in control panel HTMLRenderDef. CP Key {self.mControlPanelNameStr}. Exception are below") + return lResultStr + + + def BackwardAdapterJSONDef(self,inRequest): + lGS = Orchestrator.GSettingsGet() + lL = Orchestrator.OrchestratorLoggerGet() + # HTMLRenderDef + lItemJSONGeneratorDef = self.mBackwardCompatibilityJSONDef + lResultDict = {} + if lItemJSONGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) + lJSONResult = None + lDEFSignature = signature(lItemJSONGeneratorDef) # Get signature of the def + lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args + try: + if lDEFARGLen == 1: # def (inGSettings) + lJSONResult = lItemJSONGeneratorDef(lGS) + elif lDEFARGLen == 2: # def (inRequest, inGSettings) + lJSONResult = lItemJSONGeneratorDef(inRequest, lGS) + elif lDEFARGLen == 0: # def () + lJSONResult = lItemJSONGeneratorDef() + # RunFunction + # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" + lType = type(lJSONResult) + if lType is str or lJSONResult is None or lType is int or lType is list or lType is dict or lType is bool or lType is float: + lResultDict = lJSONResult + else: + if lL: lL.warning(f"JSONGenerator return bad type: {str(type(lJSONResult))}, CP Key {self.mControlPanelNameStr}") + except Exception as e: + if lL: lL.exception( + f"Error in control panel JSONGeneratorDef. CP Key {self.mControlPanelNameStr}. Exception are below") + return lResultDict + + def BackwardAdapterJSDef(self,inRequest): + lGS = Orchestrator.GSettingsGet() + lL = Orchestrator.OrchestratorLoggerGet() + # HTMLRenderDef + lJSInitGeneratorDef = self.mBackwardCompatibilityJSDef + lResultStr = "" + if lJSInitGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) + lJSResult = "" + lDEFSignature = signature(lJSInitGeneratorDef) # Get signature of the def + lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args + try: + if lDEFARGLen == 1: # def (inGSettings) + lJSResult = lJSInitGeneratorDef(lGS) + elif lDEFARGLen == 2: # def (inRequest, inGSettings) + lJSResult = lJSInitGeneratorDef(inRequest, lGS) + elif lDEFARGLen == 0: # def () + lJSResult = lJSInitGeneratorDef() + if type(lJSResult) is str: + lResultStr = lJSResult # Add delimiter to some cases + else: + if lL: lL.warning(f"JSInitGenerator return bad type: {str(type(lJSResult))}, CP Key {self.mControlPanelNameStr}") + except Exception as e: + if lL: lL.exception( + f"Error in control panel JSInitGeneratorDef. CP Key {self.mControlPanelNameStr}. Exception are below") + return lResultStr \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py new file mode 100644 index 00000000..7676f6bd --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py @@ -0,0 +1,249 @@ +import time +import os +from .. import __Orchestrator__ +from . import Process +import threading +from typing import List +from typing import Tuple + +from pyOpenRPA import Orchestrator + +class Git(): + + mAgentHostNameStr = None + mAgentUserNameStr = None + mAbsPathStr = None + mProcessList: List[Tuple] = [] # List of the key turples of the Process instance + + def __init__(self, inAgentHostNameStr=None, inAgentUserNameStr=None, inGitPathStr=""): + """ + Init the Git repo instance. It helps to detect new changes in repo and auto restart services + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process. If None - works with Orc session + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process. If None - works with Orc session + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: + """ + lAbsPathStr = os.path.abspath(inGitPathStr) + lAbsPathUpperStr = lAbsPathStr.upper() + lGS = __Orchestrator__.GSettingsGet() + # Check if Process is not exists in GSettings + if (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr) not in lGS["ManagersGitDict"]: + self.mAbsPathStr = lAbsPathStr + self.mAbsPathUpperStr = lAbsPathUpperStr + self.mAgentHostNameStr = inAgentHostNameStr + self.mAgentUserNameStr = inAgentUserNameStr + lGS["ManagersGitDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr)]=self + else: raise Exception(f"Managers.Git ({inAgentHostNameStr}, {inAgentUserNameStr}, {lAbsPathUpperStr}): Can't init the Git instance because it already inited in early") + + def ProcessConnect(self, inProcess: Process): + """ + Connect process to the Git instance. It will apply to stop safe process when upgrade the repo and than start it + + :param inProcess: Process instance + :type inProcess: Process + """ + lProcessTurple = inProcess.KeyTurpleGet() + if lProcessTurple not in self.mProcessList: + self.mProcessList.append(lProcessTurple) + else: + raise Exception(f"Process with current key is already exists in Git process list.") + + def ProcessListSaveStopSafe(self): + """ + Save the state and do the stop safe for the all processes + Will send safe stop in parallel mode but wait to the end of the safestop for the all processes. After that will continue + """ + lIntervalScheckSecFloat = 5.0 + lThreadList:List[threading.Thread] = [] + for lProcessItemTuple in self.mProcessList: + lProcessItem = Orchestrator.Managers.ProcessGet(*lProcessItemTuple) + lProcessItem.StatusSave() + lThread = threading.Thread(target=lProcessItem.StopSafe) + lThread.start() + lThreadList.append(lThread) + # Wait for all process will be safe stopped + lAllThreadStoppedBool = False + while not lAllThreadStoppedBool: + lAllThreadStoppedBool = True + for lThread in lThreadList: + if lThread.is_alive() == True: + lAllThreadStoppedBool = False + break + time.sleep(lIntervalScheckSecFloat) + + def ProcessListRestore(self): + """ + Restore the process state for the all processes + """ + for lProcessItem in self.mProcessList: + lProcessItem.StatusRestore() + + def __OSCMDShell__(self, inCMDStr): + """ + Detect the way of use and send the cmd. Wait for command execution! + + :return: None is not exists + """ + if self.mAgentUserNameStr is not None and self.mAgentHostNameStr is not None: # Check if Agent specified + lActivityItemGUIDStr = __Orchestrator__.AgentOSCMD(inHostNameStr=self.mAgentHostNameStr,inUserStr=self.mAgentUserNameStr,inCMDStr=inCMDStr,inRunAsyncBool=False,inSendOutputToOrchestratorLogsBool=False) + lCMDResultStr = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lActivityItemGUIDStr) + else: + lCMDResultStr = __Orchestrator__.OSCMD(inCMDStr=inCMDStr, inRunAsyncBool=False) + return lCMDResultStr + + def BranchRevGet(self, inBranchNameStr="HEAD"): + """ + Get the specified branch revision. Default return the current branch revision + + .. code-block:: python + lGit.BranchRevGet(inBranchNameStr="dev") # Get revision of the local dev branch + lGit.BranchRevGet(inBranchNameStr="remotes/origin/dev") # Get revision of the remotes dev branch + lGit.BranchRevGet(inBranchNameStr="HEAD") # Get revision of the current HEAD branch + lGit.BranchRevGet() # Equal to the call inBranchNameStr="HEAD" + + :param inBranchNameStr: The branch name where to get revision guid + :return: revision GUID + """ + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git rev-parse {inBranchNameStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def BranchRevIsLast(self, inBranchLocalStr: str, inBranchRemoteStr: str) -> bool: + """Get fetch and check if local branch revision is last (if check with remote) + + :param inBranchLocalStr: _description_ + :type inBranchLocalStr: str + :param inBranchRemoteStr: example: origin/prd + :type inBranchRemoteStr: str + :return: _description_ + :rtype: bool + """ + lIsLastBool = False + self.Fetch() + lLocalBranchRevStr = self.BranchRevGet(inBranchNameStr=inBranchLocalStr) + lRemoteBranchRevStr = self.BranchRevGet(inBranchNameStr=inBranchRemoteStr) + if lLocalBranchRevStr == lRemoteBranchRevStr: + lIsLastBool = True + return lIsLastBool + + + def BranchRevLastGetInterval(self, inBranchLocalStr: str, inBranchRemoteStr: str, inPreviousBranchRestoreBool: bool = True, inIntervalSecFloat: float = 60.0): + """Periodically check if revision is last + + :param inBranchLocalStr: _description_ + :type inBranchLocalStr: str + :param inBranchRemoteStr: example: origin/prd + :type inBranchRemoteStr: str + :param inPreviousBranchRestoreBool: _description_, defaults to True + :type inPreviousBranchRestoreBool: bool, optional + :param inIntervalSecFloat: _description_, defaults to 60.0 + :type inIntervalSecFloat: float, optional + """ + #self.BranchRevLastGet(inBranchLocalStr, inBranchRemoteStr, inPreviousBranchRestoreBool) + Orchestrator.OrchestratorScheduleGet().every(inIntervalSecFloat).seconds.do(self.BranchRevLastGet, inBranchLocalStr, inBranchRemoteStr, inPreviousBranchRestoreBool) + + def BranchRevLastGet(self, inBranchLocalStr: str, inBranchRemoteStr: str, inPreviousBranchRestoreBool: bool = True): + """Do some action to get the last revision + + :param inBranchLocalStr: [description] + :type inBranchLocalStr: str + :param inBranchRemoteStr: [description] + :type inBranchRemoteStr: str + """ + Orchestrator.OrchestratorLoggerGet().debug(f"Managers.Git ({self.mAbsPathStr}): self.BranchRevLastGet has been init") + # check if the correct revision + lCMDResultStr = None + if self.BranchRevIsLast(inBranchLocalStr=inBranchLocalStr, inBranchRemoteStr=inBranchRemoteStr) == False: + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Git ({self.mAbsPathStr}): self.BranchRevLastGet, new rev (branch: {inBranchLocalStr}) has been detected - merge (branch: {inBranchRemoteStr})") + # Do the stop safe for the connected process + self.ProcessListSaveStopSafe() + lBranchNameCurrentStr = self.BranchNameGet() + # reset all changes in local folder + self.Clear() + # checkout + self.BranchCheckout(inBranchNameStr=inBranchLocalStr) + # merge + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git merge {inBranchRemoteStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + if inPreviousBranchRestoreBool == True: + # checkout to the source branch which was + self.BranchCheckout(inBranchNameStr=lBranchNameCurrentStr) + # do the orc restart + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Git ({self.mAbsPathStr}): self.BranchRevLastGet, merge done, restart orc") + Orchestrator.OrchestratorRestart() + return lCMDResultStr + + def BranchNameGet(self) -> str: + """Get the current local branch name + + :return: current local branch name + """ + #"git rev-parse --abbrev-ref HEAD" + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git rev-parse --abbrev-ref HEAD" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def BranchCheckout(self, inBranchNameStr): + self.Clear() + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git checkout {inBranchNameStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def Clear(self): + """Clear the all changes in the local folder. Get up to the current revision + """ + # f"git clean -f -d" # Очистить от лишних файлов + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git clean -f -d" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + # f"git reset --hard" # Откатить файлы, которые отслеживаются Git и которые были изменены + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git reset --hard" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def Fetch(self): + """ + Get updates from the git server. + + .. code-block:: python + lGit.Fetch() # get updates from the server + + :return: None + """ + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git fetch" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + +def GitExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> bool: + """ + Check if the Git instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: True - process exists in gsettings; False - else + """ + return (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inGitPathStr.upper()) in __Orchestrator__.GSettingsGet()["ManagersGitDict"] + + +def GitGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> Git: + """ + Return the Git instance by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: Git instance (if exists) Else None + """ + lAbsPathUpperStr = os.path.abspath(inGitPathStr).upper() + return __Orchestrator__.GSettingsGet()["ManagersGitDict"].get((inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr),None) + + +def GitBranchRevGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str, inBranchNameStr: str="HEAD") -> str: + lGit = GitGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inGitPathStr=inGitPathStr) + if lGit is not None: return lGit.BranchRevGet(inBranchNameStr=inBranchNameStr) + +def GitFetch(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> None: + lGit = GitGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inGitPathStr=inGitPathStr) + if lGit is not None: lGit.Fetch() \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py new file mode 100644 index 00000000..276e4adc --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py @@ -0,0 +1,692 @@ +#from pyOpenRPA.Orchestrator import Managers +from .. import __Orchestrator__ +import os +import time + +from pyOpenRPA import Orchestrator +class Process(): + """ + Manager process, which is need to be started / stopped / restarted + + With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not. + + All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . + After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet + + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + + .. code-block:: python + # For the safe init class use ProcessInitSafe + lProcess = Orchestrator.Managers.ProcessInitSafe(inAgentHostNameStr="PCNAME",inAgentUserNameStr="USER", + inProcessNameWOExeStr="notepad",inStartCMDStr="notepad",inStopSafeTimeoutSecFloat=3) + # Async way to run job + lProcess.ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=5) + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(Orchestrator.OrchestratorThreadStart, + lProcess.StartCheck) + # OR (sync mode) + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(lProcess.StartCheck) + + How to use StopSafe on the robot side + + .. code-block:: python + from pyOpenRPA.Tools import StopSafe + StopSafe.Init(inLogger=None) + StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe + """ + + mAgentHostNameStr = None + mAgentUserNameStr = None + mStartPathStr = None + mStartCMDStr = None + mStartArgDict = None + mStatusCheckIntervalSecFloat = None + mProcessNameWOExeStr = None + mStopSafeTimeoutSecFloat = None + mStatusStr = None # 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + # MST - Manual Stop Trigger + mMSTdTSecFloat: float = None + mMSTdNInt = None + mMSTStartTimeList = [] + + mAgentMuteBool = False # Mute any sends to agent while some action is perfomed + + mStatusSavedStr = None # Saved status to the further restore + + def MuteWait(self): + """ + Internal def. Wait when class is apply to send new activities to the agent + + :return: + """ + lIntervalSecFloat = 0.3 + while self.mAgentMuteBool == True: + time.sleep(lIntervalSecFloat) + return None + + def KeyTurpleGet(self): + """ + Get the key turple of the current process + + """ + return (self.mAgentHostNameStr.upper(), self.mAgentUserNameStr.upper(), self.mProcessNameWOExeStr.upper()) + + + def __init__(self, inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30): + """ + Init the class instance. + !ATTENTION! Function can raise exception if process with the same (inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr) is already exists in GSettings (can be restored from previous Orchestrator session). See ProcessInitSafe to sefaty init the instance or restore previous + !ATTENTION! Schedule options you must + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inStartPathStr: Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + :param inStartCMDStr: CMD script to start program (if no start file is exists) + :param inStopSafeTimeoutSecFloat: Time to wait for stop safe. After that do the stop force (if process is not stopped) + """ + lGS = __Orchestrator__.GSettingsGet() + # Check if Process is not exists in GSettings + if (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()) not in lGS["ManagersProcessDict"]: + self.mStartArgDict = inStartArgDict + self.mAgentHostNameStr = inAgentHostNameStr + self.mAgentUserNameStr = inAgentUserNameStr + self.mStartPathStr = inStartPathStr + self.mStartCMDStr = inStartCMDStr + self.mProcessNameWOExeStr = inProcessNameWOExeStr + self.mStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat + lGS["ManagersProcessDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper())]=self + lActivityDict = __Orchestrator__.ProcessorActivityItemCreate(inDef=self.StatusCheck,inArgList=[], inThreadBool=True) + __Orchestrator__.ProcessorActivityItemAppend(inActivityItemDict=lActivityDict) + if inStatusCheckIntervalSecFloat is not None: __Orchestrator__.OrchestratorScheduleGet().every(inStatusCheckIntervalSecFloat).seconds.do(Orchestrator.OrchestratorThreadStart,self.StatusCheck) + self.mStatusCheckIntervalSecFloat = inStatusCheckIntervalSecFloat + else: raise Exception(f"Managers.Process ({inAgentHostNameStr}, {inAgentUserNameStr}, {inProcessNameWOExeStr}): Can't init the Process instance because it already inited in early (see ProcessInitSafe)") + + def ManualStopTriggerSet(self, inMSTdTSecFloat: float, inMSTdNInt: int) -> None: + """ + Set ManualStopTrigger (MST) to switch to STOPPED MANUAL if specified count of start fails will be catched in specified time period + + :param inMSTdTSecFloat: Time perios in seconds + :param inMSTdNInt: Counts of the start tries + :return: None + """ + + # MST - Manual Stop Trigger + self.mMSTdTSecFloat = inMSTdTSecFloat + self.mMSTdNInt = inMSTdNInt + + + def ManualStopTriggerNewStart(self): + """ + Log new start event. Check if it is applicable. Change status if ManualStop trigger criteria is applied + + :return: # 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if self.mMSTdTSecFloat is not None and self.mMSTdNInt is not None: + lTimeNowSecFloat = time.time() + self.mMSTStartTimeList.append(lTimeNowSecFloat) # Append current time to MST list + # Remove old items from list + lMSTStartTimeList = [] + for lTimeItemSecFloat in self.mMSTStartTimeList: + ldTSecFloat = lTimeNowSecFloat - lTimeItemSecFloat + # Move to the new list if dT less + if ldTSecFloat < self.mMSTdTSecFloat: lMSTStartTimeList.append(lTimeItemSecFloat) + self.mMSTStartTimeList = lMSTStartTimeList # Set new list + # Check count in list + if len(lMSTStartTimeList) > self.mMSTdNInt: + self.mStatusStr = "1_STOPPED_MANUAL" + # Log info about process + lL = __Orchestrator__.OrchestratorLoggerGet() + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): ManualStopTrigger is activated. {self.mMSTdNInt} start tries in {self.mMSTdTSecFloat} sec.") + return self.mStatusStr + + def ManualStopListClear(self) -> None: + """ + Clear the last start tries list. + + :return: None + """ + self.mMSTStartTimeList=[] + + def Manual2Auto(self) -> str: + """ + Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lLogBool = False + if self.mStatusStr=="1_STOPPED_MANUAL": self.mStatusStr = "0_STOPPED"; lLogBool=True + if self.mStatusStr=="3_STOP_SAFE_MANUAL": self.mStatusStr = "2_STOP_SAFE"; lLogBool=True + if self.mStatusStr=="5_STARTED_MANUAL": self.mStatusStr = "4_STARTED"; lLogBool=True + # Log info about process + if lLogBool == True: self.StatusChangeLog() + return self.mStatusStr + + def Start(self, inIsManualBool = True, inStartArgDict=None) -> str: + """ + Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. + Will not start if STOP SAFE is now and don't start auto is stopped manual now + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if inIsManualBool == False: self.ManualStopTriggerNewStart() # Set the time + if self.mStatusStr is not None and (self.mStatusStr == "1_STOPPED_MANUAL" or "STOP_SAFE" in self.mStatusStr) and inIsManualBool == False: + lStr = f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Process will not start because of stopped manual or stop safe is now." + __Orchestrator__.OrchestratorLoggerGet().warning(lStr) + return self.mStatusStr + # Send activity item to agent - wait result + if self.mStartPathStr is not None: lCMDStr = os.path.abspath(self.mStartPathStr) + elif self.mStartCMDStr is not None: lCMDStr = self.mStartCMDStr + # Append args + if inStartArgDict is not None: self.mStartArgDict = inStartArgDict + if self.mStartArgDict is not None: + for lItemKeyStr in self.mStartArgDict: + lItemValueStr = self.mStartArgDict[lItemKeyStr] + lCMDStr = f"{lCMDStr} {lItemKeyStr} {lItemValueStr}" + #import pdb + #pdb.set_trace() + self.MuteWait() + self.mAgentMuteBool=True + lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate(inDef="OSCMD", + inArgDict={"inCMDStr":lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool":False}, + inArgGSettingsStr="inGSettings") + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, + inUserStr=self.mAgentUserNameStr, + inActivityItemDict=lActivityItemStart) + lStartResult = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if inIsManualBool==True: + self.mStatusStr = "5_STARTED_MANUAL" + else: + self.mStatusStr = "4_STARTED" + # Log info about process + self.StatusChangeLog() + self.mAgentMuteBool = False + return self.mStatusStr + + def StartCheck(self) -> str: + """ + Start program if auto stopped (0_STOPPED). + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.MuteWait() + if self.mStatusStr == "0_STOPPED": + self.Start(inIsManualBool=False) + return self.mStatusStr + + def StopSafe(self, inIsManualBool = True, inStopSafeTimeoutSecFloat = None) -> str: + """ + Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if inStopSafeTimeoutSecFloat is None: inStopSafeTimeoutSecFloat = self.mStopSafeTimeoutSecFloat + self.MuteWait() + self.mAgentMuteBool=True + # Send activity item to agent - wait result + lCMDStr = f'taskkill /im "{self.mProcessNameWOExeStr}.exe" /fi "username eq %USERNAME%"' + lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate( + inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool": False},inArgGSettingsStr="inGSettings") + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, + inUserStr=self.mAgentUserNameStr, + inActivityItemDict=lActivityItemStart) + lStartResult = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if inIsManualBool==True: + self.mStatusStr = "3_STOP_SAFE_MANUAL" + else: + self.mStatusStr = "2_STOP_SAFE" + # Log info about process + self.StatusChangeLog() + # Interval check is stopped + lTimeStartFloat = time.time() + lIntervalCheckSafeStatusFLoat = 15.0 + while "SAFE" in self.mStatusStr and (time.time() - lTimeStartFloat) < inStopSafeTimeoutSecFloat: + self.StatusCheck() + if "SAFE" not in self.mStatusStr: break + time.sleep(lIntervalCheckSafeStatusFLoat) + if "SAFE" in self.mStatusStr: + # Log info about process + lL = __Orchestrator__.OrchestratorLoggerGet() + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe stop has been wait for {inStopSafeTimeoutSecFloat} sec. Now do the force stop.") + self.StopForce(inIsManualBool=inIsManualBool,inMuteIgnoreBool=True) + # Log info about process + # self.StatusChangeLog() status check has already log status (see above) + self.mAgentMuteBool = False + return self.mStatusStr + + def StopSafeCheck(self, inStopSafeTimeoutSecFloat = None) -> str: + """ + Stop safe program if auto started (4_STARTED). + + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.MuteWait() + if self.mStatusStr == "4_STARTED": + self.StopSafe(inIsManualBool=False, inStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat) + return self.mStatusStr + + def StopForce(self, inIsManualBool = True, inMuteIgnoreBool = False) -> str: + """ + Manual/Auto stop force. Force stop don't wait process termination - it just terminate process now. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if inMuteIgnoreBool == False: self.MuteWait() + lMuteWorkBool = False + if self.mAgentMuteBool==False: self.mAgentMuteBool=True; lMuteWorkBool=True + # Send activity item to agent - wait result + lCMDStr = f'taskkill /F /im "{self.mProcessNameWOExeStr}.exe" /fi "username eq %USERNAME%"' + lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate( + inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool": False},inArgGSettingsStr="inGSettings") + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, + inUserStr=self.mAgentUserNameStr, + inActivityItemDict=lActivityItemStart) + lStartResult = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if inIsManualBool==True: + self.mStatusStr = "1_STOPPED_MANUAL" + else: + self.mStatusStr = "0_STOPPED" + # Log info about process + self.StatusChangeLog() + if lMuteWorkBool == True: + self.mAgentMuteBool=False + return self.mStatusStr + + def StopForceCheck(self) -> str: + """ + Stop force program if auto started (4_STARTED). + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.MuteWait() + if self.mStatusStr == "4_STARTED": + self.StopForce(inIsManualBool=False) + return self.mStatusStr + + def RestartSafe(self, inIsManualBool = True): + """ + Manual/Auto restart safe. Restart safe is the operation which send signal to process to terminate own work (send term signal to process). Then it run process. Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StopSafe(inIsManualBool=inIsManualBool) + return self.Start(inIsManualBool=inIsManualBool) + + def RestartForce(self, inIsManualBool = True): + """ + Manual/Auto restart force. Force restart dont wait process termination - it just terminate process now ant then start it. + Manual restart will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StopForce(inIsManualBool=inIsManualBool) + return self.Start(inIsManualBool=inIsManualBool) + + def StatusSave(self): + """ + Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don't save "STOP_SAFE" status > "STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lWarnSafeBool = True + if self.mStatusStr == "2_STOP_SAFE": self.mStatusSavedStr = "0_STOPPED" + elif self.mStatusStr == "3_STOP_SAFE_MANUAL": self.mStatusSavedStr = "1_STOPPED_MANUAL" + else: self.mStatusSavedStr = self.mStatusStr; lWarnSafeBool = False + if lWarnSafeBool==True: __Orchestrator__.OrchestratorLoggerGet().warning(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe status has been catched when safe > change saved status to stopped.") + return self.mStatusStr + + + def StatusCheckIntervalRestore(self): + """Call from orchestrator when init + """ + if self.mStatusCheckIntervalSecFloat is not None: + __Orchestrator__.OrchestratorLoggerGet().info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Restore schedule to StatusCheck in interval of {self.mStatusCheckIntervalSecFloat} sec.") + __Orchestrator__.OrchestratorScheduleGet().every(self.mStatusCheckIntervalSecFloat).seconds.do(Orchestrator.OrchestratorThreadStart,self.StatusCheck) + + def StatusRestore(self): + """ + Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StatusCheck() # check current status + # Do some action + if self.mStatusSavedStr != self.mStatusStr and self.mStatusSavedStr is not None: + #lManualBool = False + #if "MANUAL" in self.mStatusSavedStr: + # lManualBool = True + if "STOPPED" in self.mStatusSavedStr and "STOPPED" not in self.mStatusStr: + self.StopSafe(inIsManualBool=True) + if "STARTED" in self.mStatusSavedStr and "STARTED" not in self.mStatusStr: + self.Start(inIsManualBool=True) + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Status has been restored to {self.mStatusSavedStr}") + self.mStatusStr = self.mStatusSavedStr + self.mStatusSavedStr = None + return self.mStatusStr + + def StatusChangeLog(self): + """ + Lof information about status change + + :return: + """ + # Log info about process + lL = __Orchestrator__.OrchestratorLoggerGet() + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Status has been changed to {self.mStatusStr})") + + + def StatusCheck(self): + """ + Check if process is alive. The def will save the manual flag is exists. Don't wait mute but set mute if it is not set. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + # Send activity item to agent - wait result + lLogBool = False + lActivityItemUserProcessList = __Orchestrator__.ProcessorActivityItemCreate(inDef="ProcessWOExeUpperUserListGet") + #self.MuteWait() + self.mAgentMuteBool=True + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr,inUserStr=self.mAgentUserNameStr,inActivityItemDict=lActivityItemUserProcessList) + lUserProcessList = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if self.mProcessNameWOExeStr.upper() in lUserProcessList: + if self.mStatusStr == "1_STOPPED_MANUAL": self.mStatusStr = "5_STARTED_MANUAL"; lLogBool=True + if self.mStatusStr == "0_STOPPED": self.mStatusStr = "4_STARTED"; lLogBool=True + if self.mStatusStr is None: self.mStatusStr = "4_STARTED"; lLogBool=True + else: + if self.mStatusStr == "2_STOP_SAFE": self.mStatusStr = "0_STOPPED"; lLogBool = True + if self.mStatusStr == "3_STOP_SAFE_MANUAL": self.mStatusStr = "1_STOPPED_MANUAL"; lLogBool = True + if self.mStatusStr == "5_STARTED_MANUAL": self.mStatusStr = "1_STOPPED_MANUAL"; lLogBool=True + if self.mStatusStr == "4_STARTED": self.mStatusStr = "0_STOPPED"; lLogBool=True + if self.mStatusStr is None: self.mStatusStr = "0_STOPPED"; lLogBool=True + # Log info about process + if lLogBool == True: self.StatusChangeLog() + self.mAgentMuteBool = False + return self.mStatusStr + def StatusCheckStart(self): + """ + Check process status and run it if auto stopped self.mStatusStr is "0_STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lStatusStr = self.StatusCheck() + if lStatusStr == "0_STOPPED": + self.Start(inIsManualBool=False) + return self.mStatusStr + def StatusCheckStopForce(self): + """ + Check process status and auto stop force it if self.mStatusStr is 4_STARTED + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lStatusStr = self.StatusCheck() + if lStatusStr == "4_STARTED": + self.StopForce(inIsManualBool=False) + return self.mStatusStr + + def StatusCheckStopSafe(self): + """ + Check process status and auto stop safe it if self.mStatusStr is 4_STARTED + + :return: + """ + lStatusStr = self.StatusCheck() + if lStatusStr == "4_STARTED": + self.StopSafe(inIsManualBool=False) + return self.mStatusStr + + +def ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=300) -> Process: + """ + Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) + Return existing instance (if exists) or create new instance and return it. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inStartPathStr: Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + :param inStartCMDStr: CMD script to start program (if no start file is exists) + :param inStopSafeTimeoutSecFloat: Time to wait for stop safe. After that do the stop force (if process is not stopped) + :return: Process instance + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess + else: return Process(inAgentHostNameStr=inAgentHostNameStr,inAgentUserNameStr=inAgentUserNameStr,inProcessNameWOExeStr=inProcessNameWOExeStr, + inStartPathStr=inStartPathStr,inStartCMDStr=inStartCMDStr,inStopSafeTimeoutSecFloat=inStopSafeTimeoutSecFloat) + +def ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> bool: + """ + Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: True - process exists in gsettings; False - else + """ + return (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()) in __Orchestrator__.GSettingsGet()["ManagersProcessDict"] + + +def ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> Process: + """ + Return the process instance by the inProcessNameWOExeStr + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process instance (if exists) Else None + """ + return __Orchestrator__.GSettingsGet()["ManagersProcessDict"].get((inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()),None) + +def ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: + """ + Get the status of the Process instance. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.mStatusStr + +def ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) -> str: + """ + Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.Start(inIsManualBool=inIsManualBool) + + +def ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat = None) -> str: + """ + Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.StopSafe(inIsManualBool=inIsManualBool) + +def ProcessStopForce(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) -> str: + """ + Manual/Auto stop force. Force stop dont wait process termination - it just terminate process now. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.StopForce(inIsManualBool=inIsManualBool) + +def ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str): + """ + Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don't save "STOP_SAFE" status > "STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusSave() + return lProcess.mStatusStr + +def ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str): + """ + Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusRestore() + return lProcess.mStatusStr + +def ProcessStatusCheck(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: + """ + Check if process is alive. The def will save the manual flag is exists. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusCheck() + return lProcess.mStatusStr + +def ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: + """ + Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.Manual2Auto() + +def ProcessManualStopTriggerSet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inMSTdTSecFloat: float, inMSTdNInt: int) -> None: + """ + Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inMSTdTSecFloat: Time periods in seconds + :param inMSTdNInt: Counts of the start tries + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: lProcess.ManualStopTriggerSet(inMSTdTSecFloat = inMSTdTSecFloat, inMSTdNInt = inMSTdNInt) + + +def ProcessManualStopListClear(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> None: + """ + Clear the last start tries list. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: lProcess.ManualStopListClear() + +def ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str,inIntervalSecondsInt: int = 120): + """ + Run status check every interval in second you specify. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIntervalSecondsInt: Interval in seconds. Default is 120 + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + # Check job in threaded way + __Orchestrator__.OrchestratorScheduleGet().every(inIntervalSecondsInt).seconds.do(__Orchestrator__.OrchestratorThreadStart,lProcess.StatusCheck) \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py new file mode 100644 index 00000000..f705852f --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py @@ -0,0 +1,3 @@ +from .ControlPanel import * +from .Process import * +from .Git import * \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py index 0f0b61e4..62e07788 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py @@ -27,14 +27,42 @@ def ProcessorRunSync(inGSettings, inRobotRDPThreadControlDict): if len(lActivityList)>0: if lL: lL.debug(f'Processor ActivityList len: {len(lActivityList)}') lActivityItem = inGSettings["ProcessorDict"]["ActivityList"].pop(0) # Extract the first item from processor queue - inRobotRDPThreadControlDict["ThreadExecuteBool"]=False # Stop the RobotRDPActive monitoring - ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item - inRobotRDPThreadControlDict["ThreadExecuteBool"] = True # Continue the RobotRDPActive monitoring + if lActivityItem.get("ThreadBool", False) is False: + inRobotRDPThreadControlDict["ThreadExecuteBool"]=False # Stop the RobotRDPActive monitoring + inGSettings["ProcessorDict"]["ActivityItemNowDict"]=lActivityItem + ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item + inGSettings["ProcessorDict"]["ActivityItemNowDict"]=None + inRobotRDPThreadControlDict["ThreadExecuteBool"] = True # Continue the RobotRDPActive monitoring + else: + ProcessorRunAsync(inGSettings = inGSettings, inActivityList=[lActivityItem]) else: time.sleep(inGSettings["ProcessorDict"]["CheckIntervalSecFloat"]) # Sleep when list is empty except Exception as e: if lL: lL.exception(f"Processor.ProcessorRunSync. Something goes very wrong in processor queue. See traceback") +# Run processor Async +def ProcessorRunAsync(inGSettings, inActivityList): + """ + "inActivityList": [ # List of the activities + # { + # "Def":"DefAliasTest", # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + # "ArgList":[1,2,3], # Args list + # "ArgDict":{"ttt":1,"222":2,"dsd":3}, # Args dictionary + # "ArgGSettings": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "ArgLogger": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "GUIDStr": "sadasd-asdas-d-asdasd", # ActivityItem GUID which identify the Activity + # "ThreadBool": True + # }, + """ + def __process__(inGSettings, inActivityList): + for lActivityItem in inActivityList: + lL = inGSettings["Logger"] # Logger alias + if lL: lL.debug(f'ActivityItem in new thread') + lResultList = ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item + # Start in new thread + lThread = threading.Thread(target=__process__,kwargs={"inGSettings": inGSettings, "inActivityList": inActivityList}) + lThread.start() + # Execute ActivityItem list # return the def result def ActivityListExecute(inGSettings, inActivityList): @@ -113,8 +141,8 @@ def ProcessorMonitorRunSync(inGSettings): lActiveTimeStart = time.time() try: while True: - if len(inGSettings["ProcessorDict"]["ActivityList"])>0: - lItemDict = inGSettings["ProcessorDict"]["ActivityList"][0] + if inGSettings["ProcessorDict"]["ActivityItemNowDict"] is not None: + lItemDict = inGSettings["ProcessorDict"]["ActivityItemNowDict"] if "GUIDStr" not in lItemDict: lGUIDStr = str(uuid.uuid4()) # generate new GUID lItemDict["GUIDStr"] = lGUIDStr diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py index 1142344d..481a111c 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py @@ -79,13 +79,15 @@ def SessionConfigurationCreate(inConfiguration): lDriveStoreDirectStr = "" for lItem in inConfiguration['SharedDriveList']: lDriveStoreDirectStr+=f"{lItem.upper()}:\\;" # Attention - all drives must be only in upper case!!! - #Replace {Width}, {Height}, {BitDepth}, {HostPort}, {Login} + #Replace {Width}, {Height}, {BitDepth}, {HostPort}, {Login} {redirectclipboard} + lRedirectClipboardStr = "1" if inConfiguration.get('RedirectClipboardBool',True) == True else "0" lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{Width}", str(inConfiguration.get('Screen',{}).get("Width",1680))) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{Height}", str(inConfiguration.get('Screen',{}).get("Height",1050))) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{BitDepth}", inConfiguration.get('Screen',{}).get("DepthBit","32")) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{HostPort}", lHostPort) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{Login}", inConfiguration['Login']) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{SharedDriveList}", lDriveStoreDirectStr) + lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{redirectclipboard}", lRedirectClipboardStr) #Save template to temp file lRDPCurrentFileFullPath = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4().hex}.rdp") open(lRDPCurrentFileFullPath, "w", encoding="utf-16-le").write(lRDPTemplateFileContent) diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py index 8ec9a493..bdbb9efe 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py @@ -10,7 +10,7 @@ import psutil gSettings = None # Gsettings will be initialized after the import module # Create new RDPSession in RobotRDPActive -def RDPSessionConnect(inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPasswordStr): +def RDPSessionConnect(inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPasswordStr, inRedirectClipboardBool = True): global gSettings # ATTENTION - dont connect if RDP session is exist if inRDPSessionKeyStr not in gSettings["RobotRDPActive"]["RDPList"]: @@ -27,6 +27,7 @@ def RDPSessionConnect(inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPa "DepthBit": "32" # "32" or "24" or "16" or "15", example "32" }, "SharedDriveList": ["c"], # List of the Root sesion hard drives, example ["c"] + "RedirectClipboardBool": inRedirectClipboardBool, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, # Flag if the RDP window is exist, old name "FlagSessionIsActive". Check every n seconds , example False diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp index aeeb6d1ba600945c54a0e897f3bd4a5d3bae368d..0e7ae9af992b186268760ff50b0a2119a33b1068 100644 GIT binary patch delta 24 gcmew*G*5U#5i4W$WKULQ#@fkyS;RMYv3_6%0Buk delta 16 YcmbOy{7Yy<5$og^EMlA2uzq3&06Z%O(*OVf diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py index 50683680..22c4ab14 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py @@ -8,7 +8,7 @@ def CheckScreen(inIntervalSeconds=1): #Send os command to create console version (base screen) Screen.ConsoleScreenBase() #Delay to create console screen - time.sleep(2) + time.sleep(5) #Delay time.sleep(inIntervalSeconds) return None \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py index 76cfea8a..3560f861 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py @@ -11,6 +11,7 @@ from socketserver import ThreadingMixIn import threading import json from threading import Thread +import inspect from . import Processor # Add new processor from . import ProcessorOld # Support old processor - deprecated defs only for backward compatibility import urllib.parse # decode URL in string @@ -45,6 +46,13 @@ def __ComplexDictMerge2to1__(in1Dict, in2Dict): # Tool to merge complex dictionaries - no exceptions, just overwrite dict 2 in dict 1 def __ComplexDictMerge2to1Overwrite__(in1Dict, in2Dict): + """ + Merge in2Dict in in1Dict. In conflict override and get value from dict 2 + + :param in1Dict: Source dict. Save the link (structure) + :param in2Dict: New data dict + :return: Merged dict 1 + """ lPathList=None if lPathList is None: lPathList = [] for lKeyStr in in2Dict: @@ -253,7 +261,7 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): #Tech def #return {"headers":[],"body":"","statuscode":111} - def URLItemCheckDo(self, inURLItem, inMethod): + def URLItemCheckDo(self, inURLItem, inMethod, inOnlyFlagUACBool = False): ############################### #Tech sub def - do item ################################ @@ -273,7 +281,14 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): lFileObject.close() #If function is set if "ResponseDefRequestGlobal" in inURLItem: - inURLItem["ResponseDefRequestGlobal"](inRequest, inGlobalDict) + lDef = inURLItem["ResponseDefRequestGlobal"] + lDefSignature = inspect.signature(lDef) + if len(lDefSignature.parameters) == 2: + inURLItem["ResponseDefRequestGlobal"](inRequest, inGlobalDict) + elif len(lDefSignature.parameters) == 1: + inURLItem["ResponseDefRequestGlobal"](inRequest) + else: + inURLItem["ResponseDefRequestGlobal"]() if "ResponseFolderPath" in inURLItem: #lRequestPath = inRequest.path lRequestPath = urllib.parse.unquote(inRequest.path) @@ -290,6 +305,9 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): # Закрыть файловый объект lFileObject.close() ############################################## + # UAC Check + if inOnlyFlagUACBool == True and inURLItem.get("UACBool",None) in [None, True]: + return False if inURLItem["Method"].upper() == inMethod.upper(): # check Match type variant: BeginWith if inURLItem["MatchType"].upper() == "BEGINWITH": @@ -348,10 +366,12 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): self.end_headers() # Write content as utf-8 data self.wfile.write(inResponseDict["Body"]) - except ConnectionResetError as e: - if lL: lL.warning(f"An existing connection was forcibly closed by the remote host - OK for the network interactions (ConnectionResetError: [WinError 10054])") + except (ConnectionResetError, ConnectionAbortedError) as e: + if lL: lL.warning(f"SERVER: Connection was forcibly closed by the client side - OK for the network interactions (ConnectionResetError: [WinError 10054] or ConnectionAbortedError: [WinError 10053])") + def do_GET(self): try: + threading.current_thread().request = self self.OpenRPA = {} self.OpenRPA["AuthToken"] = None self.OpenRPA["Domain"] = None @@ -361,6 +381,16 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): # Prepare result dict lResponseDict = {"Headers": {}, "SetCookies": {}, "Body": b"", "StatusCode": None} self.OpenRPAResponseDict = lResponseDict + ############################ + #First - all with Flag UACBool + ############################ + for lURLItem in gSettingsDict["ServerDict"]["URLList"]: + #Check if all condition are applied + lFlagURLIsApplied=False + lFlagURLIsApplied=self.URLItemCheckDo(inURLItem=lURLItem, inMethod="GET", inOnlyFlagUACBool=True) + if lFlagURLIsApplied: + self.ResponseDictSend() + return ##################################### #Do authentication #Check if authentication is turned on @@ -425,6 +455,7 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): # POST def do_POST(self): try: + threading.current_thread().request = self lL = gSettingsDict["Logger"] self.OpenRPA = {} self.OpenRPA["AuthToken"] = None @@ -436,6 +467,16 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): #pdb.set_trace() lResponseDict = {"Headers": {}, "SetCookies":{}, "Body": b"", "StatusCode": None} self.OpenRPAResponseDict = lResponseDict + ############################ + #First - all with Flag UACBool + ############################ + for lURLItem in gSettingsDict["ServerDict"]["URLList"]: + #Check if all condition are applied + lFlagURLIsApplied=False + lFlagURLIsApplied=self.URLItemCheckDo(inURLItem=lURLItem, inMethod="POST", inOnlyFlagUACBool=True) + if lFlagURLIsApplied: + self.ResponseDictSend() + return ##################################### #Do authentication #Check if authentication is turned on diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py index ed09b85f..be8011eb 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py @@ -1,6 +1,5 @@ import json, os import copy -from inspect import signature # For detect count of def args from . import __Orchestrator__ #ControlPanelDict from desktopmagic.screengrab_win32 import ( @@ -15,6 +14,7 @@ from .Web import Basic from . import BackwardCompatibility # Support old up to 1.2.0 defs from . import Processor from . import SettingsTemplate + # # # # # # # # # # # # # v 1.2.0 Functionallity # # # # # # # # # # # # @@ -24,31 +24,14 @@ def HiddenJSInitGenerate(inRequest, inGSettings): lUACCPTemplateKeyList=["pyOpenRPADict","CPKeyDict"] lL = inGSettings["Logger"] # Alias for logger lJSInitResultStr = "" - lRenderFunctionsRobotDict = inGSettings["CPDict"] + lRenderFunctionsRobotDict = inGSettings["ServerDict"]["ControlPanelDict"] for lItemKeyStr in lRenderFunctionsRobotDict: lItemDict = lRenderFunctionsRobotDict[lItemKeyStr] - lJSInitGeneratorDef = lItemDict.get("JSInitGeneratorDef",None) lUACBool = dUAC(inRoleKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC) if lItemKeyStr=="VersionCheck": lUACBool=True # For backward compatibility for the old fron version which not reload page when new orch version is comming if lUACBool: # Run function if UAC is TRUE # JSONGeneratorDef - if lJSInitGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) - lJSResult = None - lDEFSignature = signature(lJSInitGeneratorDef) # Get signature of the def - lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args - try: - if lDEFARGLen == 1: # def (inGSettings) - lJSResult = lJSInitGeneratorDef(inGSettings) - elif lDEFARGLen == 2: # def (inRequest, inGSettings) - lJSResult = lJSInitGeneratorDef(inRequest, inGSettings) - elif lDEFARGLen == 0: # def () - lJSResult = lJSInitGeneratorDef() - if type(lJSResult) is str: - lJSInitResultStr += "; "+lJSResult # Add delimiter to some cases - else: - if lL: lL.warning(f"JSInitGenerator return bad type: {str(type(lJSResult))}, CP Key {lItemKeyStr}") - except Exception as e: - if lL: lL.exception(f"Error in control panel JSInitGeneratorDef. CP Key {lItemKeyStr}. Exception are below") + lJSInitResultStr = lJSInitResultStr + ";" + lItemDict.OnInitJSStr(inRequest=inRequest) return lJSInitResultStr # Generate CP HTML + JSON @@ -59,59 +42,20 @@ def HiddenCPDictGenerate(inRequest, inGSettings): lL = inGSettings["Logger"] # Alias for logger # Create result JSON lCPDict = {} - lRenderFunctionsRobotDict = inGSettings["CPDict"] + lRenderFunctionsRobotDict = inGSettings["ServerDict"]["ControlPanelDict"] for lItemKeyStr in lRenderFunctionsRobotDict: lItemDict = lRenderFunctionsRobotDict[lItemKeyStr] - lItemHTMLRenderDef = lItemDict.get("HTMLRenderDef",None) - lItemJSONGeneratorDef = lItemDict.get("JSONGeneratorDef",None) lUACBool = dUAC(inRoleKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC) if lItemKeyStr=="VersionCheck": lUACBool=True # For backward compatibility for the old fron version which not reload page when new orch version is comming if lUACBool: # Run function if UAC is TRUE lCPItemDict = {"HTMLStr": None, "JSONDict":None} - # HTMLRenderDef - if lItemHTMLRenderDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) - lHTMLResult = None - lDEFSignature = signature(lItemHTMLRenderDef) # Get signature of the def - lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args - try: - if lDEFARGLen == 1: # def (inGSettings) - lHTMLResult = lItemHTMLRenderDef(inGSettings) - elif lDEFARGLen == 2: # def (inRequest, inGSettings) - lHTMLResult = lItemHTMLRenderDef(inRequest, inGSettings) - elif lDEFARGLen == 0: # def () - lHTMLResult = lItemHTMLRenderDef() - # RunFunction - # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" - if type(lHTMLResult) is str: - lCPItemDict["HTMLStr"] = lHTMLResult - elif "HTMLStr" in lHTMLResult or "JSONDict" in lHTMLResult: - lCPItemDict = lHTMLResult # new version - else: - # Call backward compatibility HTML generator - lCPItemDict["HTMLStr"] = Basic.HTMLControlPanelBC(inCPDict=lHTMLResult) - except Exception as e: - if lL: lL.exception(f"Error in control panel HTMLRenderDef. CP Key {lItemKeyStr}. Exception are below") - # JSONGeneratorDef - if lItemJSONGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) - lJSONResult = None - lDEFSignature = signature(lItemJSONGeneratorDef) # Get signature of the def - lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args - try: - if lDEFARGLen == 1: # def (inGSettings) - lJSONResult = lItemJSONGeneratorDef(inGSettings) - elif lDEFARGLen == 2: # def (inRequest, inGSettings) - lJSONResult = lItemJSONGeneratorDef(inRequest, inGSettings) - elif lDEFARGLen == 0: # def () - lJSONResult = lItemJSONGeneratorDef() - # RunFunction - # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" - lType = type(lJSONResult) - if lType is str or lJSONResult is None or lType is int or lType is list or lType is dict or lType is bool or lType is float: - lCPItemDict["JSONDict"] = lJSONResult - else: - if lL: lL.warning(f"JSONGenerator return bad type: {str(type(lJSONResult))}, CP Key {lItemKeyStr}") - except Exception as e: - if lL: lL.exception(f"Error in control panel JSONGeneratorDef. CP Key {lItemKeyStr}. Exception are below") + try: + # HTML Render + lCPItemDict["HTMLStr"] = lItemDict.OnRefreshHTMLStr(inRequest=inRequest) + # JSONGeneratorDef + lCPItemDict["JSONDict"] = lItemDict.OnRefreshJSONDict(inRequest=inRequest) + except Exception as e: + lL.exception(f"EXCEPTION WHEN HTML/ JSON RENDER") # Insert CPItemDict in result CPDict lCPDict[lItemKeyStr]=lCPItemDict return lCPDict @@ -207,6 +151,8 @@ def pyOpenRPA_ServerData(inRequest,inGSettings): def pyOpenRPA_ServerJSInit(inRequest,inGSettings): lResultStr = HiddenJSInitGenerate(inRequest=inRequest, inGSettings=inGSettings) inResponseDict = inRequest.OpenRPAResponseDict + if lResultStr is None: + lResultStr = "" # Write content as utf-8 data inResponseDict["Body"] = bytes(lResultStr, "utf8") @@ -281,8 +227,22 @@ def pyOpenRPA_Processor(inRequest, inGSettings): lActivityTypeListStr = "Has some error with Activity Type read" lWebAuditMessageStr = __Orchestrator__.WebAuditMessageCreate(inRequest=inRequest,inOperationCodeStr=lActivityTypeListStr, inMessageStr="pyOpenRPA_Processor") if lL: lL.info(lWebAuditMessageStr) - # Append in list - inGSettings["ProcessorDict"]["ActivityList"]+=lInput + # Separate into 2 lists - sync and async + lSyncActvityList = [] + lAsyncActivityList = [] + for lActivityItem in lInput: + if lInput.get("ThreadBool", False) == False: + lSyncActvityList.append(lActivityItem) + else: + lAsyncActivityList.append(lActivityItem) + # Sync: Append in list + inGSettings["ProcessorDict"]["ActivityList"]+=lSyncActvityList + # Async: go to run + if len(lAsyncActivityList)>0: + for lActivityItem in lAsyncActivityList: + lActivityItemArgsDict = {"inGSettings":inGSettings,"inActivityList":[lActivityItem]} + lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs=lActivityItemArgsDict) + lThread.start() else: # Logging info about processor activity if not SuperToken () if not __Orchestrator__.WebUserIsSuperToken(inRequest=inRequest, inGSettings=inGSettings): @@ -293,8 +253,13 @@ def pyOpenRPA_Processor(inRequest, inGSettings): lActivityTypeListStr = "Has some error with Activity Type read" lWebAuditMessageStr = __Orchestrator__.WebAuditMessageCreate(inRequest=inRequest,inOperationCodeStr=lActivityTypeListStr, inMessageStr="pyOpenRPA_Processor") if lL: lL.info(lWebAuditMessageStr) - # Append in list - inGSettings["ProcessorDict"]["ActivityList"].append(lInput) + if lInput.get("ThreadBool",False) == False: + # Append in list + inGSettings["ProcessorDict"]["ActivityList"].append(lInput) + else: + lActivityItemArgsDict = {"inGSettings": inGSettings, "inActivityList": [lInput]} + lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs=lActivityItemArgsDict) + lThread.start() # Execute activity list def pyOpenRPA_ActivityListExecute(inRequest, inGSettings): # Recieve the data @@ -369,7 +334,6 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): lThisAgentDict["IsListenBool"] = True # Set is online lQueueList = lThisAgentDict["ActivityList"] if len(lQueueList)>0:# Do some operations if has queue items - if lL: lL.debug(f'O2A: ConnectionCountInt: {lThisAgentDict["ConnectionCountInt"]};ConnectionFirstQueueItemCountInt {lThisAgentDict["ConnectionFirstQueueItemCountInt"]}') # check if delta datetime is < than ActivityLifeTimeSecFloat lActivityItem = lThisAgentDict["ActivityList"][0] lActivityLifetimeSecFloat = (datetime.datetime.now() - lActivityItem["CreatedByDatetime"]).total_seconds() @@ -377,36 +341,35 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): if lActivityLifetimeSecFloat > lActivityItemLifetimeLimitSecFloat: lActivityItem = lThisAgentDict["ActivityList"].pop(0) else: + lReturnActivityItemList = [] lReturnActivityItemDict = None # If lInput['ActivityLastGUIDStr'] is '' > return 0 element for send in Agent if lInput['ActivityLastGUIDStr'] == "": - lReturnActivityItemDict = lThisAgentDict["ActivityList"][0] + lReturnActivityItemList=lQueueList # 2022 02 21 - Maslov Return list - not one item else: # go from the end - search element with GUIDStr lForTriggerGetNextItem = False for lForActivityItemDict in lQueueList: if lForTriggerGetNextItem == True: lReturnActivityItemDict = lForActivityItemDict - break + lReturnActivityItemList.append(lReturnActivityItemDict) # 2022 02 21 - Maslov Return list - not one item + #break if lForActivityItemDict['GUIDStr'] == lInput['ActivityLastGUIDStr']: lForTriggerGetNextItem = True # CASE if GUID is not detected - return 0 element - if lReturnActivityItemDict == None and lForTriggerGetNextItem == False: - lReturnActivityItemDict = lThisAgentDict["ActivityList"][0] + if (len(lQueueList)==1 and lQueueList[0]['GUIDStr'] != lInput['ActivityLastGUIDStr']): + #lReturnActivityItemDict = lThisAgentDict["ActivityList"][0] + lReturnActivityItemList=lQueueList # 2022 02 21 - Maslov Return list - not one item # Send QUEUE ITEM - if lReturnActivityItemDict is not None: - lReturnActivityItemDict = copy.deepcopy(lReturnActivityItemDict) - if "CreatedByDatetime" in lReturnActivityItemDict: - del lReturnActivityItemDict["CreatedByDatetime"] - inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lReturnActivityItemDict), "utf8") + if len(lReturnActivityItemList) > 0: + lReturnActivityItemList = copy.deepcopy(lReturnActivityItemList) + for lItemDict in lReturnActivityItemList: + if "CreatedByDatetime" in lItemDict: + del lItemDict["CreatedByDatetime"] + inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lReturnActivityItemList), "utf8") # Log full version if bytes size is less than limit . else short lBodyLenInt = len(inRequest.OpenRPAResponseDict["Body"]) lAgentLimitLogSizeBytesInt = inGSettings["ServerDict"]["AgentLimitLogSizeBytesInt"] - if lBodyLenInt <= lAgentLimitLogSizeBytesInt: - if lL: lL.info(f"Activity item to agent Hostname {lInput['HostNameUpperStr']}, User {lInput['UserUpperStr']}. Activity item: {lReturnActivityItemDict}") - else: - if lL: lL.info( - f"Activity item to agent Hostname {lInput['HostNameUpperStr']}, User {lInput['UserUpperStr']}. " - f"Activity item: Was suppressed because of body size of {lBodyLenInt} bytes. Max is {lAgentLimitLogSizeBytesInt}") + if lL: lL.debug(f"ActivityItem to Agent ({lInput['HostNameUpperStr']}, {lInput['UserUpperStr']}): Item count: {len(lReturnActivityItemList)}, bytes size: {lBodyLenInt}") lDoLoopBool = False # CLose the connection else: # Nothing to send - sleep for the next iteration time.sleep(lAgentLoopSleepSecFloat) @@ -415,6 +378,32 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): except Exception as e: if lL: lL.exception("pyOpenRPA_Agent_O2A Exception!") lThisAgentDict["ConnectionCountInt"] -= 1 # Connection go to be closed - decrement the connection count + +def pyOpenRPA_Debugging_HelperDefList(inRequest, inGSettings): + # Parse query + lResultDict = { + "success": True, + "results": [] + } + # Get the path + lPathSplitList = __Orchestrator__.WebRequestParsePath(inRequest=inRequest).split('/') + lQueryStr = None + if "HelperDefList" != lPathSplitList[-1] and "" != lPathSplitList[-1]: lQueryStr = lPathSplitList[-1] + if lQueryStr != "" and lQueryStr is not None: + lDefList = __Orchestrator__.ActivityItemHelperDefList(inDefQueryStr=lQueryStr) + for lDefStr in lDefList: + lResultDict["results"].append({"name": lDefStr, "value": lDefStr, "text": lDefStr}) + __Orchestrator__.WebRequestResponseSend(inRequest=inRequest, inResponeStr=json.dumps(lResultDict)) + +def pyOpenRPA_Debugging_HelperDefAutofill(inRequest, inGSettings): + # Parse query + # Get the path + lPathSplitList = __Orchestrator__.WebRequestParsePath(inRequest=inRequest).split('/') + lQueryStr = None + if "HelperDefAutofill" != lPathSplitList[-1] and "" != lPathSplitList[-1]: lQueryStr = lPathSplitList[-1] + lResultDict = __Orchestrator__.ActivityItemHelperDefAutofill(inDef = lQueryStr) + __Orchestrator__.WebRequestResponseSend(inRequest=inRequest, inResponeStr=json.dumps(lResultDict)) + # See docs in Agent (pyOpenRPA.Agent.A2O) def pyOpenRPA_Agent_A2O(inRequest, inGSettings): lL = inGSettings["Logger"] @@ -434,7 +423,13 @@ def pyOpenRPA_Agent_A2O(inRequest, inGSettings): lActivityReturnItemValue = lInput["ActivityReturnDict"][lActivityReturnItemKeyStr] # Create item in gSettings inGSettings["AgentActivityReturnDict"][lActivityReturnItemKeyStr]=SettingsTemplate.__AgentActivityReturnDictItemCreate__(inReturn=lActivityReturnItemValue) - if lL: lL.debug(f"SERVER: pyOpenRPA_Agent_A2O:: Has recieved result of the activity items from agent! ActivityItem GUID Str: {lActivityReturnItemKeyStr}; Return value: {lActivityReturnItemValue}") + lLogStr = "x bytes" + try: + if lActivityReturnItemValue is not None: + lLogStr = f"{len(lActivityReturnItemValue)} bytes" + except Exception as e: + pass + if lL: lL.debug(f"SERVER: pyOpenRPA_Agent_A2O:: Has recieved result of the activity items from agent! ActivityItem GUID Str: {lActivityReturnItemKeyStr}; Return value len: {lLogStr}") # Delete the source activity item from AgentDict if lAgentDictItemKeyTurple in inGSettings["AgentDict"]: lAgentDictActivityListNew = [] @@ -484,6 +479,8 @@ def SettingsUpdate(inGlobalConfiguration): {"Method": "POST", "URL": "/pyOpenRPA/ActivityListExecute", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ActivityListExecute, "ResponseContentType": "application/json"}, {"Method": "POST", "URL": "/pyOpenRPA/Agent/O2A", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_Agent_O2A, "ResponseContentType": "application/json"}, {"Method": "POST", "URL": "/pyOpenRPA/Agent/A2O", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_Agent_A2O, "ResponseContentType": "application/json"}, + {"Method": "GET", "URL": "/pyOpenRPA/Debugging/HelperDefList/", "MatchType": "BeginWith","ResponseDefRequestGlobal": pyOpenRPA_Debugging_HelperDefList, "ResponseContentType": "application/json"}, + {"Method": "GET", "URL": "/pyOpenRPA/Debugging/HelperDefAutofill/", "MatchType": "BeginWith","ResponseDefRequestGlobal": pyOpenRPA_Debugging_HelperDefAutofill, "ResponseContentType": "application/json"}, ] inGlobalConfiguration["ServerDict"]["URLList"]=inGlobalConfiguration["ServerDict"]["URLList"]+lURLList return inGlobalConfiguration \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py index 242ddf55..d2f7fac4 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py @@ -1,4 +1,5 @@ import os, logging, datetime, sys +import schedule # https://schedule.readthedocs.io/en/stable/examples.html # Technical def - return GSettings structure with examples def __Create__(): @@ -39,6 +40,9 @@ def __Create__(): # # # # # # # # # # # # # # # # # # }, "ServerDict": { + "ControlPanelDict": { + # "CPKey": + }, "AgentLimitLogSizeBytesInt": 300, # Don't show body if json body of transmition is more than "ServerThread": None, # Server thread is there "AgentActivityLifetimeSecFloat": 1200.0, # Time in seconds to life for activity for the agent @@ -105,18 +109,20 @@ def __Create__(): # "ResponseFilePath": "", #Absolute or relative path # "ResponseFolderPath": "", #Absolute or relative path # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result + # "ResponseDefRequestGlobal": None ,#Function with str result + # "UACBool": True # True - check user access before do this URL item. None - get Server flag if ask user # } - { - "Method": "GET", - "URL": "/test/", # URL of the request - "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", - # "ResponseFilePath": "", #Absolute or relative path - "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", - # Absolute or relative path - # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result - } + #{ + # "Method": "GET", + # "URL": "/test/", # URL of the request + # "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", + # # "ResponseFilePath": "", #Absolute or relative path + # "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", + # # Absolute or relative path + # # "ResponseContentType": "", #HTTP Content-type + # # "ResponseDefRequestGlobal": None #Function with str result + # # "UACBool": True # True - check user access before do this URL item + #} ], }, @@ -126,6 +132,7 @@ def __Create__(): "ActivityList": [] }, "SchedulerDict": { + "Schedule": schedule, # https://schedule.readthedocs.io/en/stable/examples.html "CheckIntervalSecFloat": 5.0, # Check interval in seconds "ActivityTimeList": [ # { @@ -145,6 +152,8 @@ def __Create__(): # }, ], }, + "ManagersProcessDict":{}, # The key of the Process is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mProcessNameWOExeStr.upper()) + "ManagersGitDict":{}, # The key of the Git instance is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mAbsPathUpperStr.upper()) "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 "ActivityList": [ # List of the activities # { @@ -156,24 +165,13 @@ def __Create__(): # "GUIDStr": ..., # GUID of the activity # }, ], + "ActivityItemNowDict": None, # Activity Item which is executing now "AliasDefDict": {}, # Storage for def with Str alias. To use it see pyOpenRPA.Orchestrator.ControlPanel "CheckIntervalSecFloat": 1.0, # Interval for check gSettings in ProcessorDict > ActivityList "ExecuteBool": True, # Flag to execute thread processor "ThreadIdInt": None, # Technical field - will be setup when processor init "WarningExecutionMoreThanSecFloat": 60.0 # Push warning if execution more than n seconds }, - "ControlPanelDict": { # Old structure > CPDict - "RefreshSeconds": 5, # deprecated parameter - "RobotList": [ - #{ - # "RenderFunction": RenderRobotR01, - # "KeyStr": "TestControlPanelKey" - #} - ] - }, - "CPDict": { - # "CPKey": {"HTMLRenderDef":None, "JSONGeneratorDef":None, "JSInitGeneratorDef":None} - }, # # # # # # # # # # # # # # "RobotRDPActive": { "RecoveryDict": { @@ -294,7 +292,8 @@ def __UACClientAdminCreate__(): "RestartOrchestratorBool": True, # Restart orchestrator activity "RestartOrchestratorGITPullBool": True, # Turn off (RDP remember) orc + git pull + Turn on (rdp remember) "RestartPCBool": True, # Send CMD to restart pc - "NothingBool":True # USe option if you dont want to give some access to the RDP controls + "NothingBool":True, # USe option if you dont want to give some access to the RDP controls + "Debugging":True # Debugging tool }, "ActivityDict": { # Empty dict - all access "ActivityListExecuteBool": True, # Execute activity at the current thread @@ -324,7 +323,7 @@ def LoggerDumpLogHandlerAdd(inLogger, inGSettingsClientDict): # inModeStr: # "BASIC" - create standart configuration from pyOpenRPA.Orchestrator.Utils import LoggerHandlerDumpLogList -def Create(inModeStr="BASIC"): +def Create(inModeStr="BASIC", inLoggerLevel = None): if inModeStr=="BASIC": lResult = __Create__() # Create settings # Создать файл логирования @@ -334,26 +333,30 @@ def Create(inModeStr="BASIC"): ########################## # Подготовка логгера Robot ######################### - mRobotLogger = lResult["Logger"] - mRobotLogger.setLevel(logging.INFO) - # create the logging file handler - mRobotLoggerFH = logging.FileHandler( - "Reports\\" + datetime.datetime.now().strftime("%Y_%m_%d") + ".log") - mRobotLoggerFormatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - mRobotLoggerFH.setFormatter(mRobotLoggerFormatter) - # add handler to logger object - mRobotLogger.addHandler(mRobotLoggerFH) - ####################Add console output - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(mRobotLoggerFormatter) - mRobotLogger.addHandler(handler) - ############################################ - LoggerDumpLogHandlerAdd(inLogger=mRobotLogger, inGSettingsClientDict=lResult["Client"]) - #mHandlerDumpLogList = LoggerHandlerDumpLogList.LoggerHandlerDumpLogList(inDict=lResult["Client"], - # inKeyStr="DumpLogList", - # inHashKeyStr="DumpLogListHashStr", - # inRowCountInt=lResult["Client"][ - # "DumpLogListCountInt"]) - #mHandlerDumpLogList.setFormatter(mRobotLoggerFormatter) - #mRobotLogger.addHandler(mHandlerDumpLogList) + if inLoggerLevel is None: inLoggerLevel=logging.INFO + lL = lResult["Logger"] + if len(lL.handlers) == 0: + lL.setLevel(logging.INFO) + # create the logging file handler + mRobotLoggerFH = logging.FileHandler( + "Reports\\" + datetime.datetime.now().strftime("%Y_%m_%d") + ".log") + mRobotLoggerFormatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + mRobotLoggerFH.setFormatter(mRobotLoggerFormatter) + # add handler to logger object + lL.addHandler(mRobotLoggerFH) + ####################Add console output + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(mRobotLoggerFormatter) + lL.addHandler(handler) + ############################################ + LoggerDumpLogHandlerAdd(inLogger=lL, inGSettingsClientDict=lResult["Client"]) + #mHandlerDumpLogList = LoggerHandlerDumpLogList.LoggerHandlerDumpLogList(inDict=lResult["Client"], + # inKeyStr="DumpLogList", + # inHashKeyStr="DumpLogListHashStr", + # inRowCountInt=lResult["Client"][ + # "DumpLogListCountInt"]) + #mHandlerDumpLogList.setFormatter(mRobotLoggerFormatter) + #mRobotLogger.addHandler(mHandlerDumpLogList) + else: + if lL: lL.warning("Pay attention! Your code has been call SettingsTemplate.Create - since pyOpenRPA v1.2.7 GSettings is creating automatically") return lResult # return the result dict \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js index f4f7be28..07fe9ab2 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js @@ -883,14 +883,110 @@ $(document).ready(function() { if (lUACAsk(["pyOpenRPADict","AdminDict","RestartOrchestratorBool"])) { $(".UACClient-pyOpenRPADict-AdminDict-RestartOrchestratorBool").show(); } if (lUACAsk(["pyOpenRPADict","AdminDict","RestartOrchestratorGITPullBool"])) { $(".UACClient-pyOpenRPADict-AdminDict-RestartOrchestratorGITPullBool").show(); } if (lUACAsk(["pyOpenRPADict","AdminDict","RestartPCBool"])) { $(".UACClient-pyOpenRPADict-AdminDict-RestartPCBool").show(); } + if (lUACAsk(["pyOpenRPADict","AdminDict","Debugging"])) { $(".UACClient-pyOpenRPADict-AdminDict-Debugging").show(); } } /// v1.2.0 pyOpenRPA Init defs - mGlobal.pyOpenRPA.ServerJSInitDef(); // Recieve JS from server (if exist) and then call anothe url ServerData mGlobal.pyOpenRPA.ServerDataRefreshDef(); // Init the refresh data def from server side mGlobal.pyOpenRPA.ServerLogListRefreshDef(); // Init the refresh data def from the log window mGlobal.pyOpenRPA.ServerLogListDoRenderTrue(); // Init button to freeze/unfreeze textare with logs + mGlobal.pyOpenRPA.ServerJSInitDef(); // Recieve JS from server (if exist) and then call anothe url ServerData + //$('.ui.dropdown').dropdown(); + + //////////////////////////////////////////// + // 1.2.7 Debugging + /// Execute ActivityItem - $('.ui.dropdown').dropdown(); + // Debugging onchange def autofill init + var lDropdownOnChange = function(inEvent){ + //lValueStr = inEvent.target.value + lValueStr = inEvent + $.ajax({ + type: "GET", + url: '/pyOpenRPA/Debugging/HelperDefAutofill/'+lValueStr, + data: null, + success: + function(lData,l2,l3) + { + var lResponseJSON=JSON.parse(lData) + console.log("HelperDefAutofill:") + console.log(lResponseJSON) + //ArgDict merge + var lArgDictTargetDict = lResponseJSON["ArgDict"] + var lArgDictStr = $(".mGlobal-pyOpenRPA-Debugging-ArgDict")[0].value + if (lArgDictStr !="" && lArgDictStr !=null) { + lArgDictLastDict = JSON.parse(lArgDictStr) + lArgDictTargetDict = mGlobal.pyOpenRPA.DebuggingAutofillMerge(lArgDictTargetDict, lArgDictLastDict) + } + + $(".mGlobal-pyOpenRPA-Debugging-ArgList")[0].value = JSON.stringify(lResponseJSON["ArgList"]) + $(".mGlobal-pyOpenRPA-Debugging-ArgDict")[0].value = JSON.stringify(lArgDictTargetDict) + $(".mGlobal-pyOpenRPA-Debugging-ArgGSettingsStr")[0].value = JSON.stringify(lResponseJSON["ArgGSettingsStr"]) + $(".mGlobal-pyOpenRPA-Debugging-ArgLoggerStr")[0].value = JSON.stringify(lResponseJSON["ArgLoggerStr"]) + + }, + dataType: "text" + }); + } + //$('.ui.dropdown.mGlobal-pyOpenRPA-Debugging-Def-Dropdown')[0].onchange=lDropdownOnChange + + + + mGlobal.pyOpenRPA.DebuggingExecute=function() { + ///EXAMPLE + // { + // "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + // "ArgList":[], // Args list + // "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary + // "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + // "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + // } + ///Подготовить конфигурацию + lArgListStr = $(".mGlobal-pyOpenRPA-Debugging-ArgList")[0].value + lArgDictStr = $(".mGlobal-pyOpenRPA-Debugging-ArgDict")[0].value + lArgGSettingsStr = $(".mGlobal-pyOpenRPA-Debugging-ArgGSettingsStr")[0].value + lArgLoggerStr = $(".mGlobal-pyOpenRPA-Debugging-ArgLoggerStr")[0].value + lActivityItem = { + "Def":$(".mGlobal-pyOpenRPA-Debugging-Def")[0].value, // def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList":(lArgListStr == "" ? [] : JSON.parse(lArgListStr)), // Args list + "ArgDict":(lArgDictStr == "" ? {} : JSON.parse(lArgDictStr)), // Args dictionary + "ArgGSettingsStr": (lArgGSettingsStr == "" ? null : lArgGSettingsStr), // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLoggerStr": (lArgLoggerStr == "" ? null : lArgLoggerStr) // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + lData = [lActivityItem] + $.ajax({ + type: "POST", + url: '/pyOpenRPA/ActivityListExecute', + data: JSON.stringify(lData), + success: + function(lData,l2,l3) + { + var lResponseJSON=JSON.parse(lData) + console.log(lResponseJSON) + $(".mGlobal-pyOpenRPA-Debugging-Output")[0].value = JSON.stringify(lResponseJSON[0]) + }, + dataType: "text" + }); + } + mGlobal.pyOpenRPA.DebuggingAutofillMerge=function(inTargetDict, inLastDict) { + // Merge 2 dict (get values from Last dict if key exists in new dict + for (const [lKeyStr, lValue] of Object.entries(inTargetDict)) { + //Check if key exists in LastDict + if (lKeyStr in inLastDict) { + inTargetDict[lKeyStr] = inLastDict[lKeyStr] + } + } + return inTargetDict + } + // 1.2.7 Debugging toolbox init + $('.ui.dropdown.mGlobal-pyOpenRPA-Debugging-Def-Dropdown') + .dropdown({ + apiSettings: { + // this url parses query server side and returns filtered results + url: '/pyOpenRPA/Debugging/HelperDefList/{query}' + }, + onChange: lDropdownOnChange + }) + ; }); \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml index 74e4976d..ec06ac11 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml @@ -45,6 +45,16 @@ margin: 5em 0em 0em; padding: 5em 0em; } + .ui.search.dropdown>input.search { + width:100%; + font-family:monospace; + font-weight: bold; + } + .ui.search.dropdown>.text { + width:100%; + font-family:monospace; + font-weight: bold; + } @@ -298,6 +308,57 @@ +

diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py index ac155ad3..edbb7c0f 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -1,13 +1,15 @@ import subprocess, json, psutil, time, os, win32security, sys, base64, logging, ctypes, copy #Get input argument import pickle - -from partd import Server +import inspect +import schedule +#from partd import Server from . import Server from . import Timer from . import Processor from . import BackwardCompatibility # Backward compatibility from v1.1.13 from . import Core +from . import Managers from subprocess import CREATE_NEW_CONSOLE from .Utils import LoggerHandlerDumpLogList @@ -27,13 +29,15 @@ from . import SettingsTemplate # Settings template import uuid # Generate uuid import datetime # datetime import math +import glob # search the files +import urllib #Единый глобальный словарь (За основу взять из Settings.py) gSettingsDict = None # AGENT DEFS -def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDict): +def AgentActivityItemAdd(inHostNameStr, inUserStr, inActivityItemDict, inGSettings=None): """ Add activity in AgentDict. Check if item is created @@ -43,6 +47,8 @@ def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDi :param inActivityItemDict: ActivityItem :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ + + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = copy.deepcopy(inActivityItemDict) # Add GUIDStr if not exist lGUIDStr = None @@ -62,7 +68,7 @@ def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDi return lGUIDStr -def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): +def AgentActivityItemExists(inHostNameStr, inUserStr, inGUIDStr, inGSettings = None): """ Check by GUID if ActivityItem has exists in request list. If exist - the result response has not been recieved from the agent @@ -71,6 +77,7 @@ def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): :return: True - ActivityItem is exist in AgentDict ; False - else case """ # Check if GUID is exists in dict - has been recieved + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Main alg lAgentDictItemKeyTurple = (inHostNameStr.upper(),inUserStr.upper()) lResultBool = False @@ -81,7 +88,7 @@ def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): break return lResultBool -def AgentActivityItemReturnExists(inGSettings, inGUIDStr): +def AgentActivityItemReturnExists(inGUIDStr, inGSettings = None): """ Check by GUID if ActivityItem has been executed and result has come to the Orchestrator @@ -89,11 +96,13 @@ def AgentActivityItemReturnExists(inGSettings, inGUIDStr): :param inGUIDStr: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! :return: True - result has been received from the Agent to orc; False - else case """ + + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check if GUID is exists in dict - has been recieved return inGUIDStr in inGSettings["AgentActivityReturnDict"] -def AgentActivityItemReturnGet(inGSettings, inGUIDStr, inCheckIntervalSecFloat = 0.5): +def AgentActivityItemReturnGet(inGUIDStr, inCheckIntervalSecFloat = 0.5, inGSettings=None): """ Work synchroniously! Wait while result will be recieved. Get the result of the ActivityItem execution on the Agent side. Before this please check by the def AgentActivityItemReturnExists that result has come to the Orchestrator @@ -104,6 +113,7 @@ def AgentActivityItemReturnGet(inGSettings, inGUIDStr, inCheckIntervalSecFloat = :param inCheckIntervalSecFloat: Interval in sec of the check Activity Item result :return: Result of the ActivityItem executed on the Agent side anr transmitted to the Orchestrator. IMPORTANT! ONLY JSON ENABLED Types CAN BE TRANSMITTED TO ORCHESTRATOR! """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings #Check if Orchestrator has been initialized - else raise exception if Core.IsOrchestratorInitialized(inGSettings=inGSettings) == True: # Wait while result will not come here @@ -114,7 +124,7 @@ def AgentActivityItemReturnGet(inGSettings, inGUIDStr, inCheckIntervalSecFloat = else: raise Exception(f"__Orchestrator__.AgentActivityItemReturnGet !ATTENTION! Use this function only after Orchestrator initialization! Before orchestrator init exception will be raised.") -def AgentOSCMD(inGSettings, inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251"): +def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251", inGSettings=None, inCaptureBool=True): """ Send CMD to OS thought the pyOpenRPA.Agent daemon. Result return to log + Orchestrator by the A2O connection @@ -125,21 +135,39 @@ def AgentOSCMD(inGSettings, inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=T :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list - "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr}, # Args dictionary + "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr, "inCaptureBool":inCaptureBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) +def AgentOSLogoff(inHostNameStr, inUserStr): + """ + Logoff the agent user session -def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePathStr, inAgentFilePathStr): + :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! + """ + inGSettings = GSettingsGet() # Set the global settings + lCMDStr = "shutdown /l" + lActivityItemDict = { + "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) + "ArgList":[], # Args list + "ArgDict":{"inCMDStr":lCMDStr,"inRunAsyncBool":False, "inSendOutputToOrchestratorLogsBool": True, "inCMDEncodingStr": "cp1251"}, # Args dictionary + "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + #Send item in AgentDict for the futher data transmition + return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) + +def AgentOSFileSend(inHostNameStr, inUserStr, inOrchestratorFilePathStr, inAgentFilePathStr, inGSettings = None): """ Send the file from the Orchestrator to Agent (synchroniously) pyOpenRPA.Agent daemon process (safe for JSON transmition). Work safety with big files @@ -153,7 +181,7 @@ def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePat :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if inGSettings["ServerDict"]["ServerThread"] is None: if inGSettings["Logger"]: inGSettings["Logger"].warning(f"AgentOSFileSend run before server init - activity will be append in the processor queue.") @@ -205,7 +233,7 @@ def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePat # Close the file lFile.close() -def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBytes): +def AgentOSFileBinaryDataBytesCreate(inHostNameStr, inUserStr, inFilePathStr, inFileDataBytes, inGSettings=None): """ Create binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmition) @@ -216,7 +244,7 @@ def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFi :param inFileDataBytes: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lFileDataBase64Str = base64.b64encode(inFileDataBytes).decode("utf-8") lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) @@ -229,7 +257,7 @@ def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFi return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str): +def AgentOSFileBinaryDataBase64StrCreate(inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str, inGSettings=None): """ Create binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmission) @@ -240,7 +268,7 @@ def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, :param inFileDataBase64Str: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -252,7 +280,7 @@ def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileBinaryDataBase64StrAppend(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str): +def AgentOSFileBinaryDataBase64StrAppend(inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str, inGSettings = None): """ Append binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmission) @@ -263,7 +291,7 @@ def AgentOSFileBinaryDataBase64StrAppend(inGSettings, inHostNameStr, inUserStr, :param inFileDataBase64Str: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrAppend", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -276,7 +304,7 @@ def AgentOSFileBinaryDataBase64StrAppend(inGSettings, inHostNameStr, inUserStr, # Send text file to Agent (string) -def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataStr, inEncodingStr = "utf-8"): +def AgentOSFileTextDataStrCreate(inHostNameStr, inUserStr, inFilePathStr, inFileDataStr, inEncodingStr = "utf-8",inGSettings=None): """ Create text file by the string by the pyOpenRPA.Agent daemon process @@ -288,7 +316,7 @@ def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePa :param inEncodingStr: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileTextDataStrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -299,7 +327,7 @@ def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePa #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileBinaryDataBase64StrReceive(inGSettings, inHostNameStr, inUserStr, inFilePathStr): +def AgentOSFileBinaryDataBase64StrReceive(inHostNameStr, inUserStr, inFilePathStr, inGSettings = None): """ Read binary file and encode in base64 to transmit (safe for JSON transmition) @@ -309,7 +337,7 @@ def AgentOSFileBinaryDataBase64StrReceive(inGSettings, inHostNameStr, inUserStr, :param inFilePathStr: File path to read :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -320,7 +348,46 @@ def AgentOSFileBinaryDataBase64StrReceive(inGSettings, inHostNameStr, inUserStr, #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileTextDataStrReceive(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inEncodingStr="utf-8"): + +def AgentOSFileBinaryDataReceive(inHostNameStr, inUserStr, inFilePathStr): + """ + Read binary file from agent (synchronious) + + :param inGSettings: Global settings dict (singleton) + :param inHostNameStr: + :param inUserStr: + :param inFilePathStr: File path to read + :return: file data bytes + """ + lFileDataBytes = None + inGSettings = GSettingsGet() # Set the global settings + # Check thread + if OrchestratorIsInited() == False: + if inGSettings["Logger"]: inGSettings["Logger"].warning(f"AgentOSFileBinaryDataReceive run before orc init - activity will be append in the processor queue.") + lResult = { + "Def": AgentOSFileBinaryDataReceive, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList": [], # Args list + "ArgDict": {"inHostNameStr":inHostNameStr, "inUserStr":inUserStr, "inFilePathStr":inFilePathStr}, # Args dictionary + "ArgGSettings": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + inGSettings["ProcessorDict"]["ActivityList"].append(lResult) + else: # In processor - do execution + lActivityItemDict = { + "Def":"OSFileBinaryDataBase64StrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) + "ArgList":[], # Args list + "ArgDict":{"inFilePathStr":inFilePathStr}, # Args dictionary + "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + + #Send item in AgentDict for the futher data transmition + lGUIDStr = AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) + lFileBase64Str = AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if lFileBase64Str is not None: lFileDataBytes = base64.b64decode(lFileBase64Str) + return lFileDataBytes + +def AgentOSFileTextDataStrReceive(inHostNameStr, inUserStr, inFilePathStr, inEncodingStr="utf-8", inGSettings = None): """ Read text file in the agent GUI session @@ -331,7 +398,7 @@ def AgentOSFileTextDataStrReceive(inGSettings, inHostNameStr, inUserStr, inFileP :param inEncodingStr: Text file encoding. Default 'utf-8' :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileTextDataStrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -342,7 +409,7 @@ def AgentOSFileTextDataStrReceive(inGSettings, inHostNameStr, inUserStr, inFileP #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentProcessWOExeUpperUserListGet(inGSettings, inHostNameStr, inUserStr): +def AgentProcessWOExeUpperUserListGet(inHostNameStr, inUserStr, inGSettings = None): """ Return the process list only for the current user (where Agent is running) without .EXE in upper case. Can use in ActivityItem from Orchestrator to Agent @@ -351,7 +418,7 @@ def AgentProcessWOExeUpperUserListGet(inGSettings, inHostNameStr, inUserStr): :param inUserStr: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"ProcessWOExeUpperUserListGet", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -363,6 +430,14 @@ def AgentProcessWOExeUpperUserListGet(inGSettings, inHostNameStr, inUserStr): return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) # OS DEFS + +def OSLogoff(): + """ + Logoff the current orchestrator session + :return: + """ + os.system("shutdown /l") + def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ## """ Verify user credentials in windows. Return bool @@ -382,7 +457,7 @@ def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ## else: return True -def OSRemotePCRestart(inLogger, inHostStr, inForceBool=True): +def OSRemotePCRestart(inHostStr, inForceBool=True, inLogger = None): """ Send signal via power shell to restart remote PC ATTENTION: Orchestrator user need to have restart right on the Remote machine to restart PC. @@ -392,6 +467,7 @@ def OSRemotePCRestart(inLogger, inHostStr, inForceBool=True): :param inForceBool: True - send signal to force retart PC; False - else case :return: """ + if inLogger is None: inLogger = OrchestratorLoggerGet() lCMDStr = f"powershell -Command Restart-Computer -ComputerName {inHostStr}" if inForceBool == True: lCMDStr = lCMDStr + " -Force" OSCMD(inCMDStr=lCMDStr,inLogger=inLogger) @@ -405,7 +481,11 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inLogger = None): :param inLogger: :return: CMD result string """ + if inLogger is None: inLogger = OrchestratorLoggerGet() lResultStr = "" + # New feature + if inRunAsyncBool == True: + inCMDStr = f"start {inCMDStr}" # Subdef to listen OS result def _CMDRunAndListenLogs(inCMDStr, inLogger): lResultStr = "" @@ -443,6 +523,7 @@ def OrchestratorRestart(inGSettings=None): :param inGSettings: Global settings dict (singleton) """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings OrchestratorSessionSave(inGSettings=inGSettings) # Dump RDP List in file json if inGSettings is not None: lL = inGSettings["Logger"] @@ -451,6 +532,47 @@ def OrchestratorRestart(inGSettings=None): os.execl(sys.executable, os.path.abspath(__file__), *sys.argv) sys.exit(0) +def OrchestratorLoggerGet() -> logging.Logger: + """ + Get the logger from the Orchestrator + + :return: + """ + return GSettingsGet().get("Logger",None) + + +def OrchestratorScheduleGet() -> schedule: + """ + Get the schedule (schedule.readthedocs.io) from the Orchestrator + + Fro example you can use: + + .. code-block:: python + # One schedule threaded + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(lProcess.StatusCheckStart) + + #New schedule thread # See def description Orchestrator.OrchestratorThreadStart + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(Orchestrator.OrchestratorThreadStart,lProcess.StatusCheckStart) + + :return: schedule module. Example see here https://schedule.readthedocs.io/en/stable/examples.html + """ + if GSettingsGet().get("SchedulerDict",{}).get("Schedule",None) is None: + GSettingsGet()["SchedulerDict"]["Schedule"]=schedule + return GSettingsGet().get("SchedulerDict",{}).get("Schedule",None) + +def OrchestratorThreadStart(inDef, *inArgList, **inArgDict): + """ + Execute def in new thread and pass some args with list and dict types + + :param inDef: Python Def + :param inArgList: args as list + :param inArgDict: args as dict + :return: threading.Thread object + """ + lDefThread = threading.Thread(target=inDef,args=inArgList,kwargs=inArgDict) + lDefThread.start() + return lDefThread + def OrchestratorIsAdmin(): """ Check if Orchestrator process is running as administrator @@ -462,6 +584,24 @@ def OrchestratorIsAdmin(): except: return False +def OrchestratorIsInited() -> bool: + """Check if Orchestrator initial actions were processed + + :return: True - orc is already inited; False - else + :rtype: bool + """ + + return Core.IsOrchestratorInitialized(inGSettings=GSettingsGet()) + +def OrchestratorInitWait() -> None: + """Wait thread while orc will process initial action. + ATTENTION: DO NOT CALL THIS DEF IN THREAD WHERE ORCHESTRATOR MUST BE INITIALIZED - INFINITE LOOP + """ + lIntervalSecFloat = 0.5 + while not OrchestratorIsInited(): + time.sleep(lIntervalSecFloat) + + def OrchestratorRerunAsAdmin(): """ Check if not admin - then rerun orchestrator as administrator @@ -474,44 +614,132 @@ def OrchestratorRerunAsAdmin(): else: print(f"!SKIPPED! Already run as administrator!") -def OrchestratorSessionSave(inGSettings): +def OrchestratorPySearchInit(inGlobPatternStr, inDefStr = None, inDefArgNameGSettingsStr = None, inAsyncInitBool = False): + """ + Search the py files by the glob and do the safe init (in try except). Also add inited module in sys.modules as imported (module name = file name without extension). + You can init CP in async way! + .. code-block:: python + + # USAGE VAR 1 (without the def auto call) + # Autoinit control panels starts with CP_ + Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py") + + # USAGE VAR 2 (with the def auto call) - for the backward compatibility CP for the Orchestrator ver. < 1.2.7 + # Autoinit control panels starts with CP_ + Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings") + + # INFO: The code above will replace the code below + ## !!! For Relative import !!! CP Version Check + try: + sys.path.insert(0,os.path.abspath(os.path.join(r""))) + from ControlPanel import CP_VersionCheck + CP_VersionCheck.SettingsUpdate(inGSettings=gSettings) + except Exception as e: + gSettings["Logger"].exception(f"Exception when init CP. See below.") + + + :param inGlobPatternStr: example"..\\*\\*\\*X64*.cmd" + :param inDefStr: OPTIONAL The string name of the def. For backward compatibility if you need to auto call some def from initialized module + :param inDefArgNameGSettingsStr: OPTIONAL The name of the GSettings argument in def (if exists) + :param inAsyncInitBool: OPTIONAL True - init py modules in many threads - parallel execution. False (default) - sequence execution + :return: { "ModuleNameStr":{"PyPathStr": "", "Module": ...}, ...} + """ + + # # # # # # # # + def __execute__(inResultDict, inPyPathItemStr, inDefStr = None, inDefArgNameGSettingsStr = None): + try: + lPyPathItemStr = inPyPathItemStr + lModuleNameStr = os.path.basename(lPyPathItemStr)[0:-3] + lTechSpecification = importlib.util.spec_from_file_location(lModuleNameStr, lPyPathItemStr) + lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) + sys.modules[lModuleNameStr] = lTechModuleFromSpec # Add initialized module in sys - python will not init this module enought + lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) + lItemDict = {"ModuleNameStr": lModuleNameStr, "PyPathStr": lPyPathItemStr, "Module": lTechModuleFromSpec} + if lL: lL.info(f"Py module {lModuleNameStr} has been successfully initialized.") + inResultDict[lModuleNameStr]=lItemDict + # Backward compatibility to call def with gsettings when init + if inDefStr is not None and inDefStr is not "": + lDef = getattr(lTechModuleFromSpec, inDefStr) + lArgDict = {} + if inDefArgNameGSettingsStr is not None and inDefArgNameGSettingsStr is not "": + lArgDict = {inDefArgNameGSettingsStr:GSettingsGet()} + lDef(**lArgDict) + except Exception as e: + if lL: lL.exception(f"Exception when init the .py file {os.path.abspath(lPyPathItemStr)}") + # # # # # # # # + + lResultDict = {} + + lPyPathStrList = glob.glob(inGlobPatternStr) # get the file list + lL = OrchestratorLoggerGet() # get the logger + for lPyPathItemStr in lPyPathStrList: + if inAsyncInitBool == True: + # ASYNC EXECUTION + lThreadInit = threading.Thread(target=__execute__,kwargs={ + "inResultDict":lResultDict, "inPyPathItemStr": lPyPathItemStr, + "inDefStr": inDefStr, "inDefArgNameGSettingsStr": inDefArgNameGSettingsStr}, daemon=True) + lThreadInit.start() + else: + # SYNC EXECUTION + __execute__(inResultDict=lResultDict, inPyPathItemStr=lPyPathItemStr, inDefStr = inDefStr, inDefArgNameGSettingsStr = inDefArgNameGSettingsStr) + return lResultDict + +def OrchestratorSessionSave(inGSettings=None): """ Orchestrator session save in file + (from version 1.2.7) + _SessionLast_GSettings.pickle (binary) + + (above the version 1.2.7) _SessionLast_RDPList.json (encoding = "utf-8") _SessionLast_StorageDict.pickle (binary) :param inGSettings: Global settings dict (singleton) :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lL = inGSettings["Logger"] try: # Dump RDP List in file json - lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") - lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file - lFile.close() # Close the file - if inGSettings is not None: + #lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") + #lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file + #lFile.close() # Close the file + #if inGSettings is not None: + # if lL: lL.info( + # f"Orchestrator has dump the RDP list before the restart.") + ## _SessionLast_StorageDict.pickle (binary) + #if "StorageDict" in inGSettings: + # with open('_SessionLast_StorageDict.pickle', 'wb') as lFile: + # pickle.dump(inGSettings["StorageDict"], lFile) + # if lL: lL.info( + # f"Orchestrator has dump the StorageDict before the restart.") + + #SessionLast + lDumpDict = {"StorageDict":inGSettings["StorageDict"], "ManagersProcessDict":inGSettings["ManagersProcessDict"], + "RobotRDPActive":{"RDPList": inGSettings["RobotRDPActive"]["RDPList"]}} + with open('_SessionLast_GSettings.pickle', 'wb') as lFile: + pickle.dump(lDumpDict, lFile) if lL: lL.info( - f"Orchestrator has dump the RDP list before the restart.") - # _SessionLast_StorageDict.pickle (binary) - if "StorageDict" in inGSettings: - with open('_SessionLast_StorageDict.pickle', 'wb') as lFile: - pickle.dump(inGSettings["StorageDict"], lFile) - if lL: lL.info( - f"Orchestrator has dump the StorageDict before the restart.") + f"Orchestrator has dump the GSettings (new dump mode from v1.2.7) before the restart.") + except Exception as e: if lL: lL.exception(f"Exception when dump data before restart the Orchestrator") return True -def OrchestratorSessionRestore(inGSettings): +def OrchestratorSessionRestore(inGSettings=None): """ - Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working directory. if exist - load into gsettings - # _SessionLast_StorageDict.pickle (binary) + Check _SessioLast... files in working directory. if exist - load into gsettings + (from version 1.2.7) + _SessionLast_GSettings.pickle (binary) + + (above the version 1.2.7) _SessionLast_RDPList.json (encoding = "utf-8") _SessionLast_StorageDict.pickle (binary) :param inGSettings: Global settings dict (singleton) :return: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lL = inGSettings.get("Logger",None) # _SessionLast_RDPList.json (encoding = "utf-8") if os.path.exists("_SessionLast_RDPList.json"): @@ -531,6 +759,18 @@ def OrchestratorSessionRestore(inGSettings): in2Dict=lStorageDictDumpDict) # Merge dict 2 into dict 1 if lL: lL.warning(f"StorageDict was restored from previous Orchestrator session") os.remove("_SessionLast_StorageDict.pickle") # remove the temp file + # _SessionLast_Gsettings.pickle (binary) + if os.path.exists("_SessionLast_GSettings.pickle"): + if "StorageDict" not in inGSettings: + inGSettings["StorageDict"] = {} + if "ManagersProcessDict" not in inGSettings: + inGSettings["ManagersProcessDict"] = {} + with open('_SessionLast_GSettings.pickle', 'rb') as lFile: + lStorageDictDumpDict = pickle.load(lFile) + Server.__ComplexDictMerge2to1Overwrite__(in1Dict=inGSettings, + in2Dict=lStorageDictDumpDict) # Merge dict 2 into dict 1 + if lL: lL.warning(f"GSettings was restored from previous Orchestrator session") + os.remove("_SessionLast_GSettings.pickle") # remove the temp file def UACKeyListCheck(inRequest, inRoleKeyList) -> bool: """ @@ -551,7 +791,7 @@ def UACUserDictGet(inRequest) -> dict: """ return inRequest.UserRoleHierarchyGet() # get the Hierarchy -def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inURLList=None, inRoleHierarchyAllowedDict=None): +def UACUpdate(inADLoginStr, inADStr="", inADIsDefaultBool=True, inURLList=None, inRoleHierarchyAllowedDict=None, inGSettings = None): """ Update user access (UAC) @@ -562,6 +802,7 @@ def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inU :param inURLList: :param inRoleHierarchyAllowedDict: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lUserTurple = (inADStr.upper(),inADLoginStr.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] if inURLList is None: inURLList = [] # Check if None if inRoleHierarchyAllowedDict is None: inRoleHierarchyAllowedDict = {} # Check if None @@ -586,13 +827,14 @@ def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inU # Case add default domain + user inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"].update({("",inADLoginStr.upper()):lRuleDomainUserDict}) -def UACSuperTokenUpdate(inGSettings, inSuperTokenStr): +def UACSuperTokenUpdate(inSuperTokenStr, inGSettings=None): """ Add supertoken for the all access (it is need for the robot communication without human) :param inGSettings: Global settings dict (singleton) :param inSuperTokenStr: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lLoginStr = "SUPERTOKEN" UACUpdate(inGSettings=inGSettings, inADLoginStr=lLoginStr) inGSettings["ServerDict"]["AccessUsers"]["AuthTokensDict"].update( @@ -604,7 +846,7 @@ def UACSuperTokenUpdate(inGSettings, inSuperTokenStr): # # # # # # # # # # # # # # # # # # # # # # # -def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream"): +def WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream", inGSettings = None, inUACBool = None): """ Connect URL to DEF "inMethodStr":"GET|POST", @@ -614,12 +856,14 @@ def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, "inDef": None #Function with str result :param inGSettings: Global settings dict (singleton) - :param inMethodStr: - :param inURLStr: - :param inMatchTypeStr: - :param inDef: - :param inContentTypeStr: - """ + :param inMethodStr: "GET|POST", + :param inURLStr: example "/index", #URL of the request + :param inMatchTypeStr: #"BeginWith|Contains|Equal|EqualCase", + :param inDef: def arg allowed list: 2:[inRequest, inGSettings], 1: [inRequest], 0: [] + :param inContentTypeStr: default: "application/octet-stream" + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user + """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lURLItemDict = { "Method": inMethodStr.upper(), "URL": inURLStr, # URL of the request @@ -628,25 +872,29 @@ def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, #"ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", # Absolute or relative path "ResponseContentType": inContentTypeStr, #HTTP Content-type - "ResponseDefRequestGlobal": inDef #Function with str result + "ResponseDefRequestGlobal": inDef, #Function with str result + "UACBool": inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) -def WebURLConnectFolder(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr): +def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings = None, inUACBool = None): """ Connect URL to Folder "inMethodStr":"GET|POST", "inURLStr": "/Folder/", #URL of the request "inMatchTypeStr": "", #"BeginWith|Contains|Equal|EqualCase", "inFolderPathStr": "", #Absolute or relative path + "inUACBool" :param inGSettings: Global settings dict (singleton) :param inMethodStr: :param inURLStr: :param inMatchTypeStr: :param inFolderPathStr: + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check if last symbol is "/" - append if not exist lFolderPathStr = os.path.abspath(inFolderPathStr) if lFolderPathStr[-1]!="/":lFolderPathStr+="/" @@ -659,11 +907,12 @@ def WebURLConnectFolder(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFo "ResponseFolderPath": lFolderPathStr, # Absolute or relative path "ResponseContentType": "application/octet-stream", #HTTP Content-type #"ResponseDefRequestGlobal": inDef #Function with str result + "UACBool": inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) -def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream"): +def WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream", inGSettings = None, inUACBool = None): """ Connect URL to File "inMethodStr":"GET|POST", @@ -677,7 +926,9 @@ def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFile :param inMatchTypeStr: :param inFilePathStr: :param inContentTypeStr: + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lURLItemDict = { "Method": inMethodStr.upper(), "URL": inURLStr, # URL of the request @@ -686,10 +937,11 @@ def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFile #"ResponseFolderPath": os.path.abspath(inFilePathStr), # Absolute or relative path "ResponseContentType": inContentTypeStr, #HTTP Content-type #"ResponseDefRequestGlobal": inDef #Function with str result + "UACBool":inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) -def WebListenCreate(inGSettings, inServerKeyStr="Default", inAddressStr="", inPortInt=80, inCertFilePEMPathStr=None, inKeyFilePathStr=None): +def WebListenCreate(inServerKeyStr="Default", inAddressStr="", inPortInt=80, inCertFilePEMPathStr=None, inKeyFilePathStr=None, inGSettings = None): """ Create listen interface for the web server @@ -700,7 +952,7 @@ def WebListenCreate(inGSettings, inServerKeyStr="Default", inAddressStr="", inPo :param inKeyFilePathStr: Path to the private key file :return: """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings inGSettings["ServerDict"]["ListenDict"][inServerKeyStr]={ "AddressStr":inAddressStr, "PortInt":inPortInt, @@ -710,7 +962,7 @@ def WebListenCreate(inGSettings, inServerKeyStr="Default", inAddressStr="", inPo } -def WebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None): +def WebCPUpdate(inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None, inGSettings = None): """ Add control panel HTML, JSON generator or JS when page init @@ -720,21 +972,16 @@ def WebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDe :param inJSONGeneratorDef: :param inJSInitGeneratorDef: """ - # Create Struct if the re is current key - if inCPKeyStr not in inGSettings["CPDict"]: - inGSettings["CPDict"][inCPKeyStr] = {"HTMLRenderDef": None,"JSONGeneratorDef": None, "JSInitGeneratorDef": None} + lCPManager = Managers.ControlPanel(inControlPanelNameStr=inCPKeyStr, inRefreshHTMLJinja2TemplatePathStr=None) # CASE HTMLRender - if inHTMLRenderDef is not None: - inGSettings["CPDict"][inCPKeyStr]["HTMLRenderDef"]=inHTMLRenderDef + if inHTMLRenderDef is not None: lCPManager.mBackwardCompatibilityHTMLDef = inHTMLRenderDef # CASE JSONGenerator - if inJSONGeneratorDef is not None: - inGSettings["CPDict"][inCPKeyStr]["JSONGeneratorDef"] = inJSONGeneratorDef + if inJSONGeneratorDef is not None: lCPManager.mBackwardCompatibilityJSONDef = inJSONGeneratorDef # CASE JSInitGeneratorDef - if inJSInitGeneratorDef is not None: - inGSettings["CPDict"][inCPKeyStr]["JSInitGeneratorDef"] = inJSInitGeneratorDef + if inJSInitGeneratorDef is not None: lCPManager.mBackwardCompatibilityJSDef = inJSInitGeneratorDef -def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): +def WebAuditMessageCreate(inRequest=None, inOperationCodeStr="-", inMessageStr="-"): """ Create message string with request user details (IP, Login etc...). Very actual for IT security in big company. @@ -751,12 +998,13 @@ def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): # Log the WebAudit message lLogger.info(lWebAuditMessageStr) - :param inRequest: HTTP request handler + :param inRequest: HTTP request handler. Optional if call def from request thread :param inOperationCodeStr: operation code in string format (actual for IT audit in control panels) :param inMessageStr: additional message after :return: format "WebAudit :: DOMAIN\\USER@101.121.123.12 :: operation code :: message" """ try: + if inRequest is None: inRequest = WebRequestGet() lClientIPStr = inRequest.client_address[0] lUserDict = WebUserInfoGet(inRequest=inRequest) lDomainUpperStr = lUserDict["DomainUpperStr"] @@ -767,45 +1015,58 @@ def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): lResultStr = inMessageStr return lResultStr -def WebRequestParseBodyBytes(inRequest): +def WebRequestParseBodyBytes(inRequest=None): """ Extract the body in bytes from the request - :param inRequest: inRequest from the server + :param inRequest: inRequest from the server. Optional if call def from request thread :return: Bytes or None """ + if inRequest is None: inRequest = WebRequestGet() lBodyBytes=None if inRequest.headers.get('Content-Length') is not None: lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) lBodyBytes = inRequest.rfile.read(lInputByteArrayLength) return lBodyBytes -def WebRequestParseBodyStr(inRequest): +def WebRequestParseBodyStr(inRequest=None): """ Extract the body in str from the request - :param inRequest: inRequest from the server + :param inRequest: inRequest from the server. Optional if call def from request thread :return: str or None """ - + if inRequest is None: inRequest = WebRequestGet() return WebRequestParseBodyBytes(inRequest=inRequest).decode('utf-8') -def WebRequestParseBodyJSON(inRequest): +def WebRequestParseBodyJSON(inRequest=None): """ Extract the body in dict/list from the request - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: dict or list """ + if inRequest is None: inRequest = WebRequestGet() return json.loads(WebRequestParseBodyStr(inRequest=inRequest)) -def WebRequestParseFile(inRequest): +def WebRequestParsePath(inRequest=None): + """ + Parse the request - extract the url. Example: /pyOpenRPA/Debugging/DefHelper/... + + :param inRequest: inRequest from the server. Optional if call def from request thread + :return: Str, Example: /pyOpenRPA/Debugging/DefHelper/... + """ + if inRequest is None: inRequest = WebRequestGet() + return urllib.parse.unquote(inRequest.path) + +def WebRequestParseFile(inRequest=None): """ Parse the request - extract the file (name, body in bytes) - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: (FileNameStr, FileBodyBytes) or (None, None) """ + if inRequest is None: inRequest = WebRequestGet() lResultTurple=(None,None) if inRequest.headers.get('Content-Length') is not None: lInputByteArray = WebRequestParseBodyBytes(inRequest=inRequest) @@ -825,41 +1086,90 @@ def WebRequestParseFile(inRequest): return lResultTurple -def WebUserInfoGet(inRequest): +def WebRequestResponseSend(inResponeStr, inRequest=None): + """ + Send response for the request + + :param inRequest: inRequest from the server. Optional if call def from request thread + :return: + """ + if inRequest is None: inRequest = WebRequestGet() + inRequest.OpenRPAResponseDict["Body"] = bytes(inResponeStr, "utf8") + + +def WebRequestGet(): + """ + Return the web request instance if current thread was created by web request from client. else return None + + """ + lCurrentThread = threading.current_thread() + if hasattr(lCurrentThread, "request"): + return lCurrentThread.request + +def WebUserInfoGet(inRequest=None): """ Return User info about request - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: {"DomainUpperStr": "", "UserNameUpperStr": ""} """ + if inRequest is None: inRequest = WebRequestGet() lDomainUpperStr = inRequest.OpenRPA["Domain"].upper() lUserUpperStr = inRequest.OpenRPA["User"].upper() return {"DomainUpperStr": lDomainUpperStr, "UserNameUpperStr": lUserUpperStr} -def WebUserIsSuperToken(inRequest, inGSettings): +def WebUserIsSuperToken(inRequest = None, inGSettings = None): """ Return bool if request is authentificated with supetoken (token which is never expires) - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :param inGSettings: Global settings dict (singleton) :return: bool True - is supertoken; False - is not supertoken """ + if inRequest is None: inRequest = WebRequestGet() + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lIsSuperTokenBool = False # Get Flag is supertoken (True|False) lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inRequest.OpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False) return lIsSuperTokenBool -def WebUserUACHierarchyGet(inRequest): +def WebUserUACHierarchyGet(inRequest = None): """ Return User UAC Hierarchy DICT Return {...} - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: UAC Dict {} """ + if inRequest is None: inRequest = WebRequestGet() return inRequest.UserRoleHierarchyGet() + ## GSettings defs -def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None): + +from . import SettingsTemplate + +GSettings = SettingsTemplate.Create(inModeStr = "BASIC") +# Modules alias for pyOpenRPA.Orchestrator and pyOpenRPA.Orchestrator.__Orchestrator__ +lCurrentModule = sys.modules[__name__] +if __name__ == "pyOpenRPA.Orchestrator" and "pyOpenRPA.Orchestrator.__Orchestrator__" not in sys.modules: + sys.modules["pyOpenRPA.Orchestrator.__Orchestrator__"] = lCurrentModule +if __name__ == "pyOpenRPA.Orchestrator.__Orchestrator__" and "pyOpenRPA.Orchestrator" not in sys.modules: + sys.modules["pyOpenRPA.Orchestrator"] = lCurrentModule + +def GSettingsGet(inGSettings=None): + """ + Get the GSettings from the singleton module. + + :param inGSettings: You can pass some GSettings to check if it equal to base gsettings. If not equal - def will merge it + :return: GSettings + """ + global GSettings # identify the global variable + # Merge dictionaries if some new dictionary has come + if inGSettings is not None and GSettings is not inGSettings: + GSettings = Server.__ComplexDictMerge2to1Overwrite__(in1Dict = inGSettings, in2Dict = GSettings) + return GSettings # Return the result + +def GSettingsKeyListValueSet(inValue, inKeyList=None, inGSettings = None): """ Set value in GSettings by the key list @@ -868,6 +1178,7 @@ def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None): :param inKeyList: :return: bool """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -880,7 +1191,7 @@ def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None): lDict[inKeyList[-1]] = inValue #Set value return True -def GSettingsKeyListValueGet(inGSettings, inKeyList=None): +def GSettingsKeyListValueGet(inKeyList=None, inGSettings = None): """ Get the value from the GSettings by the key list @@ -888,6 +1199,7 @@ def GSettingsKeyListValueGet(inGSettings, inKeyList=None): :param inKeyList: :return: value any type """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -899,7 +1211,7 @@ def GSettingsKeyListValueGet(inGSettings, inKeyList=None): lDict=lDict[lItem2] return lDict.get(inKeyList[-1],None) -def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None): +def GSettingsKeyListValueAppend(inValue, inKeyList=None, inGSettings = None): """ Append value in GSettings by the key list @@ -926,6 +1238,7 @@ def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None): :param inKeyList: List of the nested keys (see example) :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -938,7 +1251,7 @@ def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None): lDict[inKeyList[-1]].append(inValue) #Set value return True -def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None): +def GSettingsKeyListValueOperatorPlus(inValue, inKeyList=None, inGSettings = None): """ Execute plus operation between 2 lists (1:inValue and 2:gSettings by the inKeyList) @@ -968,6 +1281,7 @@ def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None): :param inKeyList: List of the nested keys (see example) :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -980,10 +1294,31 @@ def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None): lDict[inKeyList[-1]] += inValue #Set value return True -def ProcessorAliasDefCreate(inGSettings, inDef, inAliasStr=None): +def StorageRobotExists(inRobotNameStr): + """ + Check if robot storage exists + + :param inRobotNameStr: Robot name (case sensitive) + :return: True - robot storage exist; False - does not exist + """ + return inRobotNameStr in GSettingsGet()["StorageDict"] + +def StorageRobotGet(inRobotNameStr): + """ + Get the robot storage by the robot name. If Robot storage is not exist - function will create it + + :param inRobotNameStr: Robot name (case sensitive) + :return: Dict + """ + if inRobotNameStr not in GSettingsGet()["StorageDict"]: + GSettingsGet()["StorageDict"][inRobotNameStr]={} + return GSettingsGet()["StorageDict"][inRobotNameStr] + +def ProcessorAliasDefCreate(inDef, inAliasStr=None, inGSettings = None): """ Create alias for def (can be used in ActivityItem in field Def) !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + Deprecated. See ActivityItemDefAliasCreate .. code-block:: python @@ -1003,20 +1338,13 @@ def ProcessorAliasDefCreate(inGSettings, inDef, inAliasStr=None): :param inAliasStr: String alias for associated def :return: str Alias string (Alias can be regenerated if previous alias was occupied) """ - #TODO Pay attention - New alias can be used too - need to create more complex algorythm to create new alias! - lL = inGSettings["Logger"] - if inAliasStr is None: inAliasStr = str(inDef) - # Check if key is not exists - if inAliasStr in inGSettings["ProcessorDict"]["AliasDefDict"]: - inAliasStr = str(inDef) - if lL: lL.warning(f"Orchestrator.ProcessorAliasDefCreate: Alias {inAliasStr} already exists in alias dictionary. Another alias will be generated and returned") - inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef - return inAliasStr + return ActivityItemDefAliasCreate(inDef=inDef, inAliasStr=inAliasStr, inGSettings = inGSettings) -def ProcessorAliasDefUpdate(inGSettings, inDef, inAliasStr): +def ProcessorAliasDefUpdate(inDef, inAliasStr, inGSettings = None): """ Update alias for def (can be used in ActivityItem in field Def). !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + Deprecated. See ActivityItemDefAliasUpdate .. code-block:: python @@ -1036,11 +1364,59 @@ def ProcessorAliasDefUpdate(inGSettings, inDef, inAliasStr): :param inAliasStr: String alias for associated def :return: str Alias string """ - if callable(inDef): inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef - else: raise Exception(f"pyOpenRPA Exception: You can't use Orchestrator.ProcessorAliasDefUpdate with arg 'inDef' string value. inDef is '{inDef}', inAliasStr is '{inAliasStr}'") - return inAliasStr + return ActivityItemDefAliasUpdate(inDef=inDef, inAliasStr=inAliasStr, inGSettings = inGSettings) -def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inGUIDStr = None): +# ActivityItem defs +def ActivityItemHelperDefList(inDefQueryStr=None): + """ + Create list of the available Def names in activity item. You can use query def filter via arg inDefQueryStr + + :param inDefStr: + :return: ["ActivityItemDefAliasUpdate", "ActivityItemDefAliasCreate", etc...] + """ + lResultList = [] + if inDefQueryStr is not None: # do search alg + for lKeyStr in GSettingsGet()["ProcessorDict"]["AliasDefDict"]: + if inDefQueryStr.upper() in lKeyStr.upper(): + lResultList.append(lKeyStr) + else: + for lKeyStr in GSettingsGet()["ProcessorDict"]["AliasDefDict"]: + lResultList.append(lKeyStr) + return lResultList + +def ActivityItemHelperDefAutofill(inDef): + """ + Detect def by the name and prepare the activity item dict with values. + + :param inDef: + :return: + """ + lResultDict = { + "Def": None, + "ArgList": [], + "ArgDict": {}, + "ArgGSettingsStr": None, + "ArgLoggerStr": None + } + lResultDict["Def"] = inDef + lGS = GSettingsGet() + if inDef in lGS["ProcessorDict"]["AliasDefDict"]: + lDefSignature = inspect.signature(lGS["ProcessorDict"]["AliasDefDict"][inDef]) + for lItemKeyStr in lDefSignature.parameters: + lItemValue = lDefSignature.parameters[lItemKeyStr] + # Check if arg name contains "GSetting" or "Logger" + if "GSETTING" in lItemKeyStr.upper(): + lResultDict["ArgGSettingsStr"] = lItemKeyStr + elif "LOGGER" in lItemKeyStr.upper(): + lResultDict["ArgLoggerStr"] = lItemKeyStr + else: + if lItemValue.default is inspect._empty: + lResultDict["ArgDict"][lItemKeyStr] = None + else: + lResultDict["ArgDict"][lItemKeyStr] = lItemValue.default + return lResultDict + +def ActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inGUIDStr = None, inThreadBool = False): """ Create activity item. Activity item can be used as list item in ProcessorActivityItemAppend or in Processor.ActivityListExecute. @@ -1052,7 +1428,7 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet # EXAMPLE 1 def TestDef(inArg1Str, inGSettings, inLogger): pass - lActivityItem = Orchestrator.ProcessorActivityItemCreate( + lActivityItem = Orchestrator.ActivityItemCreate( inDef = TestDef, inArgList=[], inArgDict={"inArg1Str": "ArgValueStr"}, @@ -1070,11 +1446,11 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet # EXAMPLE 2 def TestDef(inArg1Str): pass - Orchestrator.ProcessorAliasDefUpdate( + Orchestrator.ActivityItemDefAliasUpdate( inGSettings = gSettings, inDef = TestDef, inAliasStr="TestDefAlias") - lActivityItem = Orchestrator.ProcessorActivityItemCreate( + lActivityItem = Orchestrator.ActivityItemCreate( inDef = "TestDefAlias", inArgList=[], inArgDict={"inArg1Str": "ArgValueStr"}, @@ -1095,6 +1471,7 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet :param inArgGSettingsStr: Name of def argument of the GSettings dict :param inArgLoggerStr: Name of def argument of the logging object :param inGUIDStr: GUID which you can specify. If None the GUID will be generated + :param inThreadBool: True - execute ActivityItem in new thread; False - in processor thread :return: {} """ # Work about GUID in Activity items @@ -1108,11 +1485,158 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet "ArgDict":inArgDict, # Args dictionary "ArgGSettings": inArgGSettingsStr, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": inArgLoggerStr, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) - "GUIDStr": inGUIDStr + "GUIDStr": inGUIDStr, + "ThreadBool": inThreadBool } return lActivityItemDict -def ProcessorActivityItemAppend(inGSettings, inDef=None, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inActivityItemDict=None): + +def ActivityItemDefAliasCreate(inDef, inAliasStr=None, inGSettings = None): + """ + Create alias for def (can be used in ActivityItem in field Def) + !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + + .. code-block:: python + + # USAGE + from pyOpenRPA import Orchestrator + + def TestDef(): + pass + lAliasStr = Orchestrator.ActivityItemDefAliasCreate( + inGSettings = gSettings, + inDef = TestDef, + inAliasStr="TestDefAlias") + # Now you can call TestDef by the alias from var lAliasStr with help of ActivityItem (key Def = lAliasStr) + + :param inGSettings: Global settings dict (singleton) + :param inDef: Def + :param inAliasStr: String alias for associated def + :return: str Alias string (Alias can be regenerated if previous alias was occupied) + """ + #TODO Pay attention - New alias can be used too - need to create more complex algorythm to create new alias! + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings + lL = inGSettings["Logger"] + if inAliasStr is None: inAliasStr = str(inDef) + # Check if key is not exists + if inAliasStr in inGSettings["ProcessorDict"]["AliasDefDict"]: + inAliasStr = str(inDef) + if lL: lL.warning(f"Orchestrator.ProcessorAliasDefCreate: Alias {inAliasStr} already exists in alias dictionary. Another alias will be generated and returned") + inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef + return inAliasStr + +def ActivityItemDefAliasModulesLoad(): + """ + Load all def from sys.modules... in ActivityItem def alias dict + + :return: None + """ + lL = OrchestratorLoggerGet() + lL.info(f"ActivityItem aliases: start to load sys.modules") + lSysModulesSnapshot = copy.copy(sys.modules) # Actual when start from jupyter + for lModuleItemStr in lSysModulesSnapshot: + lModuleItem = lSysModulesSnapshot[lModuleItemStr] + for lDefItemStr in dir(lModuleItem): + try: + lDefItem = getattr(lModuleItem,lDefItemStr) + if callable(lDefItem) and not lDefItemStr.startswith("_"): + ActivityItemDefAliasCreate(inDef=lDefItem, inAliasStr=f"{lModuleItemStr}.{lDefItemStr}") + except ModuleNotFoundError: + pass + lL.info(f"ActivityItem aliases: finish to load sys.modules") + +def ActivityItemDefAliasUpdate(inDef, inAliasStr, inGSettings = None): + """ + Update alias for def (can be used in ActivityItem in field Def). + !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + + .. code-block:: python + + # USAGE + from pyOpenRPA import Orchestrator + + def TestDef(): + pass + Orchestrator.ActivityItemDefAliasUpdate( + inGSettings = gSettings, + inDef = TestDef, + inAliasStr="TestDefAlias") + # Now you can call TestDef by the alias "TestDefAlias" with help of ActivityItem (key Def = "TestDefAlias") + + :param inGSettings: Global settings dict (singleton) + :param inDef: Def + :param inAliasStr: String alias for associated def + :return: str Alias string + """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings + if callable(inDef): inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef + else: raise Exception(f"pyOpenRPA Exception: You can't use Orchestrator.ActivityItemDefAliasUpdate with arg 'inDef' string value. inDef is '{inDef}', inAliasStr is '{inAliasStr}'") + return inAliasStr + + + +def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inGUIDStr = None, inThreadBool = False): + """ + Create activity item. Activity item can be used as list item in ProcessorActivityItemAppend or in Processor.ActivityListExecute. + Deprecated. See ActivityItemCreate + .. code-block:: python + + # USAGE + from pyOpenRPA import Orchestrator + + # EXAMPLE 1 + def TestDef(inArg1Str, inGSettings, inLogger): + pass + lActivityItem = Orchestrator.ProcessorActivityItemCreate( + inDef = TestDef, + inArgList=[], + inArgDict={"inArg1Str": "ArgValueStr"}, + inArgGSettingsStr = "inGSettings", + inArgLoggerStr = "inLogger") + # lActivityItem: + # { + # "Def":TestDef, + # "ArgList":inArgList, + # "ArgDict":inArgDict, + # "ArgGSettings": "inArgGSettings", + # "ArgLogger": "inLogger" + # } + + # EXAMPLE 2 + def TestDef(inArg1Str): + pass + Orchestrator.ProcessorAliasDefUpdate( + inGSettings = gSettings, + inDef = TestDef, + inAliasStr="TestDefAlias") + lActivityItem = Orchestrator.ProcessorActivityItemCreate( + inDef = "TestDefAlias", + inArgList=[], + inArgDict={"inArg1Str": "ArgValueStr"}, + inArgGSettingsStr = None, + inArgLoggerStr = None) + # lActivityItem: + # { + # "Def":"TestDefAlias", + # "ArgList":inArgList, + # "ArgDict":inArgDict, + # "ArgGSettings": None, + # "ArgLogger": None + # } + + :param inDef: def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + :param inArgList: Args list for the Def + :param inArgDict: Args dict for the def + :param inArgGSettingsStr: Name of def argument of the GSettings dict + :param inArgLoggerStr: Name of def argument of the logging object + :param inGUIDStr: GUID which you can specify. If None the GUID will be generated + :param inThreadBool: True - execute ActivityItem in new thread; False - in processor thread + :return: {} + """ + return ActivityItemCreate(inDef=inDef, inArgList=inArgList, inArgDict=inArgDict, inArgGSettingsStr=inArgGSettingsStr, inArgLoggerStr=inArgLoggerStr, + inGUIDStr=inGUIDStr, inThreadBool=inThreadBool) + +def ProcessorActivityItemAppend(inGSettings = None, inDef=None, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inActivityItemDict=None): """ Create and add activity item in processor queue. @@ -1160,6 +1684,7 @@ def ProcessorActivityItemAppend(inGSettings, inDef=None, inArgList=None, inArgDi :param inActivityItemDict: Fill if you already have ActivityItemDict (don't fill inDef, inArgList, inArgDict, inArgGSettingsStr, inArgLoggerStr) :return ActivityItem GUIDStr """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inActivityItemDict is None: if inArgList is None: inArgList=[] if inArgDict is None: inArgDict={} @@ -1344,7 +1869,7 @@ def ProcessListGet(inProcessNameWOExeList=None): return lResult -def ProcessDefIntervalCall(inGSettings, inDef, inIntervalSecFloat, inIntervalAsyncBool=False, inDefArgList=None, inDefArgDict=None, inDefArgGSettingsNameStr=None, inDefArgLoggerNameStr=None, inExecuteInNewThreadBool=True, inLogger=None): +def ProcessDefIntervalCall(inDef, inIntervalSecFloat, inIntervalAsyncBool=False, inDefArgList=None, inDefArgDict=None, inDefArgGSettingsNameStr=None, inDefArgLoggerNameStr=None, inExecuteInNewThreadBool=True, inLogger=None, inGSettings = None): """ Use this procedure if you need to run periodically some def. Set def, args, interval and enjoy :) @@ -1360,6 +1885,8 @@ def ProcessDefIntervalCall(inGSettings, inDef, inIntervalSecFloat, inIntervalAsy :param inLogger: logging def if some case is appear :return: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings + if inLogger is None: inLogger = OrchestratorLoggerGet() #Some edits on start if inDefArgDict is None: inDefArgDict = {} if inDefArgList is None: inDefArgList = [] @@ -1448,6 +1975,7 @@ def PythonStart(inModulePathStr, inDefNameStr, inArgList=None, inArgDict=None, i :param inLogger: Logger instance to log some information when PythonStart def is running :return: None """ + if inLogger is None: inLogger = OrchestratorLoggerGet() if inArgList is None: inArgList=[] if inArgDict is None: inArgDict={} try: @@ -1461,7 +1989,7 @@ def PythonStart(inModulePathStr, inDefNameStr, inArgList=None, inArgDict=None, i # Scheduler # # # # # # # # # # # # # # # # # # # # # # # -def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekdayList=None, inActivityList=None): +def SchedulerActivityTimeAddWeekly(inTimeHHMMStr="23:55:", inWeekdayList=None, inActivityList=None, inGSettings = None): """ Add activity item list in scheduler. You can set weekday list and set time when launch. Activity list will be executed at planned time/day. @@ -1492,6 +2020,7 @@ def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekda :param inActivityList: Activity list structure :return: None """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inWeekdayList is None: inWeekdayList=[0,1,2,3,4,5,6] if inActivityList is None: inActivityList=[] Processor.__ActivityListVerify__(inActivityList=inActivityList) # DO VERIFICATION FOR THE inActivityList @@ -1508,7 +2037,7 @@ def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekda # # # # # # # # # # # # # # # # # # # # # # # def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortInt = 3389, inWidthPXInt = 1680, inHeightPXInt = 1050, - inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None): + inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None, inRedirectClipboardBool=True): """ Create RDP connect dict item/ Use it connect/reconnect (Orchestrator.RDPSessionConnect) @@ -1531,6 +2060,7 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn # "Host": "127.0.0.1", "Port": "3389", "Login": "USER_99", "Password": "USER_PASS_HERE", # "Screen": { "Width": 1680, "Height": 1050, "FlagUseAllMonitors": False, "DepthBit": "32" }, # "SharedDriveList": ["c"], + # "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else # ###### Will updated in program ############ # "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" # "SessionIsWindowExistBool": False, "SessionIsWindowResponsibleBool": False, "SessionIsIgnoredBool": False @@ -1545,6 +2075,7 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn :param inUseBothMonitorBool: True - connect to the RDP with both monitors. False - else case :param inDepthBitInt: Remote desktop bitness. Available: 32 or 24 or 16 or 15, example 32 :param inSharedDriveList: Host local disc to connect to the RDP session. Example: ["c", "d"] + :param inRedirectClipboardBool: # True - share clipboard to RDP; False - else :return: { "Host": inHostStr, # Host address, example "77.77.22.22" @@ -1559,6 +2090,7 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn "DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32" }, "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"] + "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, @@ -1570,6 +2102,8 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn """ if inSharedDriveList is None: inSharedDriveList = ["c"] + if inPortInt is None: inPortInt = 3389 + if inRedirectClipboardBool is None: inRedirectClipboardBool = True lRDPTemplateDict= { # Init the configuration item "Host": inHostStr, # Host address, example "77.77.22.22" "Port": str(inPortInt), # RDP Port, example "3389" @@ -1582,7 +2116,8 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn "FlagUseAllMonitors": inUseBothMonitorBool, # True or False, example False "DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32" }, - "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"] + "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"], + "RedirectClipboardBool": inRedirectClipboardBool, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, @@ -1607,7 +2142,7 @@ def RDPSessionDublicatesResolve(inGSettings): #for lItemKeyStr in inGSettings["RobotRDPActive"]["RDPList"]: # lItemDict = inGSettings["RobotRDPActive"]["RDPList"][lItemKeyStr] -def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None): +def RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings = None, inRedirectClipboardBool=True): """ Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if RDP key is already exists 2 way of the use @@ -1638,6 +2173,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i :param inPasswordStr: Backward compatibility from Orchestrator v 1.1.20. Use inRDPTemplateDict :return: True every time :) """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -1645,7 +2181,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i "Def": RDPSessionConnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inRDPTemplateDict":inRDPTemplateDict, "inHostStr": inHostStr, "inPortStr": inPortStr, - "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr}, # Args dictionary + "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr, "inRedirectClipboardBool": inRedirectClipboardBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } @@ -1656,7 +2192,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i # Var 2 - backward compatibility if lRDPConfigurationItem is None: lRDPConfigurationItem = RDPTemplateCreate(inLoginStr=inLoginStr, inPasswordStr=inPasswordStr, - inHostStr=inHostStr, inPortInt = int(inPortStr)) # ATTENTION - dont connect if RDP session is exist + inHostStr=inHostStr, inPortInt = int(inPortStr), inRedirectClipboardBool=inRedirectClipboardBool) # ATTENTION - dont connect if RDP session is exist # Start the connect if inRDPSessionKeyStr not in inGSettings["RobotRDPActive"]["RDPList"]: inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem # Add item in RDPList @@ -1666,7 +2202,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP session was not created because it is alredy exists in the RDPList. Use RDPSessionReconnect if you want to update RDP configuration.") return True -def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None): +def RDPSessionDisconnect(inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None, inGSettings = None): """ Disconnect the RDP session and stop monitoring it. @@ -1689,6 +2225,7 @@ def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessW Orchestrator look processes on the current machine :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inBreakTriggerProcessWOExeList is None: inBreakTriggerProcessWOExeList = [] # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): @@ -1713,7 +2250,7 @@ def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessW Connector.SystemRDPWarningClickOk() # Click all warning messages return True -def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None): +def RDPSessionReconnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inGSettings = None): """ Reconnect the RDP session @@ -1737,6 +2274,7 @@ def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None) :param inRDPTemplateDict: RDP configuration dict with settings (see def Orchestrator.RDPTemplateCreate) :return: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -1759,7 +2297,7 @@ def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None) Connector.Session(lRDPConfigurationItem) return True -def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr): +def RDPSessionMonitorStop(inRDPSessionKeyStr, inGSettings = None): """ Stop monitoring the RDP session by the Orchestrator process. Current def don't kill RDP session - only stop to track it (it can give ) @@ -1777,11 +2315,12 @@ def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr): :param inRDPSessionKeyStr: RDP Session string key - need for the further identification :return: True every time :> """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lResult = True inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList return lResult -def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None): +def RDPSessionLogoff(inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None, inGSettings = None): """ Logoff the RDP session from the Orchestrator process (close all apps in session when logoff) @@ -1801,6 +2340,7 @@ def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExe :param inBreakTriggerProcessWOExeList: List of the processes, which will stop the execution. Example ["notepad"] :return: True - logoff is successful """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inBreakTriggerProcessWOExeList is None: inBreakTriggerProcessWOExeList = [] lResult = True # Check thread @@ -1828,7 +2368,7 @@ def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExe inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList return lResult -def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): +def RDPSessionResponsibilityCheck(inRDPSessionKeyStr, inGSettings = None): """ DEVELOPING, MAYBE NOT USEFUL Check RDP Session responsibility TODO NEED DEV + TEST @@ -1836,6 +2376,7 @@ def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): :param inRDPSessionKeyStr: RDP Session string key - need for the further identification :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -1871,7 +2412,7 @@ def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): lDoCheckResponsibilityCountCurrent+=1 return True -def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True): +def RDPSessionProcessStartIfNotRunning(inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True, inGSettings = None): """ Start process in RDP if it is not running (check by the arg inProcessNameWEXEStr) @@ -1895,6 +2436,7 @@ def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProces :param inFlagGetAbsPathBool: True - get abs path from the relative path in inFilePathStr. False - else case :return: True every time :) """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread lResult = True if not Core.IsProcessorThread(inGSettings=inGSettings): @@ -1917,7 +2459,7 @@ def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProces inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSSCHECK"): +def RDPSessionCMDRun(inRDPSessionKeyStr, inCMDStr, inModeStr="CROSSCHECK", inGSettings = None): """ Send CMD command to the RDP session "RUN" window @@ -1945,6 +2487,7 @@ def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSS "IsResponsibleBool": True|False # Flag is RDP is responsible - works only when inModeStr = CROSSCHECK } """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lResult = { "OutStr": None, # Result string "IsResponsibleBool": False # Flag is RDP is responsible - works only when inModeStr = CROSSCHECK @@ -1970,7 +2513,7 @@ def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSS inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool): +def RDPSessionProcessStop(inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool, inGSettings = None): """ Send CMD command to the RDP session "RUN" window. @@ -1992,6 +2535,7 @@ def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, :param inFlagForceCloseBool: True - force close the process. False - safe close the process :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -2015,7 +2559,7 @@ def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="CROSSCHECK", inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr): +def RDPSessionFileStoredSend(inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr, inGSettings = None): """ Send file from Orchestrator session to the RDP session using shared drive in RDP (see RDP Configuration Dict, Shared drive) @@ -2037,6 +2581,7 @@ def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, :param inRDPFilePathStr: !Absolute! path to the destination file location on the RDP side. Example: "C:\\RPA\\TESTDIR\\Test.py" :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -2059,7 +2604,7 @@ def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="LISTEN", inClipboardTimeoutSec = 120, inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr): +def RDPSessionFileStoredRecieve(inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr, inGSettings = None): """ Recieve file from RDP session to the Orchestrator session using shared drive in RDP (see RDP Configuration Dict, Shared drive) @@ -2081,6 +2626,7 @@ def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathSt :param inHostFilePathStr: Relative or absolute path to the file location on the Orchestrator side. Example: "TESTDIR\\Test.py" :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -2106,17 +2652,17 @@ def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathSt # # # # # Start orchestrator # # # # # # # # # # # # # # # # # # # # # # # -def GSettingsAutocleaner(inGSettings): +def GSettingsAutocleaner(inGSettings=None): """ HIDDEN Interval gSettings auto cleaner def to clear some garbage. :param inGSettings: Global settings dict (singleton) :return: None """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings while True: time.sleep(inGSettings["Autocleaner"]["IntervalSecFloat"]) # Wait for the next iteration lL = inGSettings["Logger"] - if lL: lL.info(f"Autocleaner is running") # Info lNowDatetime = datetime.datetime.now() # Get now time # Clean old items in Client > Session > TechnicalSessionGUIDCache lTechnicalSessionGUIDCacheNew = {} @@ -2140,13 +2686,32 @@ def GSettingsAutocleaner(inGSettings): from .. import __version__ # Get version from the package -def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool = True): +def Start(inDumpRestoreBool = True, inRunAsAdministratorBool = True): + """ + Start the orchestrator threads execution + + :param inDumpRestoreBool: True - restore data from the dumo + :param inRunAsAdministratorBool: True - rerun as admin if not + :return: + """ + Orchestrator(inDumpRestoreBool = True, inRunAsAdministratorBool = True) + +def Orchestrator(inGSettings=None, inDumpRestoreBool = True, inRunAsAdministratorBool = True): + """ + Main def to start orchestrator + + :param inGSettings: + :param inDumpRestoreBool: + :param inRunAsAdministratorBool: + :return: + """ lL = inGSettings["Logger"] # https://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script if not OrchestratorIsAdmin() and inRunAsAdministratorBool==True: OrchestratorRerunAsAdmin() else: # Code of your program here + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings #mGlobalDict = Settings.Settings(sys.argv[1]) global gSettingsDict gSettingsDict = inGSettings # Alias for old name in alg @@ -2194,10 +2759,10 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lItemDef = getattr(lModule,lItemDefNameStr) if callable(lItemDef): inGSettings["ProcessorDict"]["AliasDefDict"][lItemDefNameStr]=lItemDef + #Load all defs from sys.modules + ActivityItemDefAliasModulesLoad() + #Инициализация настроечных параметров - lDaemonLoopSeconds=gSettingsDict["SchedulerDict"]["CheckIntervalSecFloat"] - lDaemonActivityLogDict={} #Словарь отработанных активностей, ключ - кортеж (, , , ) - lDaemonLastDateTime=datetime.datetime.now() gSettingsDict["ServerDict"]["WorkingDirectoryPathStr"] = os.getcwd() # Set working directory in g settings #Инициализация сервера (инициализация всех интерфейсов) @@ -2222,12 +2787,17 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lRobotRDPActiveThread.start() # Start the thread execution. if lL: lL.info("Robot RDP active has been started") #Logging + + # Init autocleaner in another thread lAutocleanerThread = threading.Thread(target= GSettingsAutocleaner, kwargs={"inGSettings":gSettingsDict}) lAutocleanerThread.daemon = True # Run the thread in daemon mode. lAutocleanerThread.start() # Start the thread execution. if lL: lL.info("Autocleaner thread has been started") #Logging + # Set flag that orchestrator has been initialized + inGSettings["HiddenIsOrchestratorInitializedBool"] = True + # Orchestrator start activity if lL: lL.info("Orchestrator start activity run") #Logging for lActivityItem in gSettingsDict["OrchestratorStart"]["ActivityList"]: @@ -2245,70 +2815,102 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lProcessorMonitorThread.start() # Start the thread execution. if lL: lL.info("Processor monitor has been started") #Logging - if lL: lL.info("Scheduler loop start") #Logging - gDaemonActivityLogDictRefreshSecInt = 10 # The second period for clear lDaemonActivityLogDict from old items - gDaemonActivityLogDictLastTime = time.time() # The second perioad for clean lDaemonActivityLogDict from old items - - # Set flag that orchestrator has been initialized - inGSettings["HiddenIsOrchestratorInitializedBool"] = True + # Scheduler loop + lSchedulerThread = threading.Thread(target= __deprecated_orchestrator_loop__) + lSchedulerThread.daemon = True # Run the thread in daemon mode. + lSchedulerThread.start() # Start the thread execution. + if lL: lL.info("Scheduler (old) loop start") #Logging + + # Schedule (new) loop + lScheduleThread = threading.Thread(target= __schedule_loop__) + lScheduleThread.daemon = True # Run the thread in daemon mode. + lScheduleThread.start() # Start the thread execution. + if lL: lL.info("Schedule module (new) loop start") #Logging + + # Restore state for process + for lProcessKeyTuple in inGSettings["ManagersProcessDict"]: + lProcess = inGSettings["ManagersProcessDict"][lProcessKeyTuple] + lProcess.StatusCheckIntervalRestore() + lThread = threading.Thread(target= lProcess.StatusRestore) + lThread.start() - while True: - try: - lCurrentDateTime = datetime.datetime.now() - #Циклический обход правил - lFlagSearchActivityType=True - # Periodically clear the lDaemonActivityLogDict - if time.time()-gDaemonActivityLogDictLastTime>=gDaemonActivityLogDictRefreshSecInt: - gDaemonActivityLogDictLastTime = time.time() # Update the time - for lIndex, lItem in enumerate(lDaemonActivityLogDict): - if lItem["ActivityEndDateTime"] and lCurrentDateTime<=lItem["ActivityEndDateTime"]: - pass - # Activity is actual - do not delete now - else: - # remove the activity - not actual - lDaemonActivityLogDict.pop(lIndex,None) - lIterationLastDateTime = lDaemonLastDateTime # Get current datetime before iterator (need for iterate all activities in loop) - # Iterate throught the activity list - for lIndex, lItem in enumerate(gSettingsDict["SchedulerDict"]["ActivityTimeList"]): - try: - # Prepare GUID of the activity - lGUID = None - if "GUID" in lItem and lItem["GUID"]: - lGUID = lItem["GUID"] - else: - lGUID = str(uuid.uuid4()) - lItem["GUID"]=lGUID - - #Проверка дней недели, в рамках которых можно запускать активность - lItemWeekdayList=lItem.get("WeekdayList", [0, 1, 2, 3, 4, 5, 6]) - if lCurrentDateTime.weekday() in lItemWeekdayList: - if lFlagSearchActivityType: - ####################################################################### - #Branch 1 - if has TimeHH:MM - ####################################################################### - if "TimeHH:MMStr" in lItem: - #Вид активности - запуск процесса - #Сформировать временной штамп, относительно которого надо будет проверять время - #часовой пояс пока не учитываем - lActivityDateTime=datetime.datetime.strptime(lItem["TimeHH:MMStr"],"%H:%M") - lActivityDateTime=lActivityDateTime.replace(year=lCurrentDateTime.year,month=lCurrentDateTime.month,day=lCurrentDateTime.day) - #Убедиться в том, что время наступило - if ( - lActivityDateTime>=lDaemonLastDateTime and - lCurrentDateTime>=lActivityDateTime): - # Log info about activity - if lL: lL.info(f"Scheduler:: Activity list is started in new thread. Parameters are not available to see.") #Logging - # Do the activity - lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs={"inGSettings": inGSettings, "inActivityList":lItem["ActivityList"]}) - lThread.start() - lIterationLastDateTime = datetime.datetime.now() # Set the new datetime for the new processor activity - except Exception as e: - if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module when activity time item was initialising. ActivityTimeItem is {lItem}") - lDaemonLastDateTime = lIterationLastDateTime # Set the new datetime for the new processor activity - #Уснуть до следующего прогона - time.sleep(lDaemonLoopSeconds) - except Exception as e: - if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module. Global error") +def __schedule_loop__(): + while True: + schedule.run_pending() + time.sleep(3) + +# Backward compatibility below to 1.2.7 +def __deprecated_orchestrator_loop__(): + lL = OrchestratorLoggerGet() + inGSettings = GSettingsGet() + lDaemonLoopSeconds = gSettingsDict["SchedulerDict"]["CheckIntervalSecFloat"] + lDaemonActivityLogDict = {} # Словарь отработанных активностей, ключ - кортеж (, , , ) + lDaemonLastDateTime = datetime.datetime.now() + gDaemonActivityLogDictRefreshSecInt = 10 # The second period for clear lDaemonActivityLogDict from old items + gDaemonActivityLogDictLastTime = time.time() # The second perioad for clean lDaemonActivityLogDict from old items + while True: + try: + lCurrentDateTime = datetime.datetime.now() + # Циклический обход правил + lFlagSearchActivityType = True + # Periodically clear the lDaemonActivityLogDict + if time.time() - gDaemonActivityLogDictLastTime >= gDaemonActivityLogDictRefreshSecInt: + gDaemonActivityLogDictLastTime = time.time() # Update the time + for lIndex, lItem in enumerate(lDaemonActivityLogDict): + if lItem["ActivityEndDateTime"] and lCurrentDateTime <= lItem["ActivityEndDateTime"]: + pass + # Activity is actual - do not delete now + else: + # remove the activity - not actual + lDaemonActivityLogDict.pop(lIndex, None) + lIterationLastDateTime = lDaemonLastDateTime # Get current datetime before iterator (need for iterate all activities in loop) + # Iterate throught the activity list + for lIndex, lItem in enumerate(gSettingsDict["SchedulerDict"]["ActivityTimeList"]): + try: + # Prepare GUID of the activity + lGUID = None + if "GUID" in lItem and lItem["GUID"]: + lGUID = lItem["GUID"] + else: + lGUID = str(uuid.uuid4()) + lItem["GUID"] = lGUID + + # Проверка дней недели, в рамках которых можно запускать активность + lItemWeekdayList = lItem.get("WeekdayList", [0, 1, 2, 3, 4, 5, 6]) + if lCurrentDateTime.weekday() in lItemWeekdayList: + if lFlagSearchActivityType: + ####################################################################### + # Branch 1 - if has TimeHH:MM + ####################################################################### + if "TimeHH:MMStr" in lItem: + # Вид активности - запуск процесса + # Сформировать временной штамп, относительно которого надо будет проверять время + # часовой пояс пока не учитываем + lActivityDateTime = datetime.datetime.strptime(lItem["TimeHH:MMStr"], "%H:%M") + lActivityDateTime = lActivityDateTime.replace(year=lCurrentDateTime.year, + month=lCurrentDateTime.month, + day=lCurrentDateTime.day) + # Убедиться в том, что время наступило + if ( + lActivityDateTime >= lDaemonLastDateTime and + lCurrentDateTime >= lActivityDateTime): + # Log info about activity + if lL: lL.info( + f"Scheduler:: Activity list is started in new thread. Parameters are not available to see.") # Logging + # Do the activity + lThread = threading.Thread(target=Processor.ActivityListExecute, + kwargs={"inGSettings": inGSettings, + "inActivityList": lItem["ActivityList"]}) + lThread.start() + lIterationLastDateTime = datetime.datetime.now() # Set the new datetime for the new processor activity + except Exception as e: + if lL: lL.exception( + f"Scheduler: Exception has been catched in Scheduler module when activity time item was initialising. ActivityTimeItem is {lItem}") + lDaemonLastDateTime = lIterationLastDateTime # Set the new datetime for the new processor activity + # Уснуть до следующего прогона + time.sleep(lDaemonLoopSeconds) + except Exception as e: + if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module. Global error") # Backward compatibility below to 1.2.0 def __deprecated_orchestrator_start__(): diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py index 897e7782..8a3500e4 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py @@ -5,5 +5,6 @@ The pyOpenRPA package (from UnicodeLabs) """ from .Web import Basic from .__Orchestrator__ import * +from . import Managers __all__ = [] __author__ = 'Ivan Maslov ' \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/Terminator.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/StopSafe.py similarity index 67% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/Terminator.py rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/StopSafe.py index 4ebe5929..8b350cf4 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/Terminator.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/Tools/StopSafe.py @@ -1,8 +1,10 @@ +""" # How to use -# from pyOpenRPA.Tools import Terminator -# Terminator.Init(inLogger=None) -# Terminator.IsSignalClose() # True - WM_CLOSE SIGNAL has come -# Terminator.SessionLogoff() # Logoff the session +# from pyOpenRPA.Tools import StopSafe +# StopSafe.Init(inLogger=None) +# StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe +""" + import win32con import win32gui @@ -12,33 +14,35 @@ gLogger = None gWindowTitleStr = "PythonTerminator" # Title of the phantom window gWindowDescriptionStr = "pyOpenRPA library for safe turn off the program (by send the WM_CLOSE signal from task kill)" # Description of the phantom window -# Init the terminator def Init(inLogger=None): + """ + Init the StopSafe module. After that you can use def IsStopSafe() to check if close signal has come. + + :param inLogger: Logger to log messages about StopSafe + :return: + """ global gLogger global gIsSignalCloseBool gIsSignalCloseBool = False # Init default gLogger = inLogger - #import sys - #import time - #import atexit import threading - #atexit.register(print, 'PYTHON SPAM APP: SHUTDOWN') - shutdown_thread = threading.Thread(target=shutdown_monitor) + if gLogger: gLogger.info(f"StopSafe: Init termination catch thread") + shutdown_thread = threading.Thread(target=_shutdown_monitor) shutdown_thread.start() #shutdown_thread.join() #shutdown_monitor() -# Terminator.IsSignalClose() # True - WM_CLOSE SIGNAL has come -def IsSignalClose(): + +def IsStopSafe(): + """ + Check if stop signal has come. + + :return: + """ global gIsSignalCloseBool # Init the global variable return gIsSignalCloseBool # Return the result -# Terminator.SessionLogoff() # Logoff the session -def SessionLogoff(): - os.system("shutdown /l") - -# Technical function -def shutdown_monitor(): +def _shutdown_monitor(): global gIsSignalCloseBool # Init the global variable global gLogger def wndproc(hwnd, msg, wparam, lparam): @@ -58,5 +62,5 @@ def shutdown_monitor(): win32gui.PumpMessages() gIsSignalCloseBool = True # WM_CLOSE message has come if gLogger: - gLogger.info(f"Terminator: Program has recieve the close signal - safe exit") + gLogger.info(f"StopSafe: Program has catch VM_CLOSE signal - do safe exit") diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/__init__.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/__init__.py index 8167039a..bcb3231f 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/__init__.py +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA/__init__.py @@ -3,7 +3,7 @@ r""" The OpenRPA package (from UnicodeLabs) """ -__version__ = 'v1.2.6' +__version__ = 'v1.2.7' __all__ = [] __author__ = 'Ivan Maslov ' #from .Core import Robot \ No newline at end of file diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/AUTHORS.rst b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/AUTHORS.rst new file mode 100644 index 00000000..0d0620fd --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/AUTHORS.rst @@ -0,0 +1,35 @@ +Thanks to all the wonderful folks who have contributed to schedule over the years: + +- mattss +- mrhwick +- cfrco +- matrixise +- abultman +- mplewis +- WoLfulus +- dylwhich +- fkromer +- alaingilbert +- Zerrossetto +- yetingsky +- schnepp +- grampajoe +- gilbsgilbs +- Nathan Wailes +- Connor Skees +- qmorek +- aisk +- MichaelCorleoneLi +- sijmenhuizenga +- eladbi +- chankeypathak +- vubon +- gaguirregabiria +- rhagenaars +- Skenvy +- zcking +- Martin Thoma +- ebllg +- fredthomsen +- biggerfisch +- sosolidkk \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/INSTALLER b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/INSTALLER similarity index 100% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/INSTALLER rename to Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/INSTALLER diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/LICENSE.txt b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/LICENSE.txt new file mode 100644 index 00000000..7c51781e --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Daniel Bader (http://dbader.org) + +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. diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/METADATA b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/METADATA new file mode 100644 index 00000000..18be5062 --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/METADATA @@ -0,0 +1,91 @@ +Metadata-Version: 2.1 +Name: schedule +Version: 1.1.0 +Summary: Job scheduling for humans. +Home-page: https://github.com/dbader/schedule +Author: Daniel Bader +Author-email: mail@dbader.org +License: MIT +Download-URL: https://github.com/dbader/schedule/tarball/1.1.0 +Keywords: schedule,periodic,jobs,scheduling,clockwork,cron,scheduler,job scheduling +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Development Status :: 5 - Production/Stable +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Natural Language :: English +Requires-Python: >=3.6 + +`schedule `__ +=============================================== + + +.. image:: https://github.com/dbader/schedule/workflows/Tests/badge.svg + :target: https://github.com/dbader/schedule/actions?query=workflow%3ATests+branch%3Amaster + +.. image:: https://coveralls.io/repos/dbader/schedule/badge.svg?branch=master + :target: https://coveralls.io/r/dbader/schedule + +.. image:: https://img.shields.io/pypi/v/schedule.svg + :target: https://pypi.python.org/pypi/schedule + +Python job scheduling for humans. Run Python functions (or any other callable) periodically using a friendly syntax. + +- A simple to use API for scheduling jobs, made for humans. +- In-process scheduler for periodic jobs. No extra processes needed! +- Very lightweight and no external dependencies. +- Excellent test coverage. +- Tested on Python and 3.6, 3.7, 3.8, 3.9 + +Usage +----- + +.. code-block:: bash + + $ pip install schedule + +.. code-block:: python + + import schedule + import time + + def job(): + print("I'm working...") + + schedule.every(10).seconds.do(job) + schedule.every(10).minutes.do(job) + schedule.every().hour.do(job) + schedule.every().day.at("10:30").do(job) + schedule.every(5).to(10).minutes.do(job) + schedule.every().monday.do(job) + schedule.every().wednesday.at("13:15").do(job) + schedule.every().minute.at(":17").do(job) + + while True: + schedule.run_pending() + time.sleep(1) + +Documentation +------------- + +Schedule's documentation lives at `schedule.readthedocs.io `_. + + +Meta +---- + +Daniel Bader - `@dbader_org `_ - mail@dbader.org + +Inspired by `Adam Wiggins' `_ article `"Rethinking Cron" `_ and the `clockwork `_ Ruby module. + +Distributed under the MIT license. See `LICENSE.txt `_ for more information. + +https://github.com/dbader/schedule + + diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/RECORD b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/RECORD new file mode 100644 index 00000000..7515b447 --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/RECORD @@ -0,0 +1,9 @@ +schedule-1.1.0.dist-info/AUTHORS.rst,sha256=kdTW4zMQMcIf50TZeSRgSd9Xt-GgBWmNBU7T-L42w4Y,1539 +schedule-1.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +schedule-1.1.0.dist-info/LICENSE.txt,sha256=MKg1LDGM4bZFrN4CmWlzQtQ4DtJjfXyhiorSVmHjtBs,1099 +schedule-1.1.0.dist-info/METADATA,sha256=PqtfjpOzw6RLQBETDHUTpy3--PbtzxBFsqerANEqRGE,2996 +schedule-1.1.0.dist-info/RECORD,, +schedule-1.1.0.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 +schedule-1.1.0.dist-info/top_level.txt,sha256=gFq0D6Oy7pGyXL4X3ITQVefZHZ6ScWCUk8Z3C1ub7SE,9 +schedule/__init__.py,sha256=t_wUVwkVvmyOeFy0vm8lZLRwkt6AbqbjFbc4jMsUk8w,28680 +schedule/__pycache__/__init__.cpython-37.pyc,, diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/WHEEL b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/WHEEL new file mode 100644 index 00000000..01b8fc7d --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/top_level.txt b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/top_level.txt new file mode 100644 index 00000000..b397e362 --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule-1.1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +schedule diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule/__init__.py b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule/__init__.py new file mode 100644 index 00000000..c550a8c8 --- /dev/null +++ b/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/schedule/__init__.py @@ -0,0 +1,839 @@ +""" +Python job scheduling for humans. + +github.com/dbader/schedule + +An in-process scheduler for periodic jobs that uses the builder pattern +for configuration. Schedule lets you run Python functions (or any other +callable) periodically at pre-determined intervals using a simple, +human-friendly syntax. + +Inspired by Addam Wiggins' article "Rethinking Cron" [1] and the +"clockwork" Ruby module [2][3]. + +Features: + - A simple to use API for scheduling jobs. + - Very lightweight and no external dependencies. + - Excellent test coverage. + - Tested on Python 3.6, 3.7, 3.8, 3.9 + +Usage: + >>> import schedule + >>> import time + + >>> def job(message='stuff'): + >>> print("I'm working on:", message) + + >>> schedule.every(10).minutes.do(job) + >>> schedule.every(5).to(10).days.do(job) + >>> schedule.every().hour.do(job, message='things') + >>> schedule.every().day.at("10:30").do(job) + + >>> while True: + >>> schedule.run_pending() + >>> time.sleep(1) + +[1] https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ +[2] https://github.com/Rykian/clockwork +[3] https://adam.herokuapp.com/past/2010/6/30/replace_cron_with_clockwork/ +""" +from collections.abc import Hashable +import datetime +import functools +import logging +import random +import re +import time +from typing import Set, List, Optional, Callable, Union + +logger = logging.getLogger("schedule") + + +class ScheduleError(Exception): + """Base schedule exception""" + + pass + + +class ScheduleValueError(ScheduleError): + """Base schedule value error""" + + pass + + +class IntervalError(ScheduleValueError): + """An improper interval was used""" + + pass + + +class CancelJob(object): + """ + Can be returned from a job to unschedule itself. + """ + + pass + + +class Scheduler(object): + """ + Objects instantiated by the :class:`Scheduler ` are + factories to create jobs, keep record of scheduled jobs and + handle their execution. + """ + + def __init__(self) -> None: + self.jobs: List[Job] = [] + + def run_pending(self) -> None: + """ + Run all jobs that are scheduled to run. + + Please note that it is *intended behavior that run_pending() + does not run missed jobs*. For example, if you've registered a job + that should run every minute and you only call run_pending() + in one hour increments then your job won't be run 60 times in + between but only once. + """ + runnable_jobs = (job for job in self.jobs if job.should_run) + for job in sorted(runnable_jobs): + self._run_job(job) + + def run_all(self, delay_seconds: int = 0) -> None: + """ + Run all jobs regardless if they are scheduled to run or not. + + A delay of `delay` seconds is added between each job. This helps + distribute system load generated by the jobs more evenly + over time. + + :param delay_seconds: A delay added between every executed job + """ + logger.debug( + "Running *all* %i jobs with %is delay in between", + len(self.jobs), + delay_seconds, + ) + for job in self.jobs[:]: + self._run_job(job) + time.sleep(delay_seconds) + + def get_jobs(self, tag: Optional[Hashable] = None) -> List["Job"]: + """ + Gets scheduled jobs marked with the given tag, or all jobs + if tag is omitted. + + :param tag: An identifier used to identify a subset of + jobs to retrieve + """ + if tag is None: + return self.jobs[:] + else: + return [job for job in self.jobs if tag in job.tags] + + def clear(self, tag: Optional[Hashable] = None) -> None: + """ + Deletes scheduled jobs marked with the given tag, or all jobs + if tag is omitted. + + :param tag: An identifier used to identify a subset of + jobs to delete + """ + if tag is None: + logger.debug("Deleting *all* jobs") + del self.jobs[:] + else: + logger.debug('Deleting all jobs tagged "%s"', tag) + self.jobs[:] = (job for job in self.jobs if tag not in job.tags) + + def cancel_job(self, job: "Job") -> None: + """ + Delete a scheduled job. + + :param job: The job to be unscheduled + """ + try: + logger.debug('Cancelling job "%s"', str(job)) + self.jobs.remove(job) + except ValueError: + logger.debug('Cancelling not-scheduled job "%s"', str(job)) + + def every(self, interval: int = 1) -> "Job": + """ + Schedule a new periodic job. + + :param interval: A quantity of a certain time unit + :return: An unconfigured :class:`Job ` + """ + job = Job(interval, self) + return job + + def _run_job(self, job: "Job") -> None: + ret = job.run() + if isinstance(ret, CancelJob) or ret is CancelJob: + self.cancel_job(job) + + @property + def next_run(self) -> Optional[datetime.datetime]: + """ + Datetime when the next job should run. + + :return: A :class:`~datetime.datetime` object + or None if no jobs scheduled + """ + if not self.jobs: + return None + return min(self.jobs).next_run + + @property + def idle_seconds(self) -> Optional[float]: + """ + :return: Number of seconds until + :meth:`next_run ` + or None if no jobs are scheduled + """ + if not self.next_run: + return None + return (self.next_run - datetime.datetime.now()).total_seconds() + + +class Job(object): + """ + A periodic job as used by :class:`Scheduler`. + + :param interval: A quantity of a certain time unit + :param scheduler: The :class:`Scheduler ` instance that + this job will register itself with once it has + been fully configured in :meth:`Job.do()`. + + Every job runs at a given fixed time interval that is defined by: + + * a :meth:`time unit ` + * a quantity of `time units` defined by `interval` + + A job is usually created and returned by :meth:`Scheduler.every` + method, which also defines its `interval`. + """ + + def __init__(self, interval: int, scheduler: Scheduler = None): + self.interval: int = interval # pause interval * unit between runs + self.latest: Optional[int] = None # upper limit to the interval + self.job_func: Optional[functools.partial] = None # the job job_func to run + + # time units, e.g. 'minutes', 'hours', ... + self.unit: Optional[str] = None + + # optional time at which this job runs + self.at_time: Optional[datetime.time] = None + + # datetime of the last run + self.last_run: Optional[datetime.datetime] = None + + # datetime of the next run + self.next_run: Optional[datetime.datetime] = None + + # timedelta between runs, only valid for + self.period: Optional[datetime.timedelta] = None + + # Specific day of the week to start on + self.start_day: Optional[str] = None + + # optional time of final run + self.cancel_after: Optional[datetime.datetime] = None + + self.tags: Set[Hashable] = set() # unique set of tags for the job + self.scheduler: Optional[Scheduler] = scheduler # scheduler to register with + + def __lt__(self, other) -> bool: + """ + PeriodicJobs are sortable based on the scheduled time they + run next. + """ + return self.next_run < other.next_run + + def __str__(self) -> str: + if hasattr(self.job_func, "__name__"): + job_func_name = self.job_func.__name__ # type: ignore + else: + job_func_name = repr(self.job_func) + + return ("Job(interval={}, unit={}, do={}, args={}, kwargs={})").format( + self.interval, + self.unit, + job_func_name, + "()" if self.job_func is None else self.job_func.args, + "{}" if self.job_func is None else self.job_func.keywords, + ) + + def __repr__(self): + def format_time(t): + return t.strftime("%Y-%m-%d %H:%M:%S") if t else "[never]" + + def is_repr(j): + return not isinstance(j, Job) + + timestats = "(last run: %s, next run: %s)" % ( + format_time(self.last_run), + format_time(self.next_run), + ) + + if hasattr(self.job_func, "__name__"): + job_func_name = self.job_func.__name__ + else: + job_func_name = repr(self.job_func) + args = [repr(x) if is_repr(x) else str(x) for x in self.job_func.args] + kwargs = ["%s=%s" % (k, repr(v)) for k, v in self.job_func.keywords.items()] + call_repr = job_func_name + "(" + ", ".join(args + kwargs) + ")" + + if self.at_time is not None: + return "Every %s %s at %s do %s %s" % ( + self.interval, + self.unit[:-1] if self.interval == 1 else self.unit, + self.at_time, + call_repr, + timestats, + ) + else: + fmt = ( + "Every %(interval)s " + + ("to %(latest)s " if self.latest is not None else "") + + "%(unit)s do %(call_repr)s %(timestats)s" + ) + + return fmt % dict( + interval=self.interval, + latest=self.latest, + unit=(self.unit[:-1] if self.interval == 1 else self.unit), + call_repr=call_repr, + timestats=timestats, + ) + + @property + def second(self): + if self.interval != 1: + raise IntervalError("Use seconds instead of second") + return self.seconds + + @property + def seconds(self): + self.unit = "seconds" + return self + + @property + def minute(self): + if self.interval != 1: + raise IntervalError("Use minutes instead of minute") + return self.minutes + + @property + def minutes(self): + self.unit = "minutes" + return self + + @property + def hour(self): + if self.interval != 1: + raise IntervalError("Use hours instead of hour") + return self.hours + + @property + def hours(self): + self.unit = "hours" + return self + + @property + def day(self): + if self.interval != 1: + raise IntervalError("Use days instead of day") + return self.days + + @property + def days(self): + self.unit = "days" + return self + + @property + def week(self): + if self.interval != 1: + raise IntervalError("Use weeks instead of week") + return self.weeks + + @property + def weeks(self): + self.unit = "weeks" + return self + + @property + def monday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .monday() jobs is only allowed for weekly jobs. " + "Using .monday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "monday" + return self.weeks + + @property + def tuesday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .tuesday() jobs is only allowed for weekly jobs. " + "Using .tuesday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "tuesday" + return self.weeks + + @property + def wednesday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .wednesday() jobs is only allowed for weekly jobs. " + "Using .wednesday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "wednesday" + return self.weeks + + @property + def thursday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .thursday() jobs is only allowed for weekly jobs. " + "Using .thursday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "thursday" + return self.weeks + + @property + def friday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .friday() jobs is only allowed for weekly jobs. " + "Using .friday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "friday" + return self.weeks + + @property + def saturday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .saturday() jobs is only allowed for weekly jobs. " + "Using .saturday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "saturday" + return self.weeks + + @property + def sunday(self): + if self.interval != 1: + raise IntervalError( + "Scheduling .sunday() jobs is only allowed for weekly jobs. " + "Using .sunday() on a job scheduled to run every 2 or more weeks " + "is not supported." + ) + self.start_day = "sunday" + return self.weeks + + def tag(self, *tags: Hashable): + """ + Tags the job with one or more unique identifiers. + + Tags must be hashable. Duplicate tags are discarded. + + :param tags: A unique list of ``Hashable`` tags. + :return: The invoked job instance + """ + if not all(isinstance(tag, Hashable) for tag in tags): + raise TypeError("Tags must be hashable") + self.tags.update(tags) + return self + + def at(self, time_str): + + """ + Specify a particular time that the job should be run at. + + :param time_str: A string in one of the following formats: + + - For daily jobs -> `HH:MM:SS` or `HH:MM` + - For hourly jobs -> `MM:SS` or `:MM` + - For minute jobs -> `:SS` + + The format must make sense given how often the job is + repeating; for example, a job that repeats every minute + should not be given a string in the form `HH:MM:SS`. The + difference between `:MM` and :SS` is inferred from the + selected time-unit (e.g. `every().hour.at(':30')` vs. + `every().minute.at(':30')`). + + :return: The invoked job instance + """ + if self.unit not in ("days", "hours", "minutes") and not self.start_day: + raise ScheduleValueError( + "Invalid unit (valid units are `days`, `hours`, and `minutes`)" + ) + if not isinstance(time_str, str): + raise TypeError("at() should be passed a string") + if self.unit == "days" or self.start_day: + if not re.match(r"^([0-2]\d:)?[0-5]\d:[0-5]\d$", time_str): + raise ScheduleValueError( + "Invalid time format for a daily job (valid format is HH:MM(:SS)?)" + ) + if self.unit == "hours": + if not re.match(r"^([0-5]\d)?:[0-5]\d$", time_str): + raise ScheduleValueError( + "Invalid time format for an hourly job (valid format is (MM)?:SS)" + ) + + if self.unit == "minutes": + if not re.match(r"^:[0-5]\d$", time_str): + raise ScheduleValueError( + "Invalid time format for a minutely job (valid format is :SS)" + ) + time_values = time_str.split(":") + hour: Union[str, int] + minute: Union[str, int] + second: Union[str, int] + if len(time_values) == 3: + hour, minute, second = time_values + elif len(time_values) == 2 and self.unit == "minutes": + hour = 0 + minute = 0 + _, second = time_values + elif len(time_values) == 2 and self.unit == "hours" and len(time_values[0]): + hour = 0 + minute, second = time_values + else: + hour, minute = time_values + second = 0 + if self.unit == "days" or self.start_day: + hour = int(hour) + if not (0 <= hour <= 23): + raise ScheduleValueError( + "Invalid number of hours ({} is not between 0 and 23)" + ) + elif self.unit == "hours": + hour = 0 + elif self.unit == "minutes": + hour = 0 + minute = 0 + minute = int(minute) + second = int(second) + self.at_time = datetime.time(hour, minute, second) + return self + + def to(self, latest: int): + """ + Schedule the job to run at an irregular (randomized) interval. + + The job's interval will randomly vary from the value given + to `every` to `latest`. The range defined is inclusive on + both ends. For example, `every(A).to(B).seconds` executes + the job function every N seconds such that A <= N <= B. + + :param latest: Maximum interval between randomized job runs + :return: The invoked job instance + """ + self.latest = latest + return self + + def until( + self, + until_time: Union[datetime.datetime, datetime.timedelta, datetime.time, str], + ): + """ + Schedule job to run until the specified moment. + + The job is canceled whenever the next run is calculated and it turns out the + next run is after the until_time. The job is also canceled right before it runs, + if the current time is after until_time. This latter case can happen when the + the job was scheduled to run before until_time, but runs after until_time. + + If until_time is a moment in the past, ScheduleValueError is thrown. + + :param until_time: A moment in the future representing the latest time a job can + be run. If only a time is supplied, the date is set to today. + The following formats are accepted: + + - datetime.datetime + - datetime.timedelta + - datetime.time + - String in one of the following formats: "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M", "%Y-%m-%d", "%H:%M:%S", "%H:%M" + as defined by strptime() behaviour. If an invalid string format is passed, + ScheduleValueError is thrown. + + :return: The invoked job instance + """ + + if isinstance(until_time, datetime.datetime): + self.cancel_after = until_time + elif isinstance(until_time, datetime.timedelta): + self.cancel_after = datetime.datetime.now() + until_time + elif isinstance(until_time, datetime.time): + self.cancel_after = datetime.datetime.combine( + datetime.datetime.now(), until_time + ) + elif isinstance(until_time, str): + cancel_after = self._decode_datetimestr( + until_time, + [ + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%d %H:%M", + "%Y-%m-%d", + "%H:%M:%S", + "%H:%M", + ], + ) + if cancel_after is None: + raise ScheduleValueError("Invalid string format for until()") + if "-" not in until_time: + # the until_time is a time-only format. Set the date to today + now = datetime.datetime.now() + cancel_after = cancel_after.replace( + year=now.year, month=now.month, day=now.day + ) + self.cancel_after = cancel_after + else: + raise TypeError( + "until() takes a string, datetime.datetime, datetime.timedelta, " + "datetime.time parameter" + ) + if self.cancel_after < datetime.datetime.now(): + raise ScheduleValueError( + "Cannot schedule a job to run until a time in the past" + ) + return self + + def do(self, job_func: Callable, *args, **kwargs): + """ + Specifies the job_func that should be called every time the + job runs. + + Any additional arguments are passed on to job_func when + the job runs. + + :param job_func: The function to be scheduled + :return: The invoked job instance + """ + self.job_func = functools.partial(job_func, *args, **kwargs) + functools.update_wrapper(self.job_func, job_func) + self._schedule_next_run() + if self.scheduler is None: + raise ScheduleError( + "Unable to a add job to schedule. " + "Job is not associated with an scheduler" + ) + self.scheduler.jobs.append(self) + return self + + @property + def should_run(self) -> bool: + """ + :return: ``True`` if the job should be run now. + """ + assert self.next_run is not None, "must run _schedule_next_run before" + return datetime.datetime.now() >= self.next_run + + def run(self): + """ + Run the job and immediately reschedule it. + If the job's deadline is reached (configured using .until()), the job is not + run and CancelJob is returned immediately. If the next scheduled run exceeds + the job's deadline, CancelJob is returned after the execution. In this latter + case CancelJob takes priority over any other returned value. + + :return: The return value returned by the `job_func`, or CancelJob if the job's + deadline is reached. + + """ + if self._is_overdue(datetime.datetime.now()): + logger.debug("Cancelling job %s", self) + return CancelJob + + logger.debug("Running job %s", self) + ret = self.job_func() + self.last_run = datetime.datetime.now() + self._schedule_next_run() + + if self._is_overdue(self.next_run): + logger.debug("Cancelling job %s", self) + return CancelJob + return ret + + def _schedule_next_run(self) -> None: + """ + Compute the instant when this job should run next. + """ + if self.unit not in ("seconds", "minutes", "hours", "days", "weeks"): + raise ScheduleValueError( + "Invalid unit (valid units are `seconds`, `minutes`, `hours`, " + "`days`, and `weeks`)" + ) + + if self.latest is not None: + if not (self.latest >= self.interval): + raise ScheduleError("`latest` is greater than `interval`") + interval = random.randint(self.interval, self.latest) + else: + interval = self.interval + + self.period = datetime.timedelta(**{self.unit: interval}) + self.next_run = datetime.datetime.now() + self.period + if self.start_day is not None: + if self.unit != "weeks": + raise ScheduleValueError("`unit` should be 'weeks'") + weekdays = ( + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ) + if self.start_day not in weekdays: + raise ScheduleValueError( + "Invalid start day (valid start days are {})".format(weekdays) + ) + weekday = weekdays.index(self.start_day) + days_ahead = weekday - self.next_run.weekday() + if days_ahead <= 0: # Target day already happened this week + days_ahead += 7 + self.next_run += datetime.timedelta(days_ahead) - self.period + if self.at_time is not None: + if self.unit not in ("days", "hours", "minutes") and self.start_day is None: + raise ScheduleValueError("Invalid unit without specifying start day") + kwargs = {"second": self.at_time.second, "microsecond": 0} + if self.unit == "days" or self.start_day is not None: + kwargs["hour"] = self.at_time.hour + if self.unit in ["days", "hours"] or self.start_day is not None: + kwargs["minute"] = self.at_time.minute + self.next_run = self.next_run.replace(**kwargs) # type: ignore + # Make sure we run at the specified time *today* (or *this hour*) + # as well. This accounts for when a job takes so long it finished + # in the next period. + if not self.last_run or (self.next_run - self.last_run) > self.period: + now = datetime.datetime.now() + if ( + self.unit == "days" + and self.at_time > now.time() + and self.interval == 1 + ): + self.next_run = self.next_run - datetime.timedelta(days=1) + elif self.unit == "hours" and ( + self.at_time.minute > now.minute + or ( + self.at_time.minute == now.minute + and self.at_time.second > now.second + ) + ): + self.next_run = self.next_run - datetime.timedelta(hours=1) + elif self.unit == "minutes" and self.at_time.second > now.second: + self.next_run = self.next_run - datetime.timedelta(minutes=1) + if self.start_day is not None and self.at_time is not None: + # Let's see if we will still make that time we specified today + if (self.next_run - datetime.datetime.now()).days >= 7: + self.next_run -= self.period + + def _is_overdue(self, when: datetime.datetime): + return self.cancel_after is not None and when > self.cancel_after + + def _decode_datetimestr( + self, datetime_str: str, formats: List[str] + ) -> Optional[datetime.datetime]: + for f in formats: + try: + return datetime.datetime.strptime(datetime_str, f) + except ValueError: + pass + return None + + +# The following methods are shortcuts for not having to +# create a Scheduler instance: + +#: Default :class:`Scheduler ` object +default_scheduler = Scheduler() + +#: Default :class:`Jobs ` list +jobs = default_scheduler.jobs # todo: should this be a copy, e.g. jobs()? + + +def every(interval: int = 1) -> Job: + """Calls :meth:`every ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.every(interval) + + +def run_pending() -> None: + """Calls :meth:`run_pending ` on the + :data:`default scheduler instance `. + """ + default_scheduler.run_pending() + + +def run_all(delay_seconds: int = 0) -> None: + """Calls :meth:`run_all ` on the + :data:`default scheduler instance `. + """ + default_scheduler.run_all(delay_seconds=delay_seconds) + + +def get_jobs(tag: Optional[Hashable] = None) -> List[Job]: + """Calls :meth:`get_jobs ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.get_jobs(tag) + + +def clear(tag: Optional[Hashable] = None) -> None: + """Calls :meth:`clear ` on the + :data:`default scheduler instance `. + """ + default_scheduler.clear(tag) + + +def cancel_job(job: Job) -> None: + """Calls :meth:`cancel_job ` on the + :data:`default scheduler instance `. + """ + default_scheduler.cancel_job(job) + + +def next_run() -> Optional[datetime.datetime]: + """Calls :meth:`next_run ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.next_run + + +def idle_seconds() -> Optional[float]: + """Calls :meth:`idle_seconds ` on the + :data:`default scheduler instance `. + """ + return default_scheduler.idle_seconds + + +def repeat(job, *args, **kwargs): + """ + Decorator to schedule a new periodic job. + + Any additional arguments are passed on to the decorated function + when the job runs. + + :param job: a :class:`Jobs ` + """ + + def _schedule_decorator(decorated_function): + job.do(decorated_function, *args, **kwargs) + return decorated_function + + return _schedule_decorator diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/INSTALLER b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/METADATA b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/METADATA similarity index 90% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/METADATA rename to Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/METADATA index c6e28f2c..2d9287f9 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/METADATA +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/METADATA @@ -1,15 +1,17 @@ Metadata-Version: 2.1 Name: pyOpenRPA -Version: 1.2.6 +Version: 1.2.7 Summary: First open source RPA platform for business -Home-page: https://gitlab.com/UnicodeLabs/OpenRPA +Home-page: https://pyopenrpa.ru/ Author: Ivan Maslov -Author-email: Ivan.Maslov@unicodelabs.ru -License: MIT -Keywords: OpenRPA RPA Robot Automation Robotization OpenSource +Author-email: Ivan.Maslov@pyopenrpa.ru +License: PYOPENRPA +Keywords: pyOpenRPA OpenRPA RPA Robot Automation Robotization OpenSource IT4Business Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable -Classifier: License :: OSI Approved :: MIT License +Classifier: License :: Free For Educational Use +Classifier: License :: Free For Home Use +Classifier: License :: Free for non-commercial use Classifier: Intended Audience :: Developers Classifier: Environment :: Win32 (MS Windows) Classifier: Environment :: X11 Applications @@ -25,6 +27,7 @@ Requires-Dist: pillow (>=6.0.0) Requires-Dist: keyboard (>=0.13.3) Requires-Dist: pyautogui (<=0.9.52) Requires-Dist: crypto (>=1.4.1) +Requires-Dist: schedule (>=1.1.0) Requires-Dist: pywinauto (>=0.6.8) ; platform_system == "win32" and python_version >= "3.0" Requires-Dist: WMI (>=1.4.9) ; platform_system == "win32" and python_version >= "3.0" Requires-Dist: pywin32 (>=224) ; platform_system == "win32" and python_version >= "3.0" diff --git a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/RECORD b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/RECORD similarity index 92% rename from Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/RECORD rename to Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/RECORD index 6f519764..23916d31 100644 --- a/Resources/WPy32-3720/python-3.7.2/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/RECORD +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/RECORD @@ -1,9 +1,9 @@ -pyOpenRPA-1.2.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pyOpenRPA-1.2.6.dist-info/METADATA,sha256=pU-EpQuvMPYjhD1o2BYjfpOfsruNrxm9s7mfWX9mF1M,3612 -pyOpenRPA-1.2.6.dist-info/RECORD,, -pyOpenRPA-1.2.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pyOpenRPA-1.2.6.dist-info/WHEEL,sha256=qB97nP5e4MrOsXW5bIU5cUn_KSVr10EV0l-GCHG9qNs,97 -pyOpenRPA-1.2.6.dist-info/top_level.txt,sha256=RPzwQXgYBRo_m5L3ZLs6Voh8aEkMeT29Xsul1w1qE0g,10 +pyOpenRPA-1.2.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pyOpenRPA-1.2.7.dist-info/METADATA,sha256=BzhjdELqdQkNJuXEIKiYjzPFg1-cNp2PHBfF9sVzBT4,3744 +pyOpenRPA-1.2.7.dist-info/RECORD,, +pyOpenRPA-1.2.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyOpenRPA-1.2.7.dist-info/WHEEL,sha256=qB97nP5e4MrOsXW5bIU5cUn_KSVr10EV0l-GCHG9qNs,97 +pyOpenRPA-1.2.7.dist-info/top_level.txt,sha256=RPzwQXgYBRo_m5L3ZLs6Voh8aEkMeT29Xsul1w1qE0g,10 pyOpenRPA/.idea/inspectionProfiles/profiles_settings.xml,sha256=YXLFmX7rPNGcnKK1uX1uKYPN0fpgskYNe7t0BV7cqkY,174 pyOpenRPA/.idea/misc.xml,sha256=V-fQnOz-bYEZULgfbFgm-8mURphZrKfXMSd0wKjeEyA,188 pyOpenRPA/.idea/modules.xml,sha256=Q__U1JIA2cjxbLRXAv-SfYY00fZA0TNlpkkbY4s3ncg,277 @@ -11,9 +11,9 @@ pyOpenRPA/.idea/pyOpenRPA.iml,sha256=EXh41F8lqRiSBMVg-n2tKaEaHC6_3gGSuKkPJA12Na0 pyOpenRPA/.idea/vcs.xml,sha256=2HygA1oRAwc3VBf-irxHrX5JJG9DXuQwrN0BlubhoKY,191 pyOpenRPA/.idea/workspace.xml,sha256=6tJZehshdK4And6tEoUvkIB0KE7waL_NnTSkTYYAeFA,3802 pyOpenRPA/Agent/A2O.py,sha256=PlIZZCTnVrYF2i6DSAi_KbzZfc2gtcBPmOerrEZq68U,1718 -pyOpenRPA/Agent/O2A.py,sha256=vu7UgiB0qY6-1i9qVWEBGyXWSi68TTNfkvnpMIZH7Vo,4458 -pyOpenRPA/Agent/Processor.py,sha256=Co8nWpffgsnEE_TpG9WrpKxz3N0sDF7eFnKxDOk1sd8,4653 -pyOpenRPA/Agent/__Agent__.py,sha256=JcMFvSC3_M94HEdZe8AK2IHuJOxeDJi4RpnY_LivWpM,10639 +pyOpenRPA/Agent/O2A.py,sha256=XHl5nytUoUqfPvmYWh5auYo-s0GIThNmkOA9ON-JCis,5535 +pyOpenRPA/Agent/Processor.py,sha256=xNZfQ_HcV-qm_x90tBLKYJqvnENiTqHSoUk2LhDfqWQ,6346 +pyOpenRPA/Agent/__Agent__.py,sha256=RLy7YQyTm_IF9kjZ22k7hd5E7wRTBSau_zsxBtW17l0,12554 pyOpenRPA/Agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Agent/__pycache__/A2O.cpython-37.pyc,, pyOpenRPA/Agent/__pycache__/O2A.cpython-37.pyc,, @@ -22,20 +22,28 @@ pyOpenRPA/Agent/__pycache__/__Agent__.cpython-37.pyc,, pyOpenRPA/Agent/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Agent/readme.md,sha256=QF_Bnv204OK3t1JUEhjfICkxFmSdX6bvaRl_HI6lH9I,19 pyOpenRPA/Info.md,sha256=u4Nv-PjniSF0Zlbtr6jEJX2vblK3_1zhSLNUgOdtDaA,85 -pyOpenRPA/Orchestrator/BackwardCompatibility.py,sha256=a2UZINDnHCKZVvHtOOPMyFZmDynzfcyQhFJCEEMhadY,34599 +pyOpenRPA/Orchestrator/BackwardCompatibility.py,sha256=CpJtOc_WnV14AGIU7FKVRuemlf9bSr4Eo5_67wuyi_k,37506 pyOpenRPA/Orchestrator/ControlPanel.py,sha256=OzS8HjG__8OZgqhajr8L8owyugXPuSLWHLtXuKdEP78,103 -pyOpenRPA/Orchestrator/Core.py,sha256=Kjphtu0g6iaS4D_fIWmzRaLLgBQ9fcwccpQJhOETTAc,521 -pyOpenRPA/Orchestrator/Processor.py,sha256=Z1SglmX6ykTLifD3M1mzWEJQUgweWo6HjjCjHldjGyM,8594 +pyOpenRPA/Orchestrator/Core.py,sha256=OHa3mSC3_wRAizqrWBVjlR6ln4-xVVvBpOSnWl6qVvY,529 +pyOpenRPA/Orchestrator/Managers/ControlPanel.py,sha256=BgtLjb6PR6kTlOjPLCg2YGP458LS9JOaYEfNurhS0nk,16544 +pyOpenRPA/Orchestrator/Managers/Git.py,sha256=dgXx2UzSwiEev4ov2hBbb-5MhXVhFKWZo2lmr19QSCQ,12582 +pyOpenRPA/Orchestrator/Managers/Process.py,sha256=7T_qofdkRJHdPQbaiEsTDOboImSf2N6d_Ku513rURkw,41369 +pyOpenRPA/Orchestrator/Managers/__init__.py,sha256=4my0XiwmI_QLRQVhOzNvWTggCosF3tb2yRxGkehOCq0,71 +pyOpenRPA/Orchestrator/Managers/__pycache__/ControlPanel.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Managers/__pycache__/Git.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Managers/__pycache__/Process.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Managers/__pycache__/__init__.cpython-37.pyc,, +pyOpenRPA/Orchestrator/Processor.py,sha256=FtNmdIsBStkLHLlOe6MDWzSmZv9m7ntlQs-NirA6OgQ,10264 pyOpenRPA/Orchestrator/ProcessorOld.py,sha256=Vh5zLRpWWf-vt9CCYI8nDY7oaefiufnu6Pnl4tp27pY,13749 pyOpenRPA/Orchestrator/RobotRDPActive/CMDStr.py,sha256=6otw1WnR2_evvQ5LGyOVh0BLk_nTdilViGub7p56fXQ,1531 pyOpenRPA/Orchestrator/RobotRDPActive/Clipboard.py,sha256=YB5HJL-Qf4IlVrFHyRv_ZMJ0Vo4vjyYqWKjvrTnf1k4,1564 -pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py,sha256=Qwf194uO_wcO3kBe2hTI0py90ZC1ejDUGeh6UWEfavc,27789 +pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py,sha256=_rTktGLcPaQoPUi482vnUWoeuB0zZcyS3k5kwEbvnM8,28021 pyOpenRPA/Orchestrator/RobotRDPActive/ConnectorExceptions.py,sha256=wwH9JOoMFFxDKQ7IyNyh1OkFkZ23o1cD8Jm3n31ycII,657 -pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py,sha256=AQ_u9QVSLpce9hhSacm3UT98bErc636MXza4Q6mHsSc,12264 +pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py,sha256=bMahu6wRznmoLYsopgbNOLqufcQnEDanIepuGvXIsac,12405 pyOpenRPA/Orchestrator/RobotRDPActive/Recovery.py,sha256=jneD474V-ZBYnmQFxWoY_feGNMSL0lGaRK6TEfQ6gOc,2954 pyOpenRPA/Orchestrator/RobotRDPActive/RobotRDPActive.py,sha256=5FX48HlIn8NKfs7q_rp3lpumWtNcwdHq7J8ygnOwU_g,12284 pyOpenRPA/Orchestrator/RobotRDPActive/Scheduler.py,sha256=21N0ilFzWI1mj3X5S9tPMgwvG7BviuBxfTuqBY85Hy4,9144 -pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp,sha256=JEMVYkEmNcfg_p8isdIyvj9E-2ZB5mj-R3MkcNMKxkA,2426 +pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp,sha256=YjIxCXyIvDtZx-MPpyHPj3quT9dlUZPuuILiB21eRpU,2462 pyOpenRPA/Orchestrator/RobotRDPActive/Timer.py,sha256=y8--fUvg10qEFomecl_cmdWpdGjarZBlFpMbs_GvzoQ,1077 pyOpenRPA/Orchestrator/RobotRDPActive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Orchestrator/RobotRDPActive/__main__.py,sha256=z9PaUK4_nBiGd0YJdYVHV_rFx6VjZaxrrmKxSyoTFwY,2508 @@ -51,7 +59,7 @@ pyOpenRPA/Orchestrator/RobotRDPActive/__pycache__/Timer.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotRDPActive/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotRDPActive/__pycache__/__main__.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/ConsoleStart.bat,sha256=_HNadUKHOYI5y6foG3srh8wjSzhX33xaKNylFtDjOJk,114 -pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py,sha256=TV-YisVqa_uGiyJLG9oK4u-5aDjGiFYZFh1dPjOgYc8,492 +pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py,sha256=6gIiWFyacMvudv9t3D7jxz6uVHUt53b-SS3Ebqo2_lo,492 pyOpenRPA/Orchestrator/RobotScreenActive/Screen.py,sha256=VnYcvCVymrD35l2J4ln_tlVn7CilZhxE4Ggw9P-OhIw,606 pyOpenRPA/Orchestrator/RobotScreenActive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Orchestrator/RobotScreenActive/__main__.py,sha256=JASxDDVKWUU7DAbDkRrGTrPk-P7LZchTZFh8usp6b4U,593 @@ -59,21 +67,21 @@ pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/Monitor.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/Screen.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Orchestrator/RobotScreenActive/__pycache__/__main__.cpython-37.pyc,, -pyOpenRPA/Orchestrator/Server.py,sha256=Ke89zh5iZezhA_qyQ3sfWJmL9bfc1rBBYeq-WzznfsE,30432 -pyOpenRPA/Orchestrator/ServerSettings.py,sha256=zVI-brV_58uKJ-MWESTZGYv89nN_0iW_-HfVNhip4jE,32890 -pyOpenRPA/Orchestrator/SettingsTemplate.py,sha256=-LIyHRKVnbrALAyss6r6L56jBX_yOAdMEhnj8N2fN9A,20532 +pyOpenRPA/Orchestrator/Server.py,sha256=DbvHZTTItOBbYiykn2GMjG2r6iUsXUqQZZjjnYPnZ8Q,32455 +pyOpenRPA/Orchestrator/ServerSettings.py,sha256=YaZem-osX1bD8gnJyuYU0PWDOnhqkacmXjXXHYLqW3g,31731 +pyOpenRPA/Orchestrator/SettingsTemplate.py,sha256=ij1quU5ENu43QjccHYsy8SwPCGibqJNGwcDaoF7cAPo,21340 pyOpenRPA/Orchestrator/Timer.py,sha256=HvYtEeH2Q5WVVjgds9XaBpWRmvZgwgBXurJDdVVq_T0,2097 pyOpenRPA/Orchestrator/Utils/LoggerHandlerDumpLogList.py,sha256=hD47TiOuKR-G8IWu9lJD2kG28qlH7YZV63i3qv1N5Dk,681 pyOpenRPA/Orchestrator/Utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 pyOpenRPA/Orchestrator/Utils/__pycache__/LoggerHandlerDumpLogList.cpython-37.pyc,, pyOpenRPA/Orchestrator/Utils/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Orchestrator/Web/Basic.py,sha256=pPH55rPwZz1ktpzNIcC51jeV2MgZI10Zf0Q0DncihDw,7757 -pyOpenRPA/Orchestrator/Web/Index.js,sha256=Blo3LHe_a3zrW7MqYo4BSIwoOx7nlO7Ko9LWxfeqU_o,39090 -pyOpenRPA/Orchestrator/Web/Index.xhtml,sha256=a4N_reLA6_Zb2KXiL73a7cWtJwO0W0Dr5lZ-RpUwuI0,16428 +pyOpenRPA/Orchestrator/Web/Index.js,sha256=YACiZAvjr6NmFlDhQu6urkJp49BX7L8WJU9p-MeIlCI,43508 +pyOpenRPA/Orchestrator/Web/Index.xhtml,sha256=XGPCG-qaFsAcoaXnZe1mEjPEqwcVQZ3NVPjtKX8gV4c,19192 pyOpenRPA/Orchestrator/Web/__pycache__/Basic.cpython-37.pyc,, pyOpenRPA/Orchestrator/Web/favicon.ico,sha256=6S8XwSQ_3FXPpaX6zYkf8uUewVXO9bHnrrDHEoWrEgw,112922 -pyOpenRPA/Orchestrator/__Orchestrator__.py,sha256=n-PrWqEDql1POF4bM-r9cTun9WZUqzIYRjxDEmV7xAM,119846 -pyOpenRPA/Orchestrator/__init__.py,sha256=f1RFDzOkL3IVorCtqogjGdXYPtHH-P-y-5CqT7PGy7A,183 +pyOpenRPA/Orchestrator/__Orchestrator__.py,sha256=wIRuMdoOwudlchdoMua0u2jO5AxCeATw0RSsQ9bKreo,150251 +pyOpenRPA/Orchestrator/__init__.py,sha256=nJhjYtBXKOUNX_yNu1rRFk5y9cDz6AFiL0M6KgX_utQ,207 pyOpenRPA/Orchestrator/__main__.py,sha256=czJrc7_57WiO3EPIYfPeF_LG3pZsQVmuAYgbl_YXcVU,273 pyOpenRPA/Orchestrator/__pycache__/BackwardCompatibility.cpython-37.pyc,, pyOpenRPA/Orchestrator/__pycache__/ControlPanel.cpython-37.pyc,, @@ -337,10 +345,10 @@ pyOpenRPA/Tools/SafeSource/__pycache__/Crypter.cpython-37.pyc,, pyOpenRPA/Tools/SafeSource/__pycache__/DistrCreate.cpython-37.pyc,, pyOpenRPA/Tools/SafeSource/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/Tools/SafeSource/__pycache__/__main__.cpython-37.pyc,, -pyOpenRPA/Tools/Terminator.py,sha256=VcjX3gFXiCGu3MMCidhrTNsmC9wsAqfjRJdTSU9fLnU,2178 +pyOpenRPA/Tools/StopSafe.py,sha256=BNTtMmvsRE1Wtri3EkwhoBi6gGOjEPRQnJSV1C03c84,2176 pyOpenRPA/Tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pyOpenRPA/Tools/__pycache__/Terminator.cpython-37.pyc,, +pyOpenRPA/Tools/__pycache__/StopSafe.cpython-37.pyc,, pyOpenRPA/Tools/__pycache__/__init__.cpython-37.pyc,, -pyOpenRPA/__init__.py,sha256=fI-2Npv3ZqIEhm1omXoocfYZw7PY1Ccf_pHXi_bvI0w,174 +pyOpenRPA/__init__.py,sha256=thBwsh1ouqe_mKoJCCECIcKbo7oF6WPz9ZV52uvuPQM,174 pyOpenRPA/__pycache__/__init__.cpython-37.pyc,, pyOpenRPA/test.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/REQUESTED b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/REQUESTED similarity index 100% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/REQUESTED rename to Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/REQUESTED diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/WHEEL b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/WHEEL similarity index 100% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/WHEEL rename to Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/WHEEL diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/top_level.txt b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/top_level.txt similarity index 100% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.6.dist-info/top_level.txt rename to Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA-1.2.7.dist-info/top_level.txt diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/O2A.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/O2A.py index 0994a0c0..853df131 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/O2A.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/O2A.py @@ -1,4 +1,5 @@ import requests, time, json +from . import Processor # O2A - Data flow Orchestrator to Agent # f"{lProtocolStr}://{lHostStr}:{lPortInt}/pyOpenRPA/Agent/O2A" @@ -20,6 +21,11 @@ def O2A_Loop(inGSettings): while inGSettings["O2ADict"]["IsOnlineBool"]: # Send request to the orchestrator server lRequestBody = None + # ConnectionError - CE + lCEPhaseFastTimeLastGoodFloat = time.time() + lCEPhaseFastDurationSecFloat = inGSettings['O2ADict']['ConnectionTimeoutSecFloat'] + lCEPhaseFastRetrySecFloat = inGSettings['O2ADict']['RetryTimeoutSecFloat']/5.0 + lCEPhaseLongRetrySecFloat = inGSettings['O2ADict']['RetryTimeoutSecFloat']*12.0 try: lProtocolStr= "https" if inGSettings["OrchestratorDict"]["IsHTTPSBool"] else "http" lHostStr = inGSettings["OrchestratorDict"]["HostStr"] @@ -27,6 +33,7 @@ def O2A_Loop(inGSettings): lURLStr=f"{lProtocolStr}://{lHostStr}:{lPortInt}/pyOpenRPA/Agent/O2A" lDataDict = { "HostNameUpperStr": inGSettings["AgentDict"]["HostNameUpperStr"], "UserUpperStr": inGSettings["AgentDict"]["UserUpperStr"], "ActivityLastGUIDStr": lActivityLastGUIDStr} lResponse = requests.post(url= lURLStr, cookies = {"AuthToken":inGSettings["OrchestratorDict"]["SuperTokenStr"]}, json=lDataDict, timeout=inGSettings["O2ADict"]["ConnectionTimeoutSecFloat"]) + lCEPhaseFastTimeLastGoodFloat = time.time() if lResponse.status_code != 200: if lL: lL.warning(f"Agent can not connect to Orchestrator. Below the response from the orchestrator:{lResponse}") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) @@ -34,30 +41,39 @@ def O2A_Loop(inGSettings): lRequestBody = lResponse.text lBodyLenInt = len(lRequestBody) if lBodyLenInt != 0: # CHeck if not empty result when close the connection from orch - lQueueItem = lResponse.json() # Try to get JSON - # Append QUEUE item in ProcessorDict > ActivityList - lActivityLastGUIDStr = lQueueItem["GUIDStr"] - inGSettings["ProcessorDict"]["ActivityList"].append(lQueueItem) - # Log full version if bytes size is less than limit . else short - lAgentLimitLogSizeBytesInt = 500 - if lBodyLenInt <= lAgentLimitLogSizeBytesInt: - if lL: lL.info(f"ActivityItem was received from orchestrator: {lQueueItem}"); - else: - if lL: lL.info(f"ActivityItem was received from orchestrator: Was supressed because of big size. Max is {lAgentLimitLogSizeBytesInt} bytes"); + lQueueList = lResponse.json() # Try to get JSON + for lQueueItem in lQueueList: + # Append QUEUE item in ProcessorDict > ActivityList + lActivityLastGUIDStr = lQueueItem["GUIDStr"] + # Check if ActivityItem ["ThreadBool"] = False > go sync mode in processor queue; Else: New thread + if lQueueItem.get("ThreadBool",False) == False: + inGSettings["ProcessorDict"]["ActivityList"].append(lQueueItem) + else: + Processor.ProcessorRunAsync(inGSettings=inGSettings,inActivityList=[lQueueItem]) + # Log full version if bytes size is less than limit . else short + lAgentLimitLogSizeBytesInt = 500 + if lBodyLenInt <= lAgentLimitLogSizeBytesInt: + if lL: lL.info(f"ActivityItem from orchestrator: {lQueueItem}"); + else: + if lL: lL.info(f"ActivityItem from orchestrator: Supressed - big size. Size is {lBodyLenInt} bytes"); else: if lL: lL.debug(f"Empty response from the orchestrator - loop when refresh the connection between Orc and Agent"); except requests.exceptions.ConnectionError as e: - if lL: lL.error(f"O2A Connection error - orchestrator is not available. Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") - time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) + if time.time() - lCEPhaseFastTimeLastGoodFloat <= lCEPhaseFastDurationSecFloat: + if lL: lL.error(f"O2A Connection error - orchestrator is not available. Sleep for {lCEPhaseFastRetrySecFloat} s.") + time.sleep(lCEPhaseFastRetrySecFloat) + else: + if lL: lL.error(f"O2A Connection error - orchestrator is not available. Sleep for {lCEPhaseLongRetrySecFloat} s.") + time.sleep(lCEPhaseLongRetrySecFloat) except ConnectionResetError as e: - if lL: lL.error(f"O2A Connection reset error - orchestrator is not available. Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") + if lL: lL.error(f"O2A Connection reset error - orchestrator is not available. Sleep for {inGSettings['O2ADict']['RetryTimeoutSecFloat']} s.") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) except json.decoder.JSONDecodeError as e: if lL: lL.error(f"O2A JSON decode error - See body of the recieved content from the Orchestrator: {lRequestBody}") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) except requests.exceptions.Timeout as e: - if lL: lL.exception(f"O2A requests timeout error (no response for long time). Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") + if lL: lL.exception(f"O2A requests timeout error (no response for long time). Sleep for {inGSettings['O2ADict']['RetryTimeoutSecFloat']} s.") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) except Exception as e: - if lL: lL.exception(f"O2A Error handler. Sleep for {inGSettings['A2ODict']['RetryTimeoutSecFloat']} s.") + if lL: lL.exception(f"O2A Error handler. Sleep for {inGSettings['O2ADict']['RetryTimeoutSecFloat']} s.") time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/Processor.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/Processor.py index a96a973f..0fe9a965 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/Processor.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/Processor.py @@ -13,6 +13,7 @@ def ProcessorRunSync(inGSettings): # "ArgGSettings": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) # "ArgLogger": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) # "GUIDStr": "sadasd-asdas-d-asdasd", # ActivityItem GUID which identify the Activity + # "ThreadBool": False # }, ], "AliasDefDict": {}, # Storage for def with Str alias. To use it see pyOpenRPA.Orchestrator.ControlPanel @@ -36,6 +37,35 @@ def ProcessorRunSync(inGSettings): else: time.sleep(inGSettings["ProcessorDict"]["CheckIntervalSecFloat"]) # Sleep when list is empty +# Run processor Async +def ProcessorRunAsync(inGSettings, inActivityList): + """ + "inActivityList": [ # List of the activities + # { + # "Def":"DefAliasTest", # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + # "ArgList":[1,2,3], # Args list + # "ArgDict":{"ttt":1,"222":2,"dsd":3}, # Args dictionary + # "ArgGSettings": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "ArgLogger": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "GUIDStr": "sadasd-asdas-d-asdasd", # ActivityItem GUID which identify the Activity + # "ThreadBool": True + # }, + """ + def __process__(inGSettings, inActivityList): + for lActivityItem in inActivityList: + lL = inGSettings["Logger"] # Logger alias + if lL: lL.debug(f'ActivityItem in new thread') + lResultList = ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item + #Some help code + if len(lResultList) == 0: lResultList= [None] + # Send result to Orc if we have GUIDStr + if "GUIDStr" in lActivityItem: + # Def to send to Orc + A2O.ActivityReturnDictSend(inGSettings=inGSettings, inActivityItemGUIDStr=lActivityItem["GUIDStr"],inReturn=lResultList[0]) + # Start in new thread + lThread = threading.Thread(target=__process__,kwargs={"inGSettings": inGSettings, "inActivityList": inActivityList}) + lThread.start() + # Execute ActivityItem list # return the def result def ActivityListExecute(inGSettings, inActivityList): diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py index 9ac93c19..f7d448de 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Agent/__Agent__.py @@ -2,6 +2,9 @@ import threading, socket, getpass, sys, uuid, subprocess, base64, psutil, getpas from . import O2A, A2O # Data flow Orchestrator To Agent from . import Processor # Processor Queue from subprocess import CREATE_NEW_CONSOLE # Flag to create new process in another CMD +import os + +gSettings = None # Create binary file by the base64 string (safe for JSON transmition) def OSFileBinaryDataBase64StrCreate(inFilePathStr, inFileDataBase64Str,inGSettings = None): @@ -58,16 +61,37 @@ def OSFileBinaryDataBase64StrReceive(inFilePathStr, inGSettings=None): :param inGSettings: global settings of the Agent (singleton) :return: File content in string base64 format (use base64.b64decode to decode data). Return None if file is not exist """ - lFile = open(inFilePathStr, "rb") - lFileDataBytes = lFile.read() - lFile.close() - lFileDataBase64Str = base64.b64encode(lFileDataBytes).decode("utf-8") lL = inGSettings.get("Logger", None) if type(inGSettings) is dict else None - lMessageStr = f"AGENT Binary file {inFilePathStr} has been read." - if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + lFileDataBase64Str = None + if os.path.exists(inFilePathStr): + lFile = open(inFilePathStr, "rb") + lFileDataBytes = lFile.read() + lFile.close() + lFileDataBase64Str = base64.b64encode(lFileDataBytes).decode("utf-8") + lMessageStr = f"OSFileBinaryDataBase64StrReceive: file {inFilePathStr} has been read" + if lL: lL.debug(lMessageStr) + #A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + else: + if lL: lL.debug(f"OSFileBinaryDataBase64StrReceive: file {inFilePathStr} is not exists - return None") return lFileDataBase64Str +def OSFileMTimeGet(inFilePathStr: str) -> float or None: + """ + Read file modification time timestamp format (float) + + :param inFilePathStr: File path to read + :return: timestamp (float) Return None if file is not exist + """ + global gSettings + lL = gSettings.get("Logger", None) if type(gSettings) is dict else None + lFileMTimeFloat = None + if os.path.exists(inFilePathStr): + lFileMTimeFloat = os.path.getmtime(inFilePathStr) + if lL: lL.debug(f"OSFileMTimeGet: file {inFilePathStr} has been read") + else: + if lL: lL.debug(f"OSFileMTimeGet: file {inFilePathStr} is not exists - return None") + return lFileMTimeFloat + def OSFileTextDataStrReceive(inFilePathStr, inEncodingStr="utf-8", inGSettings=None): """ Read text file in the agent GUI session @@ -77,17 +101,21 @@ def OSFileTextDataStrReceive(inFilePathStr, inEncodingStr="utf-8", inGSettings=N :param inGSettings: global settings of the Agent (singleton) :return: File text content in string format (use base64.b64decode to decode data). Return None if file is not exist """ - lFile = open(inFilePathStr, "r", encoding=inEncodingStr) - lFileDataStr = lFile.read() - lFile.close() + lFileDataStr = None lL = inGSettings.get("Logger", None) if type(inGSettings) is dict else None - lMessageStr = f"AGENT Text file {inFilePathStr} has been read." - if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if os.path.exists(inFilePathStr): + lFile = open(inFilePathStr, "r", encoding=inEncodingStr) + lFileDataStr = lFile.read() + lFile.close() + lMessageStr = f"OSFileTextDataStrReceive: file {inFilePathStr} has been read" + if lL: lL.info(lMessageStr) + #A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + else: + if lL: lL.info(f"OSFileTextDataStrReceive: file {inFilePathStr} is not exists - return None") return lFileDataStr # Send CMD to OS. Result return to log + Orchestrator by the A2O connection -def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251"): +def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251", inCaptureBool = True): """ Execute CMD on the Agent daemonic process @@ -95,18 +123,21 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inGSettings: Agent global settings dict :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True - !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: """ lResultStr = "" + # New feature + if inSendOutputToOrchestratorLogsBool == False and inCaptureBool == False: + inCMDStr = f"start {inCMDStr}" # Subdef to listen OS result - def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None): + def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None, inCaptureBool = True): lL = inGSettings.get("Logger",None) if type(inGSettings) is dict else None lResultStr = "" lOSCMDKeyStr = str(uuid.uuid4())[0:4].upper() lCMDProcess = None - if inSendOutputToOrchestratorLogsBool == True: + if inCaptureBool == True: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=None, stderr=None, @@ -114,12 +145,15 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche lListenBool = True lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been STARTED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) lMessageStr = f"{lOSCMDKeyStr}: {inCMDStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) while lListenBool: - if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + #if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + if inCaptureBool == True: # Capturing can be turned on! lOutputLineBytes = lCMDProcess.stdout.readline() if lOutputLineBytes == b"": lListenBool = False @@ -127,7 +161,8 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche if lStr.endswith("\n"): lStr = lStr[:-1] lMessageStr = f"{lOSCMDKeyStr}: {lStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) lResultStr+=lStr else: #Capturing is not turned on - wait until process will be closed lCMDProcessPoll = lCMDProcess.poll() @@ -137,15 +172,16 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrche lListenBool = False lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been FINISHED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) return lResultStr # New call if inRunAsyncBool: - lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr }) + lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr, "inCaptureBool": inCaptureBool }) lThread.start() lResultStr="ActivityList has been started in async mode - no output is available here." else: - lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr) + lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr, inCaptureBool=inCaptureBool) #lCMDCode = "cmd /c " + inCMDStr #subprocess.Popen(lCMDCode) #lResultCMDRun = 1 # os.system(lCMDCode) @@ -180,7 +216,8 @@ def ProcessWOExeUpperUserListGet(): # Main def def Agent(inGSettings): lL = inGSettings["Logger"] - + global gSettings + gSettings = inGSettings # Append Orchestrator def to ProcessorDictAlias lModule = sys.modules[__name__] lModuleDefList = dir(lModule) diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py index 727376ee..b8f7052b 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/BackwardCompatibility.py @@ -2,7 +2,7 @@ # !!! ATTENTION: Backward compatibility has been started from v1.1.13 !!! # So you can use config of the orchestrator 1.1.13 in new Orchestrator versions and all will be ok :) (hope it's true) import win32security, json, datetime, time, copy - +import schedule # # # # # # # # # # # # # # # # # # # # Backward compatibility Web defs up to v1.2.0 # # # # # # # # # # # # # # # # # # # @@ -488,4 +488,48 @@ def Update(inGSettings): if "AgentLimitLogSizeBytesInt" not in inGSettings["ServerDict"]: inGSettings["ServerDict"]["AgentLimitLogSizeBytesInt"] = 300 if lL: lL.warning( - f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > AgentLimitLogSizeBytesInt") # Log about compatibility \ No newline at end of file + f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > AgentLimitLogSizeBytesInt") # Log about compatibility + # Remove ControlPanelDict and CPDict > go to ServerDict > ControlPanelDict + if "ControlPanelDict" in inGSettings: + del inGSettings["ControlPanelDict"] + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Remove old key: ControlPanelDict") # Log about compatibility + if "CPDict" in inGSettings: + for lCPKeyStr in inGSettings["CPDict"]: + lCPItemDict = inGSettings["CPDict"][lCPKeyStr] + __Orchestrator__.WebCPUpdate(inCPKeyStr=lCPKeyStr,inHTMLRenderDef=lCPItemDict["HTMLRenderDef"], + inJSONGeneratorDef=lCPItemDict["JSONGeneratorDef"], + inJSInitGeneratorDef=lCPItemDict["JSInitGeneratorDef"]) + del inGSettings["CPDict"] + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Remove old key: CPDict") # Log about compatibility + if "ControlPanelDict" not in inGSettings["ServerDict"]: + inGSettings["ServerDict"]["ControlPanelDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ServerDict > ControlPanelDict") # Log about compatibility + # ManagersProcessDict + if "ManagersProcessDict" not in inGSettings: + inGSettings["ManagersProcessDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ManagersProcessDict") # Log about compatibility + # Check "SchedulerDict": { "Schedule": schedule, # https://schedule.readthedocs.io/en/stable/examples.html + if inGSettings.get("SchedulerDict",{}).get("Schedule",None) is None: + inGSettings["SchedulerDict"]["Schedule"] = schedule + if lL: lL.warning(f"Backward compatibility (v1.2.4 to v1.2.7): Create new module schedule (schedule.readthedocs.io)") # Log about compatibility + # ManagersGitDict + if "ManagersGitDict" not in inGSettings: + inGSettings["ManagersGitDict"]={} + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ManagersGitDict") # Log about compatibility + # ProcessorDict > ActivityItemNowDict + if "ActivityItemNowDict" not in inGSettings["ProcessorDict"]: + inGSettings["ProcessorDict"]["ActivityItemNowDict"]=None + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): Create new key: ProcessorDict > ActivityItemNowDict") # Log about compatibility + + # # "UACBool": True # True - check user access before do this URL item + for lURLItemDict in inGSettings["ServerDict"]["URLList"]: + if "UACBool" not in lURLItemDict: + lURLItemDict["UACBool"]=None + if lL: lL.warning( + f"Backward compatibility (v1.2.4 to v1.2.7): ServerDict > URLList > item: add UACBool = None") # Log about compatibility diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py index 329386fe..f92f7442 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Core.py @@ -4,7 +4,7 @@ import threading def IsProcessorThread(inGSettings): return inGSettings["ProcessorDict"]["ThreadIdInt"] == threading.get_ident() -def IsOrchestratorInitialized(inGSettings): +def IsOrchestratorInitialized(inGSettings) -> bool: """ Check if Orchestrator will be successfully initialized diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py new file mode 100644 index 00000000..cef4acd4 --- /dev/null +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/ControlPanel.py @@ -0,0 +1,348 @@ +from ... import Orchestrator +import jinja2 +import os +from inspect import signature # For detect count of def args +from ..Web import Basic +import operator +import math + +class ControlPanel(): + """ + Manage your control panel on the orchestrator + + Control panel has 3 events types: + - onRefreshHTML - run every n (see settings) second to detect changes in HTML control panel. + - onRefreshJSON - run every n (see settings) second to detect changes in JSON data container to client side. + - onInitJS - run when client reload the Orchestrator web page + + .. code-block:: python + + # Usage example: + lCPManager = Orchestrator.Managers.ControlPanel(inControlPanelNameStr="TestControlPanel", + inRefreshHTMLJinja2TemplatePathStr="ControlPanel\\test.html", inJinja2TemplateRefreshBool = True) + + + + If you use Jinja2 you can use next data context: + StorageRobotDict: Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr), + ControlPanelInstance: self, + OrchestratorModule:Orchestrator, + RequestInstance: inRequest, + UserInfoDict: Orchestrator.WebUserInfoGet(inRequest=inRequest), + UserUACDict: Orchestrator.UACUserDictGet(inRequest=inRequest), + UserUACCheckDef: inRequest.UACClientCheck, + EnumerateDef: enumerate, + OperatorModule: operator, + MathModule: math + + You can modify jinja context by use the function: + Jinja2DataUpdateDictSet + + .. code-block:: html + Hello my control panel! + You can use any def from Orchestrator module here in Jinja2 HTML template: + Example: OrchestratorModule.OSCMD(inCMDStr="notepad") + {{MathModule.pi}} + {% if UserInfoDict['UserNameUpperStr']=="ND" %} + YES - IT IS ND + {% endif %} + + """ + mControlPanelNameStr = None + # Jinja2 consolidated + mJinja2TemplateRefreshBool = None + mJinja2DataUpdateDict = None + + # RefreshHTML block + mRefreshHTMLJinja2TemplatePathStr = None + mRefreshHTMLJinja2TemplateFileNameStr = None + mRefreshHTMLJinja2Loader = None + mRefreshHTMLJinja2Env = None + mRefreshHTMLJinja2Template = None + + # InitJS block + mInitJSJinja2TemplatePathStr = None + mInitJSJinja2TemplateFileNameStr = None + mInitJSJinja2Loader = None + mInitJSJinja2Env = None + mInitJSJinja2Template = None + + mBackwardCompatibilityHTMLDef = None + mBackwardCompatibilityJSDef = None + mBackwardCompatibilityJSONDef = None + + mRobotNameStr = None + + def __init__(self, inControlPanelNameStr, inRefreshHTMLJinja2TemplatePathStr = None, inJinja2TemplateRefreshBool = False, inRobotNameStr = None): + """ + Constructor of the control panel manager + + :param inControlPanelNameStr: + :param inJinja2TemplatePathStr: + """ + # Connect self witch pyOpenRPA via ControlPanelNameStr + if inControlPanelNameStr in Orchestrator.GSettingsGet()["ServerDict"]["ControlPanelDict"]: + raise Exception(f"Another control panel with name {inControlPanelNameStr} is already exists. Please resolve the error and restart") + Orchestrator.GSettingsGet()["ServerDict"]["ControlPanelDict"][inControlPanelNameStr] = self + self.RefreshHTMLJinja2TemplatePathSet(inJinja2TemplatePathStr = inRefreshHTMLJinja2TemplatePathStr) + self.mJinja2TemplateRefreshBool = inJinja2TemplateRefreshBool + self.mControlPanelNameStr = inControlPanelNameStr # Set the name of the control panel + self.mRobotNameStr = inRobotNameStr # Set the robot name for robot it execute + + def Jinja2DataUpdateDictSet(self, inJinja2DataUpdateDict): + """ + Set the data dict from the Jinja2 context (you can add some new params) + + :param inJinja2DataUpdateDict: dict, which will be appended to main data context + :return: None + """ + self.mJinja2DataUpdateDict = inJinja2DataUpdateDict + + def RefreshHTMLJinja2TemplatePathSet(self, inJinja2TemplatePathStr): + """ + Create Jinja2 env and load the template html + + :param inJinja2TemplatePathStr: + :return: + """ + try: + if inJinja2TemplatePathStr is not None: + lSystemLoaderPathStr = "/".join(inJinja2TemplatePathStr.split("\\")[0:-1]) + lTemplateFileNameStr = inJinja2TemplatePathStr.split("\\")[-1] + self.mRefreshHTMLJinja2TemplateFileNameStr = lTemplateFileNameStr + self.mRefreshHTMLJinja2Loader = jinja2.FileSystemLoader(lSystemLoaderPathStr) + self.mRefreshHTMLJinja2Env = jinja2.Environment(loader=self.mRefreshHTMLJinja2Loader, trim_blocks=True) + self.mRefreshHTMLJinja2Template = self.mRefreshHTMLJinja2Env.get_template(lTemplateFileNameStr) + except Exception as e: + Orchestrator.OrchestratorLoggerGet().exception("EXCEPTION WHEN INIT Jinja2") + + def RefreshHTMLJinja2StrGenerate(self, inDataDict): + """ + Generate the HTML str from the Jinja2. Pass the context inDataDict + :param inDataDict: + :return: + """ + if self.mJinja2TemplateRefreshBool == True: + self.mRefreshHTMLJinja2Template = self.mRefreshHTMLJinja2Env.get_template(self.mRefreshHTMLJinja2TemplateFileNameStr) + lHTMLStr = self.mRefreshHTMLJinja2Template.render(**inDataDict) # Render the template into str + return lHTMLStr + + def InitJSJinja2TemplatePathSet(self, inJinja2TemplatePathStr): + """ + Create Jinja2 env and load the template html + + :param inJinja2TemplatePathStr: + :return: + """ + try: + if inJinja2TemplatePathStr is not None: + lSystemLoaderPathStr = "/".join(inJinja2TemplatePathStr.split("\\")[0:-1]) + lTemplateFileNameStr = inJinja2TemplatePathStr.split("\\")[-1] + self.mInitJSJinja2TemplateFileNameStr = lTemplateFileNameStr + self.mInitJSJinja2Loader = jinja2.FileSystemLoader(lSystemLoaderPathStr) + self.mInitJSJinja2Env = jinja2.Environment(loader=self.mInitJSJinja2Loader, trim_blocks=True) + self.mInitJSJinja2Template = self.mInitJSJinja2Env.get_template(lTemplateFileNameStr) + except Exception as e: + Orchestrator.OrchestratorLoggerGet().exception("EXCEPTION WHEN INIT Jinja2") + + def InitJSJinja2StrGenerate(self, inDataDict): + """ + Generate the HTML str from the Jinja2. Pass the context inDataDict + :param inDataDict: + :return: + """ + if self.mJinja2TemplateRefreshBool == True: + self.mInitJSJinja2Template = self.mInitJSJinja2Env.get_template(self.mInitJSJinja2TemplateFileNameStr) + lHTMLStr = self.mInitJSJinja2Template.render(**inDataDict) # Render the template into str + return lHTMLStr + + def DataDictGenerate(self, inRequest): + """ + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: + """ + lData = { + "StorageRobotDict": None, + "ControlPanelInstance":self, + "OrchestratorModule":Orchestrator, + "RequestInstance": inRequest, + "UserInfoDict": Orchestrator.WebUserInfoGet(inRequest=inRequest), + "UserUACDict": Orchestrator.UACUserDictGet(inRequest=inRequest), + "UserUACCheckDef": inRequest.UACClientCheck, + "EnumerateDef": enumerate, + "OperatorModule": operator, + "MathModule": math + } + # Get the robot storage by the robot name (if you set robot name when init) + if self.mRobotNameStr is not None: + lData["StorageRobotDict"] = Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr) + # Checkj Jinja2DataUpdateDict + if self.mJinja2DataUpdateDict is not None: + lData.update(self.mJinja2DataUpdateDict) + return lData + + def OnRefreshHTMLStr(self, inRequest): + """ + Event to generate HTML code of the control panel when refresh time is over. + Support backward compatibility for previous versions. + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: + """ + lHTMLStr = None + lL = Orchestrator.OrchestratorLoggerGet() + if self.mBackwardCompatibilityHTMLDef is None: + if self.mRefreshHTMLJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mRefreshHTMLJinja2TemplateFileNameStr is not None): + lDataDict = self.OnRefreshHTMLDataDict(inRequest = inRequest) + # Jinja code + lHTMLStr = self.RefreshHTMLJinja2StrGenerate(inDataDict=lDataDict) + else: + lHTMLStr = self.BackwardAdapterHTMLDef(inRequest=inRequest) + # return the str + return lHTMLStr + + def OnRefreshHTMLDataDict(self, inRequest): + """ + Event to prepare data context for the futher Jinja2 HTML generation. You can override this def if you want some thing more data + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: dict + """ + return self.DataDictGenerate(inRequest=inRequest) + + def OnRefreshHTMLHashStr(self, inRequest): + """ + Generate the hash the result output HTML. You can override this function if you know how to optimize HTML rendering. + TODO NEED TO MODIFY ServerSettings to work with Hash because of all defs are need do use Hash + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: None - default, hash function is not determined. Str - hash function is working on! + """ + return None + + def OnRefreshJSONDict(self, inRequest): + """ + Event to transmit some data from server side to the client side in JSON format. Call when page refresh is initialized + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: Dict type + """ + lResultDict = None + if self.mBackwardCompatibilityJSONDef is None: + pass + else: + lResultDict = self.BackwardAdapterJSONDef(inRequest=inRequest) + return lResultDict + + def OnInitJSStr(self, inRequest): + """ + Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once. + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: "" + """ + lJSStr = "" + if self.mBackwardCompatibilityJSDef is None: + if self.mInitJSJinja2Template is not None or (self.mJinja2TemplateRefreshBool == True and self.mInitJSJinja2TemplateFileNameStr is not None): + lDataDict = self.OnInitJSDataDict(inRequest = inRequest) + # Jinja code + lJSStr = self.InitJSJinja2StrGenerate(inDataDict=lDataDict) + else: + lJSStr = self.BackwardAdapterJSDef(inRequest=inRequest) + return lJSStr + + def OnInitJSDataDict(self, inRequest): + """ + Event to prepare data context for the futher Jinja2 JS init generation. You can override this def if you want some thing more data + + :param inRequest: request handler (from http.server import BaseHTTPRequestHandler) + :return: dict + """ + return self.DataDictGenerate(inRequest=inRequest) + + def BackwardAdapterHTMLDef(self,inRequest): + lGS = Orchestrator.GSettingsGet() + lL = Orchestrator.OrchestratorLoggerGet() + # HTMLRenderDef + lItemHTMLRenderDef = self.mBackwardCompatibilityHTMLDef + lResultStr = "" + if lItemHTMLRenderDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) + lHTMLResult = None + lDEFSignature = signature(lItemHTMLRenderDef) # Get signature of the def + lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args + try: + if lDEFARGLen == 1: # def (inGSettings) + lHTMLResult = lItemHTMLRenderDef(lGS) + elif lDEFARGLen == 2: # def (inRequest, inGSettings) + lHTMLResult = lItemHTMLRenderDef(inRequest, lGS) + elif lDEFARGLen == 0: # def () + lHTMLResult = lItemHTMLRenderDef() + # RunFunction + # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" + if type(lHTMLResult) is str: + lResultStr = lHTMLResult + elif "HTMLStr" in lHTMLResult or "JSONDict" in lHTMLResult: + lResultStr = lHTMLResult["HTMLStr"] + else: + # Call backward compatibility HTML generator + lResultStr = Basic.HTMLControlPanelBC(inCPDict=lHTMLResult) + except Exception as e: + if lL: lL.exception(f"Error in control panel HTMLRenderDef. CP Key {self.mControlPanelNameStr}. Exception are below") + return lResultStr + + + def BackwardAdapterJSONDef(self,inRequest): + lGS = Orchestrator.GSettingsGet() + lL = Orchestrator.OrchestratorLoggerGet() + # HTMLRenderDef + lItemJSONGeneratorDef = self.mBackwardCompatibilityJSONDef + lResultDict = {} + if lItemJSONGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) + lJSONResult = None + lDEFSignature = signature(lItemJSONGeneratorDef) # Get signature of the def + lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args + try: + if lDEFARGLen == 1: # def (inGSettings) + lJSONResult = lItemJSONGeneratorDef(lGS) + elif lDEFARGLen == 2: # def (inRequest, inGSettings) + lJSONResult = lItemJSONGeneratorDef(inRequest, lGS) + elif lDEFARGLen == 0: # def () + lJSONResult = lItemJSONGeneratorDef() + # RunFunction + # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" + lType = type(lJSONResult) + if lType is str or lJSONResult is None or lType is int or lType is list or lType is dict or lType is bool or lType is float: + lResultDict = lJSONResult + else: + if lL: lL.warning(f"JSONGenerator return bad type: {str(type(lJSONResult))}, CP Key {self.mControlPanelNameStr}") + except Exception as e: + if lL: lL.exception( + f"Error in control panel JSONGeneratorDef. CP Key {self.mControlPanelNameStr}. Exception are below") + return lResultDict + + def BackwardAdapterJSDef(self,inRequest): + lGS = Orchestrator.GSettingsGet() + lL = Orchestrator.OrchestratorLoggerGet() + # HTMLRenderDef + lJSInitGeneratorDef = self.mBackwardCompatibilityJSDef + lResultStr = "" + if lJSInitGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) + lJSResult = "" + lDEFSignature = signature(lJSInitGeneratorDef) # Get signature of the def + lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args + try: + if lDEFARGLen == 1: # def (inGSettings) + lJSResult = lJSInitGeneratorDef(lGS) + elif lDEFARGLen == 2: # def (inRequest, inGSettings) + lJSResult = lJSInitGeneratorDef(inRequest, lGS) + elif lDEFARGLen == 0: # def () + lJSResult = lJSInitGeneratorDef() + if type(lJSResult) is str: + lResultStr = lJSResult # Add delimiter to some cases + else: + if lL: lL.warning(f"JSInitGenerator return bad type: {str(type(lJSResult))}, CP Key {self.mControlPanelNameStr}") + except Exception as e: + if lL: lL.exception( + f"Error in control panel JSInitGeneratorDef. CP Key {self.mControlPanelNameStr}. Exception are below") + return lResultStr \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py new file mode 100644 index 00000000..7676f6bd --- /dev/null +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Git.py @@ -0,0 +1,249 @@ +import time +import os +from .. import __Orchestrator__ +from . import Process +import threading +from typing import List +from typing import Tuple + +from pyOpenRPA import Orchestrator + +class Git(): + + mAgentHostNameStr = None + mAgentUserNameStr = None + mAbsPathStr = None + mProcessList: List[Tuple] = [] # List of the key turples of the Process instance + + def __init__(self, inAgentHostNameStr=None, inAgentUserNameStr=None, inGitPathStr=""): + """ + Init the Git repo instance. It helps to detect new changes in repo and auto restart services + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process. If None - works with Orc session + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process. If None - works with Orc session + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: + """ + lAbsPathStr = os.path.abspath(inGitPathStr) + lAbsPathUpperStr = lAbsPathStr.upper() + lGS = __Orchestrator__.GSettingsGet() + # Check if Process is not exists in GSettings + if (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr) not in lGS["ManagersGitDict"]: + self.mAbsPathStr = lAbsPathStr + self.mAbsPathUpperStr = lAbsPathUpperStr + self.mAgentHostNameStr = inAgentHostNameStr + self.mAgentUserNameStr = inAgentUserNameStr + lGS["ManagersGitDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr)]=self + else: raise Exception(f"Managers.Git ({inAgentHostNameStr}, {inAgentUserNameStr}, {lAbsPathUpperStr}): Can't init the Git instance because it already inited in early") + + def ProcessConnect(self, inProcess: Process): + """ + Connect process to the Git instance. It will apply to stop safe process when upgrade the repo and than start it + + :param inProcess: Process instance + :type inProcess: Process + """ + lProcessTurple = inProcess.KeyTurpleGet() + if lProcessTurple not in self.mProcessList: + self.mProcessList.append(lProcessTurple) + else: + raise Exception(f"Process with current key is already exists in Git process list.") + + def ProcessListSaveStopSafe(self): + """ + Save the state and do the stop safe for the all processes + Will send safe stop in parallel mode but wait to the end of the safestop for the all processes. After that will continue + """ + lIntervalScheckSecFloat = 5.0 + lThreadList:List[threading.Thread] = [] + for lProcessItemTuple in self.mProcessList: + lProcessItem = Orchestrator.Managers.ProcessGet(*lProcessItemTuple) + lProcessItem.StatusSave() + lThread = threading.Thread(target=lProcessItem.StopSafe) + lThread.start() + lThreadList.append(lThread) + # Wait for all process will be safe stopped + lAllThreadStoppedBool = False + while not lAllThreadStoppedBool: + lAllThreadStoppedBool = True + for lThread in lThreadList: + if lThread.is_alive() == True: + lAllThreadStoppedBool = False + break + time.sleep(lIntervalScheckSecFloat) + + def ProcessListRestore(self): + """ + Restore the process state for the all processes + """ + for lProcessItem in self.mProcessList: + lProcessItem.StatusRestore() + + def __OSCMDShell__(self, inCMDStr): + """ + Detect the way of use and send the cmd. Wait for command execution! + + :return: None is not exists + """ + if self.mAgentUserNameStr is not None and self.mAgentHostNameStr is not None: # Check if Agent specified + lActivityItemGUIDStr = __Orchestrator__.AgentOSCMD(inHostNameStr=self.mAgentHostNameStr,inUserStr=self.mAgentUserNameStr,inCMDStr=inCMDStr,inRunAsyncBool=False,inSendOutputToOrchestratorLogsBool=False) + lCMDResultStr = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lActivityItemGUIDStr) + else: + lCMDResultStr = __Orchestrator__.OSCMD(inCMDStr=inCMDStr, inRunAsyncBool=False) + return lCMDResultStr + + def BranchRevGet(self, inBranchNameStr="HEAD"): + """ + Get the specified branch revision. Default return the current branch revision + + .. code-block:: python + lGit.BranchRevGet(inBranchNameStr="dev") # Get revision of the local dev branch + lGit.BranchRevGet(inBranchNameStr="remotes/origin/dev") # Get revision of the remotes dev branch + lGit.BranchRevGet(inBranchNameStr="HEAD") # Get revision of the current HEAD branch + lGit.BranchRevGet() # Equal to the call inBranchNameStr="HEAD" + + :param inBranchNameStr: The branch name where to get revision guid + :return: revision GUID + """ + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git rev-parse {inBranchNameStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def BranchRevIsLast(self, inBranchLocalStr: str, inBranchRemoteStr: str) -> bool: + """Get fetch and check if local branch revision is last (if check with remote) + + :param inBranchLocalStr: _description_ + :type inBranchLocalStr: str + :param inBranchRemoteStr: example: origin/prd + :type inBranchRemoteStr: str + :return: _description_ + :rtype: bool + """ + lIsLastBool = False + self.Fetch() + lLocalBranchRevStr = self.BranchRevGet(inBranchNameStr=inBranchLocalStr) + lRemoteBranchRevStr = self.BranchRevGet(inBranchNameStr=inBranchRemoteStr) + if lLocalBranchRevStr == lRemoteBranchRevStr: + lIsLastBool = True + return lIsLastBool + + + def BranchRevLastGetInterval(self, inBranchLocalStr: str, inBranchRemoteStr: str, inPreviousBranchRestoreBool: bool = True, inIntervalSecFloat: float = 60.0): + """Periodically check if revision is last + + :param inBranchLocalStr: _description_ + :type inBranchLocalStr: str + :param inBranchRemoteStr: example: origin/prd + :type inBranchRemoteStr: str + :param inPreviousBranchRestoreBool: _description_, defaults to True + :type inPreviousBranchRestoreBool: bool, optional + :param inIntervalSecFloat: _description_, defaults to 60.0 + :type inIntervalSecFloat: float, optional + """ + #self.BranchRevLastGet(inBranchLocalStr, inBranchRemoteStr, inPreviousBranchRestoreBool) + Orchestrator.OrchestratorScheduleGet().every(inIntervalSecFloat).seconds.do(self.BranchRevLastGet, inBranchLocalStr, inBranchRemoteStr, inPreviousBranchRestoreBool) + + def BranchRevLastGet(self, inBranchLocalStr: str, inBranchRemoteStr: str, inPreviousBranchRestoreBool: bool = True): + """Do some action to get the last revision + + :param inBranchLocalStr: [description] + :type inBranchLocalStr: str + :param inBranchRemoteStr: [description] + :type inBranchRemoteStr: str + """ + Orchestrator.OrchestratorLoggerGet().debug(f"Managers.Git ({self.mAbsPathStr}): self.BranchRevLastGet has been init") + # check if the correct revision + lCMDResultStr = None + if self.BranchRevIsLast(inBranchLocalStr=inBranchLocalStr, inBranchRemoteStr=inBranchRemoteStr) == False: + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Git ({self.mAbsPathStr}): self.BranchRevLastGet, new rev (branch: {inBranchLocalStr}) has been detected - merge (branch: {inBranchRemoteStr})") + # Do the stop safe for the connected process + self.ProcessListSaveStopSafe() + lBranchNameCurrentStr = self.BranchNameGet() + # reset all changes in local folder + self.Clear() + # checkout + self.BranchCheckout(inBranchNameStr=inBranchLocalStr) + # merge + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git merge {inBranchRemoteStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + if inPreviousBranchRestoreBool == True: + # checkout to the source branch which was + self.BranchCheckout(inBranchNameStr=lBranchNameCurrentStr) + # do the orc restart + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Git ({self.mAbsPathStr}): self.BranchRevLastGet, merge done, restart orc") + Orchestrator.OrchestratorRestart() + return lCMDResultStr + + def BranchNameGet(self) -> str: + """Get the current local branch name + + :return: current local branch name + """ + #"git rev-parse --abbrev-ref HEAD" + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git rev-parse --abbrev-ref HEAD" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def BranchCheckout(self, inBranchNameStr): + self.Clear() + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git checkout {inBranchNameStr}" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def Clear(self): + """Clear the all changes in the local folder. Get up to the current revision + """ + # f"git clean -f -d" # Очистить от лишних файлов + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git clean -f -d" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + # f"git reset --hard" # Откатить файлы, которые отслеживаются Git и которые были изменены + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git reset --hard" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + + def Fetch(self): + """ + Get updates from the git server. + + .. code-block:: python + lGit.Fetch() # get updates from the server + + :return: None + """ + lCMDStr = f"cd \"{self.mAbsPathUpperStr}\" && git fetch" + lCMDResultStr = self.__OSCMDShell__(inCMDStr=lCMDStr) + return lCMDResultStr + +def GitExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> bool: + """ + Check if the Git instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: True - process exists in gsettings; False - else + """ + return (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inGitPathStr.upper()) in __Orchestrator__.GSettingsGet()["ManagersGitDict"] + + +def GitGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> Git: + """ + Return the Git instance by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inGitPathStr: Relative (from the orchestrator working directory) or absolute. If "" - work with Orc repo + :return: Git instance (if exists) Else None + """ + lAbsPathUpperStr = os.path.abspath(inGitPathStr).upper() + return __Orchestrator__.GSettingsGet()["ManagersGitDict"].get((inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), lAbsPathUpperStr),None) + + +def GitBranchRevGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str, inBranchNameStr: str="HEAD") -> str: + lGit = GitGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inGitPathStr=inGitPathStr) + if lGit is not None: return lGit.BranchRevGet(inBranchNameStr=inBranchNameStr) + +def GitFetch(inAgentHostNameStr: str, inAgentUserNameStr: str, inGitPathStr: str) -> None: + lGit = GitGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inGitPathStr=inGitPathStr) + if lGit is not None: lGit.Fetch() \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py new file mode 100644 index 00000000..276e4adc --- /dev/null +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/Process.py @@ -0,0 +1,692 @@ +#from pyOpenRPA.Orchestrator import Managers +from .. import __Orchestrator__ +import os +import time + +from pyOpenRPA import Orchestrator +class Process(): + """ + Manager process, which is need to be started / stopped / restarted + + With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not. + + All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . + After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet + + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + + .. code-block:: python + # For the safe init class use ProcessInitSafe + lProcess = Orchestrator.Managers.ProcessInitSafe(inAgentHostNameStr="PCNAME",inAgentUserNameStr="USER", + inProcessNameWOExeStr="notepad",inStartCMDStr="notepad",inStopSafeTimeoutSecFloat=3) + # Async way to run job + lProcess.ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=5) + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(Orchestrator.OrchestratorThreadStart, + lProcess.StartCheck) + # OR (sync mode) + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(lProcess.StartCheck) + + How to use StopSafe on the robot side + + .. code-block:: python + from pyOpenRPA.Tools import StopSafe + StopSafe.Init(inLogger=None) + StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe + """ + + mAgentHostNameStr = None + mAgentUserNameStr = None + mStartPathStr = None + mStartCMDStr = None + mStartArgDict = None + mStatusCheckIntervalSecFloat = None + mProcessNameWOExeStr = None + mStopSafeTimeoutSecFloat = None + mStatusStr = None # 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + # MST - Manual Stop Trigger + mMSTdTSecFloat: float = None + mMSTdNInt = None + mMSTStartTimeList = [] + + mAgentMuteBool = False # Mute any sends to agent while some action is perfomed + + mStatusSavedStr = None # Saved status to the further restore + + def MuteWait(self): + """ + Internal def. Wait when class is apply to send new activities to the agent + + :return: + """ + lIntervalSecFloat = 0.3 + while self.mAgentMuteBool == True: + time.sleep(lIntervalSecFloat) + return None + + def KeyTurpleGet(self): + """ + Get the key turple of the current process + + """ + return (self.mAgentHostNameStr.upper(), self.mAgentUserNameStr.upper(), self.mProcessNameWOExeStr.upper()) + + + def __init__(self, inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30): + """ + Init the class instance. + !ATTENTION! Function can raise exception if process with the same (inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr) is already exists in GSettings (can be restored from previous Orchestrator session). See ProcessInitSafe to sefaty init the instance or restore previous + !ATTENTION! Schedule options you must + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inStartPathStr: Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + :param inStartCMDStr: CMD script to start program (if no start file is exists) + :param inStopSafeTimeoutSecFloat: Time to wait for stop safe. After that do the stop force (if process is not stopped) + """ + lGS = __Orchestrator__.GSettingsGet() + # Check if Process is not exists in GSettings + if (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()) not in lGS["ManagersProcessDict"]: + self.mStartArgDict = inStartArgDict + self.mAgentHostNameStr = inAgentHostNameStr + self.mAgentUserNameStr = inAgentUserNameStr + self.mStartPathStr = inStartPathStr + self.mStartCMDStr = inStartCMDStr + self.mProcessNameWOExeStr = inProcessNameWOExeStr + self.mStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat + lGS["ManagersProcessDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper())]=self + lActivityDict = __Orchestrator__.ProcessorActivityItemCreate(inDef=self.StatusCheck,inArgList=[], inThreadBool=True) + __Orchestrator__.ProcessorActivityItemAppend(inActivityItemDict=lActivityDict) + if inStatusCheckIntervalSecFloat is not None: __Orchestrator__.OrchestratorScheduleGet().every(inStatusCheckIntervalSecFloat).seconds.do(Orchestrator.OrchestratorThreadStart,self.StatusCheck) + self.mStatusCheckIntervalSecFloat = inStatusCheckIntervalSecFloat + else: raise Exception(f"Managers.Process ({inAgentHostNameStr}, {inAgentUserNameStr}, {inProcessNameWOExeStr}): Can't init the Process instance because it already inited in early (see ProcessInitSafe)") + + def ManualStopTriggerSet(self, inMSTdTSecFloat: float, inMSTdNInt: int) -> None: + """ + Set ManualStopTrigger (MST) to switch to STOPPED MANUAL if specified count of start fails will be catched in specified time period + + :param inMSTdTSecFloat: Time perios in seconds + :param inMSTdNInt: Counts of the start tries + :return: None + """ + + # MST - Manual Stop Trigger + self.mMSTdTSecFloat = inMSTdTSecFloat + self.mMSTdNInt = inMSTdNInt + + + def ManualStopTriggerNewStart(self): + """ + Log new start event. Check if it is applicable. Change status if ManualStop trigger criteria is applied + + :return: # 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if self.mMSTdTSecFloat is not None and self.mMSTdNInt is not None: + lTimeNowSecFloat = time.time() + self.mMSTStartTimeList.append(lTimeNowSecFloat) # Append current time to MST list + # Remove old items from list + lMSTStartTimeList = [] + for lTimeItemSecFloat in self.mMSTStartTimeList: + ldTSecFloat = lTimeNowSecFloat - lTimeItemSecFloat + # Move to the new list if dT less + if ldTSecFloat < self.mMSTdTSecFloat: lMSTStartTimeList.append(lTimeItemSecFloat) + self.mMSTStartTimeList = lMSTStartTimeList # Set new list + # Check count in list + if len(lMSTStartTimeList) > self.mMSTdNInt: + self.mStatusStr = "1_STOPPED_MANUAL" + # Log info about process + lL = __Orchestrator__.OrchestratorLoggerGet() + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): ManualStopTrigger is activated. {self.mMSTdNInt} start tries in {self.mMSTdTSecFloat} sec.") + return self.mStatusStr + + def ManualStopListClear(self) -> None: + """ + Clear the last start tries list. + + :return: None + """ + self.mMSTStartTimeList=[] + + def Manual2Auto(self) -> str: + """ + Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lLogBool = False + if self.mStatusStr=="1_STOPPED_MANUAL": self.mStatusStr = "0_STOPPED"; lLogBool=True + if self.mStatusStr=="3_STOP_SAFE_MANUAL": self.mStatusStr = "2_STOP_SAFE"; lLogBool=True + if self.mStatusStr=="5_STARTED_MANUAL": self.mStatusStr = "4_STARTED"; lLogBool=True + # Log info about process + if lLogBool == True: self.StatusChangeLog() + return self.mStatusStr + + def Start(self, inIsManualBool = True, inStartArgDict=None) -> str: + """ + Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. + Will not start if STOP SAFE is now and don't start auto is stopped manual now + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if inIsManualBool == False: self.ManualStopTriggerNewStart() # Set the time + if self.mStatusStr is not None and (self.mStatusStr == "1_STOPPED_MANUAL" or "STOP_SAFE" in self.mStatusStr) and inIsManualBool == False: + lStr = f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Process will not start because of stopped manual or stop safe is now." + __Orchestrator__.OrchestratorLoggerGet().warning(lStr) + return self.mStatusStr + # Send activity item to agent - wait result + if self.mStartPathStr is not None: lCMDStr = os.path.abspath(self.mStartPathStr) + elif self.mStartCMDStr is not None: lCMDStr = self.mStartCMDStr + # Append args + if inStartArgDict is not None: self.mStartArgDict = inStartArgDict + if self.mStartArgDict is not None: + for lItemKeyStr in self.mStartArgDict: + lItemValueStr = self.mStartArgDict[lItemKeyStr] + lCMDStr = f"{lCMDStr} {lItemKeyStr} {lItemValueStr}" + #import pdb + #pdb.set_trace() + self.MuteWait() + self.mAgentMuteBool=True + lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate(inDef="OSCMD", + inArgDict={"inCMDStr":lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool":False}, + inArgGSettingsStr="inGSettings") + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, + inUserStr=self.mAgentUserNameStr, + inActivityItemDict=lActivityItemStart) + lStartResult = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if inIsManualBool==True: + self.mStatusStr = "5_STARTED_MANUAL" + else: + self.mStatusStr = "4_STARTED" + # Log info about process + self.StatusChangeLog() + self.mAgentMuteBool = False + return self.mStatusStr + + def StartCheck(self) -> str: + """ + Start program if auto stopped (0_STOPPED). + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.MuteWait() + if self.mStatusStr == "0_STOPPED": + self.Start(inIsManualBool=False) + return self.mStatusStr + + def StopSafe(self, inIsManualBool = True, inStopSafeTimeoutSecFloat = None) -> str: + """ + Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if inStopSafeTimeoutSecFloat is None: inStopSafeTimeoutSecFloat = self.mStopSafeTimeoutSecFloat + self.MuteWait() + self.mAgentMuteBool=True + # Send activity item to agent - wait result + lCMDStr = f'taskkill /im "{self.mProcessNameWOExeStr}.exe" /fi "username eq %USERNAME%"' + lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate( + inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool": False},inArgGSettingsStr="inGSettings") + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, + inUserStr=self.mAgentUserNameStr, + inActivityItemDict=lActivityItemStart) + lStartResult = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if inIsManualBool==True: + self.mStatusStr = "3_STOP_SAFE_MANUAL" + else: + self.mStatusStr = "2_STOP_SAFE" + # Log info about process + self.StatusChangeLog() + # Interval check is stopped + lTimeStartFloat = time.time() + lIntervalCheckSafeStatusFLoat = 15.0 + while "SAFE" in self.mStatusStr and (time.time() - lTimeStartFloat) < inStopSafeTimeoutSecFloat: + self.StatusCheck() + if "SAFE" not in self.mStatusStr: break + time.sleep(lIntervalCheckSafeStatusFLoat) + if "SAFE" in self.mStatusStr: + # Log info about process + lL = __Orchestrator__.OrchestratorLoggerGet() + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe stop has been wait for {inStopSafeTimeoutSecFloat} sec. Now do the force stop.") + self.StopForce(inIsManualBool=inIsManualBool,inMuteIgnoreBool=True) + # Log info about process + # self.StatusChangeLog() status check has already log status (see above) + self.mAgentMuteBool = False + return self.mStatusStr + + def StopSafeCheck(self, inStopSafeTimeoutSecFloat = None) -> str: + """ + Stop safe program if auto started (4_STARTED). + + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.MuteWait() + if self.mStatusStr == "4_STARTED": + self.StopSafe(inIsManualBool=False, inStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat) + return self.mStatusStr + + def StopForce(self, inIsManualBool = True, inMuteIgnoreBool = False) -> str: + """ + Manual/Auto stop force. Force stop don't wait process termination - it just terminate process now. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + if inMuteIgnoreBool == False: self.MuteWait() + lMuteWorkBool = False + if self.mAgentMuteBool==False: self.mAgentMuteBool=True; lMuteWorkBool=True + # Send activity item to agent - wait result + lCMDStr = f'taskkill /F /im "{self.mProcessNameWOExeStr}.exe" /fi "username eq %USERNAME%"' + lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate( + inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool": False},inArgGSettingsStr="inGSettings") + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, + inUserStr=self.mAgentUserNameStr, + inActivityItemDict=lActivityItemStart) + lStartResult = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if inIsManualBool==True: + self.mStatusStr = "1_STOPPED_MANUAL" + else: + self.mStatusStr = "0_STOPPED" + # Log info about process + self.StatusChangeLog() + if lMuteWorkBool == True: + self.mAgentMuteBool=False + return self.mStatusStr + + def StopForceCheck(self) -> str: + """ + Stop force program if auto started (4_STARTED). + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.MuteWait() + if self.mStatusStr == "4_STARTED": + self.StopForce(inIsManualBool=False) + return self.mStatusStr + + def RestartSafe(self, inIsManualBool = True): + """ + Manual/Auto restart safe. Restart safe is the operation which send signal to process to terminate own work (send term signal to process). Then it run process. Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StopSafe(inIsManualBool=inIsManualBool) + return self.Start(inIsManualBool=inIsManualBool) + + def RestartForce(self, inIsManualBool = True): + """ + Manual/Auto restart force. Force restart dont wait process termination - it just terminate process now ant then start it. + Manual restart will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StopForce(inIsManualBool=inIsManualBool) + return self.Start(inIsManualBool=inIsManualBool) + + def StatusSave(self): + """ + Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don't save "STOP_SAFE" status > "STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lWarnSafeBool = True + if self.mStatusStr == "2_STOP_SAFE": self.mStatusSavedStr = "0_STOPPED" + elif self.mStatusStr == "3_STOP_SAFE_MANUAL": self.mStatusSavedStr = "1_STOPPED_MANUAL" + else: self.mStatusSavedStr = self.mStatusStr; lWarnSafeBool = False + if lWarnSafeBool==True: __Orchestrator__.OrchestratorLoggerGet().warning(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe status has been catched when safe > change saved status to stopped.") + return self.mStatusStr + + + def StatusCheckIntervalRestore(self): + """Call from orchestrator when init + """ + if self.mStatusCheckIntervalSecFloat is not None: + __Orchestrator__.OrchestratorLoggerGet().info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Restore schedule to StatusCheck in interval of {self.mStatusCheckIntervalSecFloat} sec.") + __Orchestrator__.OrchestratorScheduleGet().every(self.mStatusCheckIntervalSecFloat).seconds.do(Orchestrator.OrchestratorThreadStart,self.StatusCheck) + + def StatusRestore(self): + """ + Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StatusCheck() # check current status + # Do some action + if self.mStatusSavedStr != self.mStatusStr and self.mStatusSavedStr is not None: + #lManualBool = False + #if "MANUAL" in self.mStatusSavedStr: + # lManualBool = True + if "STOPPED" in self.mStatusSavedStr and "STOPPED" not in self.mStatusStr: + self.StopSafe(inIsManualBool=True) + if "STARTED" in self.mStatusSavedStr and "STARTED" not in self.mStatusStr: + self.Start(inIsManualBool=True) + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Status has been restored to {self.mStatusSavedStr}") + self.mStatusStr = self.mStatusSavedStr + self.mStatusSavedStr = None + return self.mStatusStr + + def StatusChangeLog(self): + """ + Lof information about status change + + :return: + """ + # Log info about process + lL = __Orchestrator__.OrchestratorLoggerGet() + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Status has been changed to {self.mStatusStr})") + + + def StatusCheck(self): + """ + Check if process is alive. The def will save the manual flag is exists. Don't wait mute but set mute if it is not set. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + # Send activity item to agent - wait result + lLogBool = False + lActivityItemUserProcessList = __Orchestrator__.ProcessorActivityItemCreate(inDef="ProcessWOExeUpperUserListGet") + #self.MuteWait() + self.mAgentMuteBool=True + lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr,inUserStr=self.mAgentUserNameStr,inActivityItemDict=lActivityItemUserProcessList) + lUserProcessList = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if self.mProcessNameWOExeStr.upper() in lUserProcessList: + if self.mStatusStr == "1_STOPPED_MANUAL": self.mStatusStr = "5_STARTED_MANUAL"; lLogBool=True + if self.mStatusStr == "0_STOPPED": self.mStatusStr = "4_STARTED"; lLogBool=True + if self.mStatusStr is None: self.mStatusStr = "4_STARTED"; lLogBool=True + else: + if self.mStatusStr == "2_STOP_SAFE": self.mStatusStr = "0_STOPPED"; lLogBool = True + if self.mStatusStr == "3_STOP_SAFE_MANUAL": self.mStatusStr = "1_STOPPED_MANUAL"; lLogBool = True + if self.mStatusStr == "5_STARTED_MANUAL": self.mStatusStr = "1_STOPPED_MANUAL"; lLogBool=True + if self.mStatusStr == "4_STARTED": self.mStatusStr = "0_STOPPED"; lLogBool=True + if self.mStatusStr is None: self.mStatusStr = "0_STOPPED"; lLogBool=True + # Log info about process + if lLogBool == True: self.StatusChangeLog() + self.mAgentMuteBool = False + return self.mStatusStr + def StatusCheckStart(self): + """ + Check process status and run it if auto stopped self.mStatusStr is "0_STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lStatusStr = self.StatusCheck() + if lStatusStr == "0_STOPPED": + self.Start(inIsManualBool=False) + return self.mStatusStr + def StatusCheckStopForce(self): + """ + Check process status and auto stop force it if self.mStatusStr is 4_STARTED + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lStatusStr = self.StatusCheck() + if lStatusStr == "4_STARTED": + self.StopForce(inIsManualBool=False) + return self.mStatusStr + + def StatusCheckStopSafe(self): + """ + Check process status and auto stop safe it if self.mStatusStr is 4_STARTED + + :return: + """ + lStatusStr = self.StatusCheck() + if lStatusStr == "4_STARTED": + self.StopSafe(inIsManualBool=False) + return self.mStatusStr + + +def ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=300) -> Process: + """ + Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) + Return existing instance (if exists) or create new instance and return it. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inStartPathStr: Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + :param inStartCMDStr: CMD script to start program (if no start file is exists) + :param inStopSafeTimeoutSecFloat: Time to wait for stop safe. After that do the stop force (if process is not stopped) + :return: Process instance + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess + else: return Process(inAgentHostNameStr=inAgentHostNameStr,inAgentUserNameStr=inAgentUserNameStr,inProcessNameWOExeStr=inProcessNameWOExeStr, + inStartPathStr=inStartPathStr,inStartCMDStr=inStartCMDStr,inStopSafeTimeoutSecFloat=inStopSafeTimeoutSecFloat) + +def ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> bool: + """ + Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: True - process exists in gsettings; False - else + """ + return (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()) in __Orchestrator__.GSettingsGet()["ManagersProcessDict"] + + +def ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> Process: + """ + Return the process instance by the inProcessNameWOExeStr + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process instance (if exists) Else None + """ + return __Orchestrator__.GSettingsGet()["ManagersProcessDict"].get((inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()),None) + +def ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: + """ + Get the status of the Process instance. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.mStatusStr + +def ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) -> str: + """ + Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.Start(inIsManualBool=inIsManualBool) + + +def ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat = None) -> str: + """ + Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.StopSafe(inIsManualBool=inIsManualBool) + +def ProcessStopForce(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) -> str: + """ + Manual/Auto stop force. Force stop dont wait process termination - it just terminate process now. + Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.StopForce(inIsManualBool=inIsManualBool) + +def ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str): + """ + Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don't save "STOP_SAFE" status > "STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusSave() + return lProcess.mStatusStr + +def ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str): + """ + Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusRestore() + return lProcess.mStatusStr + +def ProcessStatusCheck(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: + """ + Check if process is alive. The def will save the manual flag is exists. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusCheck() + return lProcess.mStatusStr + +def ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: + """ + Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: Process status. See self.mStatusStr. + Process instance has the following statuses: + - 0_STOPPED + - 1_STOPPED_MANUAL + - 2_STOP_SAFE + - 3_STOP_SAFE_MANUAL + - 4_STARTED + - 5_STARTED_MANUAL + - None (if Process instance not exists) + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess.Manual2Auto() + +def ProcessManualStopTriggerSet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inMSTdTSecFloat: float, inMSTdNInt: int) -> None: + """ + Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inMSTdTSecFloat: Time periods in seconds + :param inMSTdNInt: Counts of the start tries + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: lProcess.ManualStopTriggerSet(inMSTdTSecFloat = inMSTdTSecFloat, inMSTdNInt = inMSTdNInt) + + +def ProcessManualStopListClear(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> None: + """ + Clear the last start tries list. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: lProcess.ManualStopListClear() + +def ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str,inIntervalSecondsInt: int = 120): + """ + Run status check every interval in second you specify. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIntervalSecondsInt: Interval in seconds. Default is 120 + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + # Check job in threaded way + __Orchestrator__.OrchestratorScheduleGet().every(inIntervalSecondsInt).seconds.do(__Orchestrator__.OrchestratorThreadStart,lProcess.StatusCheck) \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py new file mode 100644 index 00000000..f705852f --- /dev/null +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Managers/__init__.py @@ -0,0 +1,3 @@ +from .ControlPanel import * +from .Process import * +from .Git import * \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py index 0f0b61e4..62e07788 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Processor.py @@ -27,14 +27,42 @@ def ProcessorRunSync(inGSettings, inRobotRDPThreadControlDict): if len(lActivityList)>0: if lL: lL.debug(f'Processor ActivityList len: {len(lActivityList)}') lActivityItem = inGSettings["ProcessorDict"]["ActivityList"].pop(0) # Extract the first item from processor queue - inRobotRDPThreadControlDict["ThreadExecuteBool"]=False # Stop the RobotRDPActive monitoring - ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item - inRobotRDPThreadControlDict["ThreadExecuteBool"] = True # Continue the RobotRDPActive monitoring + if lActivityItem.get("ThreadBool", False) is False: + inRobotRDPThreadControlDict["ThreadExecuteBool"]=False # Stop the RobotRDPActive monitoring + inGSettings["ProcessorDict"]["ActivityItemNowDict"]=lActivityItem + ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item + inGSettings["ProcessorDict"]["ActivityItemNowDict"]=None + inRobotRDPThreadControlDict["ThreadExecuteBool"] = True # Continue the RobotRDPActive monitoring + else: + ProcessorRunAsync(inGSettings = inGSettings, inActivityList=[lActivityItem]) else: time.sleep(inGSettings["ProcessorDict"]["CheckIntervalSecFloat"]) # Sleep when list is empty except Exception as e: if lL: lL.exception(f"Processor.ProcessorRunSync. Something goes very wrong in processor queue. See traceback") +# Run processor Async +def ProcessorRunAsync(inGSettings, inActivityList): + """ + "inActivityList": [ # List of the activities + # { + # "Def":"DefAliasTest", # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + # "ArgList":[1,2,3], # Args list + # "ArgDict":{"ttt":1,"222":2,"dsd":3}, # Args dictionary + # "ArgGSettings": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "ArgLogger": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + # "GUIDStr": "sadasd-asdas-d-asdasd", # ActivityItem GUID which identify the Activity + # "ThreadBool": True + # }, + """ + def __process__(inGSettings, inActivityList): + for lActivityItem in inActivityList: + lL = inGSettings["Logger"] # Logger alias + if lL: lL.debug(f'ActivityItem in new thread') + lResultList = ActivityListExecute(inGSettings = inGSettings, inActivityList = [lActivityItem]) # execute the activity item + # Start in new thread + lThread = threading.Thread(target=__process__,kwargs={"inGSettings": inGSettings, "inActivityList": inActivityList}) + lThread.start() + # Execute ActivityItem list # return the def result def ActivityListExecute(inGSettings, inActivityList): @@ -113,8 +141,8 @@ def ProcessorMonitorRunSync(inGSettings): lActiveTimeStart = time.time() try: while True: - if len(inGSettings["ProcessorDict"]["ActivityList"])>0: - lItemDict = inGSettings["ProcessorDict"]["ActivityList"][0] + if inGSettings["ProcessorDict"]["ActivityItemNowDict"] is not None: + lItemDict = inGSettings["ProcessorDict"]["ActivityItemNowDict"] if "GUIDStr" not in lItemDict: lGUIDStr = str(uuid.uuid4()) # generate new GUID lItemDict["GUIDStr"] = lGUIDStr diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py index 1142344d..481a111c 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Connector.py @@ -79,13 +79,15 @@ def SessionConfigurationCreate(inConfiguration): lDriveStoreDirectStr = "" for lItem in inConfiguration['SharedDriveList']: lDriveStoreDirectStr+=f"{lItem.upper()}:\\;" # Attention - all drives must be only in upper case!!! - #Replace {Width}, {Height}, {BitDepth}, {HostPort}, {Login} + #Replace {Width}, {Height}, {BitDepth}, {HostPort}, {Login} {redirectclipboard} + lRedirectClipboardStr = "1" if inConfiguration.get('RedirectClipboardBool',True) == True else "0" lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{Width}", str(inConfiguration.get('Screen',{}).get("Width",1680))) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{Height}", str(inConfiguration.get('Screen',{}).get("Height",1050))) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{BitDepth}", inConfiguration.get('Screen',{}).get("DepthBit","32")) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{HostPort}", lHostPort) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{Login}", inConfiguration['Login']) lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{SharedDriveList}", lDriveStoreDirectStr) + lRDPTemplateFileContent = lRDPTemplateFileContent.replace("{redirectclipboard}", lRedirectClipboardStr) #Save template to temp file lRDPCurrentFileFullPath = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4().hex}.rdp") open(lRDPCurrentFileFullPath, "w", encoding="utf-16-le").write(lRDPTemplateFileContent) diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py index 8ec9a493..bdbb9efe 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Processor.py @@ -10,7 +10,7 @@ import psutil gSettings = None # Gsettings will be initialized after the import module # Create new RDPSession in RobotRDPActive -def RDPSessionConnect(inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPasswordStr): +def RDPSessionConnect(inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPasswordStr, inRedirectClipboardBool = True): global gSettings # ATTENTION - dont connect if RDP session is exist if inRDPSessionKeyStr not in gSettings["RobotRDPActive"]["RDPList"]: @@ -27,6 +27,7 @@ def RDPSessionConnect(inRDPSessionKeyStr, inHostStr, inPortStr, inLoginStr, inPa "DepthBit": "32" # "32" or "24" or "16" or "15", example "32" }, "SharedDriveList": ["c"], # List of the Root sesion hard drives, example ["c"] + "RedirectClipboardBool": inRedirectClipboardBool, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, # Flag if the RDP window is exist, old name "FlagSessionIsActive". Check every n seconds , example False diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotRDPActive/Template.rdp index aeeb6d1ba600945c54a0e897f3bd4a5d3bae368d..0e7ae9af992b186268760ff50b0a2119a33b1068 100644 GIT binary patch delta 24 gcmew*G*5U#5i4W$WKULQ#@fkyS;RMYv3_6%0Buk delta 16 YcmbOy{7Yy<5$og^EMlA2uzq3&06Z%O(*OVf diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py index 50683680..22c4ab14 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/RobotScreenActive/Monitor.py @@ -8,7 +8,7 @@ def CheckScreen(inIntervalSeconds=1): #Send os command to create console version (base screen) Screen.ConsoleScreenBase() #Delay to create console screen - time.sleep(2) + time.sleep(5) #Delay time.sleep(inIntervalSeconds) return None \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py index 76cfea8a..3560f861 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Server.py @@ -11,6 +11,7 @@ from socketserver import ThreadingMixIn import threading import json from threading import Thread +import inspect from . import Processor # Add new processor from . import ProcessorOld # Support old processor - deprecated defs only for backward compatibility import urllib.parse # decode URL in string @@ -45,6 +46,13 @@ def __ComplexDictMerge2to1__(in1Dict, in2Dict): # Tool to merge complex dictionaries - no exceptions, just overwrite dict 2 in dict 1 def __ComplexDictMerge2to1Overwrite__(in1Dict, in2Dict): + """ + Merge in2Dict in in1Dict. In conflict override and get value from dict 2 + + :param in1Dict: Source dict. Save the link (structure) + :param in2Dict: New data dict + :return: Merged dict 1 + """ lPathList=None if lPathList is None: lPathList = [] for lKeyStr in in2Dict: @@ -253,7 +261,7 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): #Tech def #return {"headers":[],"body":"","statuscode":111} - def URLItemCheckDo(self, inURLItem, inMethod): + def URLItemCheckDo(self, inURLItem, inMethod, inOnlyFlagUACBool = False): ############################### #Tech sub def - do item ################################ @@ -273,7 +281,14 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): lFileObject.close() #If function is set if "ResponseDefRequestGlobal" in inURLItem: - inURLItem["ResponseDefRequestGlobal"](inRequest, inGlobalDict) + lDef = inURLItem["ResponseDefRequestGlobal"] + lDefSignature = inspect.signature(lDef) + if len(lDefSignature.parameters) == 2: + inURLItem["ResponseDefRequestGlobal"](inRequest, inGlobalDict) + elif len(lDefSignature.parameters) == 1: + inURLItem["ResponseDefRequestGlobal"](inRequest) + else: + inURLItem["ResponseDefRequestGlobal"]() if "ResponseFolderPath" in inURLItem: #lRequestPath = inRequest.path lRequestPath = urllib.parse.unquote(inRequest.path) @@ -290,6 +305,9 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): # Закрыть файловый объект lFileObject.close() ############################################## + # UAC Check + if inOnlyFlagUACBool == True and inURLItem.get("UACBool",None) in [None, True]: + return False if inURLItem["Method"].upper() == inMethod.upper(): # check Match type variant: BeginWith if inURLItem["MatchType"].upper() == "BEGINWITH": @@ -348,10 +366,12 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): self.end_headers() # Write content as utf-8 data self.wfile.write(inResponseDict["Body"]) - except ConnectionResetError as e: - if lL: lL.warning(f"An existing connection was forcibly closed by the remote host - OK for the network interactions (ConnectionResetError: [WinError 10054])") + except (ConnectionResetError, ConnectionAbortedError) as e: + if lL: lL.warning(f"SERVER: Connection was forcibly closed by the client side - OK for the network interactions (ConnectionResetError: [WinError 10054] or ConnectionAbortedError: [WinError 10053])") + def do_GET(self): try: + threading.current_thread().request = self self.OpenRPA = {} self.OpenRPA["AuthToken"] = None self.OpenRPA["Domain"] = None @@ -361,6 +381,16 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): # Prepare result dict lResponseDict = {"Headers": {}, "SetCookies": {}, "Body": b"", "StatusCode": None} self.OpenRPAResponseDict = lResponseDict + ############################ + #First - all with Flag UACBool + ############################ + for lURLItem in gSettingsDict["ServerDict"]["URLList"]: + #Check if all condition are applied + lFlagURLIsApplied=False + lFlagURLIsApplied=self.URLItemCheckDo(inURLItem=lURLItem, inMethod="GET", inOnlyFlagUACBool=True) + if lFlagURLIsApplied: + self.ResponseDictSend() + return ##################################### #Do authentication #Check if authentication is turned on @@ -425,6 +455,7 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): # POST def do_POST(self): try: + threading.current_thread().request = self lL = gSettingsDict["Logger"] self.OpenRPA = {} self.OpenRPA["AuthToken"] = None @@ -436,6 +467,16 @@ class testHTTPServer_RequestHandler(BaseHTTPRequestHandler): #pdb.set_trace() lResponseDict = {"Headers": {}, "SetCookies":{}, "Body": b"", "StatusCode": None} self.OpenRPAResponseDict = lResponseDict + ############################ + #First - all with Flag UACBool + ############################ + for lURLItem in gSettingsDict["ServerDict"]["URLList"]: + #Check if all condition are applied + lFlagURLIsApplied=False + lFlagURLIsApplied=self.URLItemCheckDo(inURLItem=lURLItem, inMethod="POST", inOnlyFlagUACBool=True) + if lFlagURLIsApplied: + self.ResponseDictSend() + return ##################################### #Do authentication #Check if authentication is turned on diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py index ed09b85f..be8011eb 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/ServerSettings.py @@ -1,6 +1,5 @@ import json, os import copy -from inspect import signature # For detect count of def args from . import __Orchestrator__ #ControlPanelDict from desktopmagic.screengrab_win32 import ( @@ -15,6 +14,7 @@ from .Web import Basic from . import BackwardCompatibility # Support old up to 1.2.0 defs from . import Processor from . import SettingsTemplate + # # # # # # # # # # # # # v 1.2.0 Functionallity # # # # # # # # # # # # @@ -24,31 +24,14 @@ def HiddenJSInitGenerate(inRequest, inGSettings): lUACCPTemplateKeyList=["pyOpenRPADict","CPKeyDict"] lL = inGSettings["Logger"] # Alias for logger lJSInitResultStr = "" - lRenderFunctionsRobotDict = inGSettings["CPDict"] + lRenderFunctionsRobotDict = inGSettings["ServerDict"]["ControlPanelDict"] for lItemKeyStr in lRenderFunctionsRobotDict: lItemDict = lRenderFunctionsRobotDict[lItemKeyStr] - lJSInitGeneratorDef = lItemDict.get("JSInitGeneratorDef",None) lUACBool = dUAC(inRoleKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC) if lItemKeyStr=="VersionCheck": lUACBool=True # For backward compatibility for the old fron version which not reload page when new orch version is comming if lUACBool: # Run function if UAC is TRUE # JSONGeneratorDef - if lJSInitGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) - lJSResult = None - lDEFSignature = signature(lJSInitGeneratorDef) # Get signature of the def - lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args - try: - if lDEFARGLen == 1: # def (inGSettings) - lJSResult = lJSInitGeneratorDef(inGSettings) - elif lDEFARGLen == 2: # def (inRequest, inGSettings) - lJSResult = lJSInitGeneratorDef(inRequest, inGSettings) - elif lDEFARGLen == 0: # def () - lJSResult = lJSInitGeneratorDef() - if type(lJSResult) is str: - lJSInitResultStr += "; "+lJSResult # Add delimiter to some cases - else: - if lL: lL.warning(f"JSInitGenerator return bad type: {str(type(lJSResult))}, CP Key {lItemKeyStr}") - except Exception as e: - if lL: lL.exception(f"Error in control panel JSInitGeneratorDef. CP Key {lItemKeyStr}. Exception are below") + lJSInitResultStr = lJSInitResultStr + ";" + lItemDict.OnInitJSStr(inRequest=inRequest) return lJSInitResultStr # Generate CP HTML + JSON @@ -59,59 +42,20 @@ def HiddenCPDictGenerate(inRequest, inGSettings): lL = inGSettings["Logger"] # Alias for logger # Create result JSON lCPDict = {} - lRenderFunctionsRobotDict = inGSettings["CPDict"] + lRenderFunctionsRobotDict = inGSettings["ServerDict"]["ControlPanelDict"] for lItemKeyStr in lRenderFunctionsRobotDict: lItemDict = lRenderFunctionsRobotDict[lItemKeyStr] - lItemHTMLRenderDef = lItemDict.get("HTMLRenderDef",None) - lItemJSONGeneratorDef = lItemDict.get("JSONGeneratorDef",None) lUACBool = dUAC(inRoleKeyList=lUACCPTemplateKeyList+[lItemKeyStr]) # Check if render function is applicable User Access Rights (UAC) if lItemKeyStr=="VersionCheck": lUACBool=True # For backward compatibility for the old fron version which not reload page when new orch version is comming if lUACBool: # Run function if UAC is TRUE lCPItemDict = {"HTMLStr": None, "JSONDict":None} - # HTMLRenderDef - if lItemHTMLRenderDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) - lHTMLResult = None - lDEFSignature = signature(lItemHTMLRenderDef) # Get signature of the def - lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args - try: - if lDEFARGLen == 1: # def (inGSettings) - lHTMLResult = lItemHTMLRenderDef(inGSettings) - elif lDEFARGLen == 2: # def (inRequest, inGSettings) - lHTMLResult = lItemHTMLRenderDef(inRequest, inGSettings) - elif lDEFARGLen == 0: # def () - lHTMLResult = lItemHTMLRenderDef() - # RunFunction - # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" - if type(lHTMLResult) is str: - lCPItemDict["HTMLStr"] = lHTMLResult - elif "HTMLStr" in lHTMLResult or "JSONDict" in lHTMLResult: - lCPItemDict = lHTMLResult # new version - else: - # Call backward compatibility HTML generator - lCPItemDict["HTMLStr"] = Basic.HTMLControlPanelBC(inCPDict=lHTMLResult) - except Exception as e: - if lL: lL.exception(f"Error in control panel HTMLRenderDef. CP Key {lItemKeyStr}. Exception are below") - # JSONGeneratorDef - if lItemJSONGeneratorDef is not None: # Call def (inRequest, inGSettings) or def (inGSettings) - lJSONResult = None - lDEFSignature = signature(lItemJSONGeneratorDef) # Get signature of the def - lDEFARGLen = len(lDEFSignature.parameters.keys()) # get count of the def args - try: - if lDEFARGLen == 1: # def (inGSettings) - lJSONResult = lItemJSONGeneratorDef(inGSettings) - elif lDEFARGLen == 2: # def (inRequest, inGSettings) - lJSONResult = lItemJSONGeneratorDef(inRequest, inGSettings) - elif lDEFARGLen == 0: # def () - lJSONResult = lItemJSONGeneratorDef() - # RunFunction - # Backward compatibility up to 1.2.0 - call HTML generator if result has no "HTMLStr" - lType = type(lJSONResult) - if lType is str or lJSONResult is None or lType is int or lType is list or lType is dict or lType is bool or lType is float: - lCPItemDict["JSONDict"] = lJSONResult - else: - if lL: lL.warning(f"JSONGenerator return bad type: {str(type(lJSONResult))}, CP Key {lItemKeyStr}") - except Exception as e: - if lL: lL.exception(f"Error in control panel JSONGeneratorDef. CP Key {lItemKeyStr}. Exception are below") + try: + # HTML Render + lCPItemDict["HTMLStr"] = lItemDict.OnRefreshHTMLStr(inRequest=inRequest) + # JSONGeneratorDef + lCPItemDict["JSONDict"] = lItemDict.OnRefreshJSONDict(inRequest=inRequest) + except Exception as e: + lL.exception(f"EXCEPTION WHEN HTML/ JSON RENDER") # Insert CPItemDict in result CPDict lCPDict[lItemKeyStr]=lCPItemDict return lCPDict @@ -207,6 +151,8 @@ def pyOpenRPA_ServerData(inRequest,inGSettings): def pyOpenRPA_ServerJSInit(inRequest,inGSettings): lResultStr = HiddenJSInitGenerate(inRequest=inRequest, inGSettings=inGSettings) inResponseDict = inRequest.OpenRPAResponseDict + if lResultStr is None: + lResultStr = "" # Write content as utf-8 data inResponseDict["Body"] = bytes(lResultStr, "utf8") @@ -281,8 +227,22 @@ def pyOpenRPA_Processor(inRequest, inGSettings): lActivityTypeListStr = "Has some error with Activity Type read" lWebAuditMessageStr = __Orchestrator__.WebAuditMessageCreate(inRequest=inRequest,inOperationCodeStr=lActivityTypeListStr, inMessageStr="pyOpenRPA_Processor") if lL: lL.info(lWebAuditMessageStr) - # Append in list - inGSettings["ProcessorDict"]["ActivityList"]+=lInput + # Separate into 2 lists - sync and async + lSyncActvityList = [] + lAsyncActivityList = [] + for lActivityItem in lInput: + if lInput.get("ThreadBool", False) == False: + lSyncActvityList.append(lActivityItem) + else: + lAsyncActivityList.append(lActivityItem) + # Sync: Append in list + inGSettings["ProcessorDict"]["ActivityList"]+=lSyncActvityList + # Async: go to run + if len(lAsyncActivityList)>0: + for lActivityItem in lAsyncActivityList: + lActivityItemArgsDict = {"inGSettings":inGSettings,"inActivityList":[lActivityItem]} + lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs=lActivityItemArgsDict) + lThread.start() else: # Logging info about processor activity if not SuperToken () if not __Orchestrator__.WebUserIsSuperToken(inRequest=inRequest, inGSettings=inGSettings): @@ -293,8 +253,13 @@ def pyOpenRPA_Processor(inRequest, inGSettings): lActivityTypeListStr = "Has some error with Activity Type read" lWebAuditMessageStr = __Orchestrator__.WebAuditMessageCreate(inRequest=inRequest,inOperationCodeStr=lActivityTypeListStr, inMessageStr="pyOpenRPA_Processor") if lL: lL.info(lWebAuditMessageStr) - # Append in list - inGSettings["ProcessorDict"]["ActivityList"].append(lInput) + if lInput.get("ThreadBool",False) == False: + # Append in list + inGSettings["ProcessorDict"]["ActivityList"].append(lInput) + else: + lActivityItemArgsDict = {"inGSettings": inGSettings, "inActivityList": [lInput]} + lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs=lActivityItemArgsDict) + lThread.start() # Execute activity list def pyOpenRPA_ActivityListExecute(inRequest, inGSettings): # Recieve the data @@ -369,7 +334,6 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): lThisAgentDict["IsListenBool"] = True # Set is online lQueueList = lThisAgentDict["ActivityList"] if len(lQueueList)>0:# Do some operations if has queue items - if lL: lL.debug(f'O2A: ConnectionCountInt: {lThisAgentDict["ConnectionCountInt"]};ConnectionFirstQueueItemCountInt {lThisAgentDict["ConnectionFirstQueueItemCountInt"]}') # check if delta datetime is < than ActivityLifeTimeSecFloat lActivityItem = lThisAgentDict["ActivityList"][0] lActivityLifetimeSecFloat = (datetime.datetime.now() - lActivityItem["CreatedByDatetime"]).total_seconds() @@ -377,36 +341,35 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): if lActivityLifetimeSecFloat > lActivityItemLifetimeLimitSecFloat: lActivityItem = lThisAgentDict["ActivityList"].pop(0) else: + lReturnActivityItemList = [] lReturnActivityItemDict = None # If lInput['ActivityLastGUIDStr'] is '' > return 0 element for send in Agent if lInput['ActivityLastGUIDStr'] == "": - lReturnActivityItemDict = lThisAgentDict["ActivityList"][0] + lReturnActivityItemList=lQueueList # 2022 02 21 - Maslov Return list - not one item else: # go from the end - search element with GUIDStr lForTriggerGetNextItem = False for lForActivityItemDict in lQueueList: if lForTriggerGetNextItem == True: lReturnActivityItemDict = lForActivityItemDict - break + lReturnActivityItemList.append(lReturnActivityItemDict) # 2022 02 21 - Maslov Return list - not one item + #break if lForActivityItemDict['GUIDStr'] == lInput['ActivityLastGUIDStr']: lForTriggerGetNextItem = True # CASE if GUID is not detected - return 0 element - if lReturnActivityItemDict == None and lForTriggerGetNextItem == False: - lReturnActivityItemDict = lThisAgentDict["ActivityList"][0] + if (len(lQueueList)==1 and lQueueList[0]['GUIDStr'] != lInput['ActivityLastGUIDStr']): + #lReturnActivityItemDict = lThisAgentDict["ActivityList"][0] + lReturnActivityItemList=lQueueList # 2022 02 21 - Maslov Return list - not one item # Send QUEUE ITEM - if lReturnActivityItemDict is not None: - lReturnActivityItemDict = copy.deepcopy(lReturnActivityItemDict) - if "CreatedByDatetime" in lReturnActivityItemDict: - del lReturnActivityItemDict["CreatedByDatetime"] - inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lReturnActivityItemDict), "utf8") + if len(lReturnActivityItemList) > 0: + lReturnActivityItemList = copy.deepcopy(lReturnActivityItemList) + for lItemDict in lReturnActivityItemList: + if "CreatedByDatetime" in lItemDict: + del lItemDict["CreatedByDatetime"] + inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lReturnActivityItemList), "utf8") # Log full version if bytes size is less than limit . else short lBodyLenInt = len(inRequest.OpenRPAResponseDict["Body"]) lAgentLimitLogSizeBytesInt = inGSettings["ServerDict"]["AgentLimitLogSizeBytesInt"] - if lBodyLenInt <= lAgentLimitLogSizeBytesInt: - if lL: lL.info(f"Activity item to agent Hostname {lInput['HostNameUpperStr']}, User {lInput['UserUpperStr']}. Activity item: {lReturnActivityItemDict}") - else: - if lL: lL.info( - f"Activity item to agent Hostname {lInput['HostNameUpperStr']}, User {lInput['UserUpperStr']}. " - f"Activity item: Was suppressed because of body size of {lBodyLenInt} bytes. Max is {lAgentLimitLogSizeBytesInt}") + if lL: lL.debug(f"ActivityItem to Agent ({lInput['HostNameUpperStr']}, {lInput['UserUpperStr']}): Item count: {len(lReturnActivityItemList)}, bytes size: {lBodyLenInt}") lDoLoopBool = False # CLose the connection else: # Nothing to send - sleep for the next iteration time.sleep(lAgentLoopSleepSecFloat) @@ -415,6 +378,32 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): except Exception as e: if lL: lL.exception("pyOpenRPA_Agent_O2A Exception!") lThisAgentDict["ConnectionCountInt"] -= 1 # Connection go to be closed - decrement the connection count + +def pyOpenRPA_Debugging_HelperDefList(inRequest, inGSettings): + # Parse query + lResultDict = { + "success": True, + "results": [] + } + # Get the path + lPathSplitList = __Orchestrator__.WebRequestParsePath(inRequest=inRequest).split('/') + lQueryStr = None + if "HelperDefList" != lPathSplitList[-1] and "" != lPathSplitList[-1]: lQueryStr = lPathSplitList[-1] + if lQueryStr != "" and lQueryStr is not None: + lDefList = __Orchestrator__.ActivityItemHelperDefList(inDefQueryStr=lQueryStr) + for lDefStr in lDefList: + lResultDict["results"].append({"name": lDefStr, "value": lDefStr, "text": lDefStr}) + __Orchestrator__.WebRequestResponseSend(inRequest=inRequest, inResponeStr=json.dumps(lResultDict)) + +def pyOpenRPA_Debugging_HelperDefAutofill(inRequest, inGSettings): + # Parse query + # Get the path + lPathSplitList = __Orchestrator__.WebRequestParsePath(inRequest=inRequest).split('/') + lQueryStr = None + if "HelperDefAutofill" != lPathSplitList[-1] and "" != lPathSplitList[-1]: lQueryStr = lPathSplitList[-1] + lResultDict = __Orchestrator__.ActivityItemHelperDefAutofill(inDef = lQueryStr) + __Orchestrator__.WebRequestResponseSend(inRequest=inRequest, inResponeStr=json.dumps(lResultDict)) + # See docs in Agent (pyOpenRPA.Agent.A2O) def pyOpenRPA_Agent_A2O(inRequest, inGSettings): lL = inGSettings["Logger"] @@ -434,7 +423,13 @@ def pyOpenRPA_Agent_A2O(inRequest, inGSettings): lActivityReturnItemValue = lInput["ActivityReturnDict"][lActivityReturnItemKeyStr] # Create item in gSettings inGSettings["AgentActivityReturnDict"][lActivityReturnItemKeyStr]=SettingsTemplate.__AgentActivityReturnDictItemCreate__(inReturn=lActivityReturnItemValue) - if lL: lL.debug(f"SERVER: pyOpenRPA_Agent_A2O:: Has recieved result of the activity items from agent! ActivityItem GUID Str: {lActivityReturnItemKeyStr}; Return value: {lActivityReturnItemValue}") + lLogStr = "x bytes" + try: + if lActivityReturnItemValue is not None: + lLogStr = f"{len(lActivityReturnItemValue)} bytes" + except Exception as e: + pass + if lL: lL.debug(f"SERVER: pyOpenRPA_Agent_A2O:: Has recieved result of the activity items from agent! ActivityItem GUID Str: {lActivityReturnItemKeyStr}; Return value len: {lLogStr}") # Delete the source activity item from AgentDict if lAgentDictItemKeyTurple in inGSettings["AgentDict"]: lAgentDictActivityListNew = [] @@ -484,6 +479,8 @@ def SettingsUpdate(inGlobalConfiguration): {"Method": "POST", "URL": "/pyOpenRPA/ActivityListExecute", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_ActivityListExecute, "ResponseContentType": "application/json"}, {"Method": "POST", "URL": "/pyOpenRPA/Agent/O2A", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_Agent_O2A, "ResponseContentType": "application/json"}, {"Method": "POST", "URL": "/pyOpenRPA/Agent/A2O", "MatchType": "Equal","ResponseDefRequestGlobal": pyOpenRPA_Agent_A2O, "ResponseContentType": "application/json"}, + {"Method": "GET", "URL": "/pyOpenRPA/Debugging/HelperDefList/", "MatchType": "BeginWith","ResponseDefRequestGlobal": pyOpenRPA_Debugging_HelperDefList, "ResponseContentType": "application/json"}, + {"Method": "GET", "URL": "/pyOpenRPA/Debugging/HelperDefAutofill/", "MatchType": "BeginWith","ResponseDefRequestGlobal": pyOpenRPA_Debugging_HelperDefAutofill, "ResponseContentType": "application/json"}, ] inGlobalConfiguration["ServerDict"]["URLList"]=inGlobalConfiguration["ServerDict"]["URLList"]+lURLList return inGlobalConfiguration \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py index 242ddf55..d2f7fac4 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/SettingsTemplate.py @@ -1,4 +1,5 @@ import os, logging, datetime, sys +import schedule # https://schedule.readthedocs.io/en/stable/examples.html # Technical def - return GSettings structure with examples def __Create__(): @@ -39,6 +40,9 @@ def __Create__(): # # # # # # # # # # # # # # # # # # }, "ServerDict": { + "ControlPanelDict": { + # "CPKey": + }, "AgentLimitLogSizeBytesInt": 300, # Don't show body if json body of transmition is more than "ServerThread": None, # Server thread is there "AgentActivityLifetimeSecFloat": 1200.0, # Time in seconds to life for activity for the agent @@ -105,18 +109,20 @@ def __Create__(): # "ResponseFilePath": "", #Absolute or relative path # "ResponseFolderPath": "", #Absolute or relative path # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result + # "ResponseDefRequestGlobal": None ,#Function with str result + # "UACBool": True # True - check user access before do this URL item. None - get Server flag if ask user # } - { - "Method": "GET", - "URL": "/test/", # URL of the request - "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", - # "ResponseFilePath": "", #Absolute or relative path - "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", - # Absolute or relative path - # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result - } + #{ + # "Method": "GET", + # "URL": "/test/", # URL of the request + # "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", + # # "ResponseFilePath": "", #Absolute or relative path + # "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", + # # Absolute or relative path + # # "ResponseContentType": "", #HTTP Content-type + # # "ResponseDefRequestGlobal": None #Function with str result + # # "UACBool": True # True - check user access before do this URL item + #} ], }, @@ -126,6 +132,7 @@ def __Create__(): "ActivityList": [] }, "SchedulerDict": { + "Schedule": schedule, # https://schedule.readthedocs.io/en/stable/examples.html "CheckIntervalSecFloat": 5.0, # Check interval in seconds "ActivityTimeList": [ # { @@ -145,6 +152,8 @@ def __Create__(): # }, ], }, + "ManagersProcessDict":{}, # The key of the Process is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mProcessNameWOExeStr.upper()) + "ManagersGitDict":{}, # The key of the Git instance is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mAbsPathUpperStr.upper()) "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 "ActivityList": [ # List of the activities # { @@ -156,24 +165,13 @@ def __Create__(): # "GUIDStr": ..., # GUID of the activity # }, ], + "ActivityItemNowDict": None, # Activity Item which is executing now "AliasDefDict": {}, # Storage for def with Str alias. To use it see pyOpenRPA.Orchestrator.ControlPanel "CheckIntervalSecFloat": 1.0, # Interval for check gSettings in ProcessorDict > ActivityList "ExecuteBool": True, # Flag to execute thread processor "ThreadIdInt": None, # Technical field - will be setup when processor init "WarningExecutionMoreThanSecFloat": 60.0 # Push warning if execution more than n seconds }, - "ControlPanelDict": { # Old structure > CPDict - "RefreshSeconds": 5, # deprecated parameter - "RobotList": [ - #{ - # "RenderFunction": RenderRobotR01, - # "KeyStr": "TestControlPanelKey" - #} - ] - }, - "CPDict": { - # "CPKey": {"HTMLRenderDef":None, "JSONGeneratorDef":None, "JSInitGeneratorDef":None} - }, # # # # # # # # # # # # # # "RobotRDPActive": { "RecoveryDict": { @@ -294,7 +292,8 @@ def __UACClientAdminCreate__(): "RestartOrchestratorBool": True, # Restart orchestrator activity "RestartOrchestratorGITPullBool": True, # Turn off (RDP remember) orc + git pull + Turn on (rdp remember) "RestartPCBool": True, # Send CMD to restart pc - "NothingBool":True # USe option if you dont want to give some access to the RDP controls + "NothingBool":True, # USe option if you dont want to give some access to the RDP controls + "Debugging":True # Debugging tool }, "ActivityDict": { # Empty dict - all access "ActivityListExecuteBool": True, # Execute activity at the current thread @@ -324,7 +323,7 @@ def LoggerDumpLogHandlerAdd(inLogger, inGSettingsClientDict): # inModeStr: # "BASIC" - create standart configuration from pyOpenRPA.Orchestrator.Utils import LoggerHandlerDumpLogList -def Create(inModeStr="BASIC"): +def Create(inModeStr="BASIC", inLoggerLevel = None): if inModeStr=="BASIC": lResult = __Create__() # Create settings # Создать файл логирования @@ -334,26 +333,30 @@ def Create(inModeStr="BASIC"): ########################## # Подготовка логгера Robot ######################### - mRobotLogger = lResult["Logger"] - mRobotLogger.setLevel(logging.INFO) - # create the logging file handler - mRobotLoggerFH = logging.FileHandler( - "Reports\\" + datetime.datetime.now().strftime("%Y_%m_%d") + ".log") - mRobotLoggerFormatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') - mRobotLoggerFH.setFormatter(mRobotLoggerFormatter) - # add handler to logger object - mRobotLogger.addHandler(mRobotLoggerFH) - ####################Add console output - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(mRobotLoggerFormatter) - mRobotLogger.addHandler(handler) - ############################################ - LoggerDumpLogHandlerAdd(inLogger=mRobotLogger, inGSettingsClientDict=lResult["Client"]) - #mHandlerDumpLogList = LoggerHandlerDumpLogList.LoggerHandlerDumpLogList(inDict=lResult["Client"], - # inKeyStr="DumpLogList", - # inHashKeyStr="DumpLogListHashStr", - # inRowCountInt=lResult["Client"][ - # "DumpLogListCountInt"]) - #mHandlerDumpLogList.setFormatter(mRobotLoggerFormatter) - #mRobotLogger.addHandler(mHandlerDumpLogList) + if inLoggerLevel is None: inLoggerLevel=logging.INFO + lL = lResult["Logger"] + if len(lL.handlers) == 0: + lL.setLevel(logging.INFO) + # create the logging file handler + mRobotLoggerFH = logging.FileHandler( + "Reports\\" + datetime.datetime.now().strftime("%Y_%m_%d") + ".log") + mRobotLoggerFormatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + mRobotLoggerFH.setFormatter(mRobotLoggerFormatter) + # add handler to logger object + lL.addHandler(mRobotLoggerFH) + ####################Add console output + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(mRobotLoggerFormatter) + lL.addHandler(handler) + ############################################ + LoggerDumpLogHandlerAdd(inLogger=lL, inGSettingsClientDict=lResult["Client"]) + #mHandlerDumpLogList = LoggerHandlerDumpLogList.LoggerHandlerDumpLogList(inDict=lResult["Client"], + # inKeyStr="DumpLogList", + # inHashKeyStr="DumpLogListHashStr", + # inRowCountInt=lResult["Client"][ + # "DumpLogListCountInt"]) + #mHandlerDumpLogList.setFormatter(mRobotLoggerFormatter) + #mRobotLogger.addHandler(mHandlerDumpLogList) + else: + if lL: lL.warning("Pay attention! Your code has been call SettingsTemplate.Create - since pyOpenRPA v1.2.7 GSettings is creating automatically") return lResult # return the result dict \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js index f4f7be28..07fe9ab2 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.js @@ -883,14 +883,110 @@ $(document).ready(function() { if (lUACAsk(["pyOpenRPADict","AdminDict","RestartOrchestratorBool"])) { $(".UACClient-pyOpenRPADict-AdminDict-RestartOrchestratorBool").show(); } if (lUACAsk(["pyOpenRPADict","AdminDict","RestartOrchestratorGITPullBool"])) { $(".UACClient-pyOpenRPADict-AdminDict-RestartOrchestratorGITPullBool").show(); } if (lUACAsk(["pyOpenRPADict","AdminDict","RestartPCBool"])) { $(".UACClient-pyOpenRPADict-AdminDict-RestartPCBool").show(); } + if (lUACAsk(["pyOpenRPADict","AdminDict","Debugging"])) { $(".UACClient-pyOpenRPADict-AdminDict-Debugging").show(); } } /// v1.2.0 pyOpenRPA Init defs - mGlobal.pyOpenRPA.ServerJSInitDef(); // Recieve JS from server (if exist) and then call anothe url ServerData mGlobal.pyOpenRPA.ServerDataRefreshDef(); // Init the refresh data def from server side mGlobal.pyOpenRPA.ServerLogListRefreshDef(); // Init the refresh data def from the log window mGlobal.pyOpenRPA.ServerLogListDoRenderTrue(); // Init button to freeze/unfreeze textare with logs + mGlobal.pyOpenRPA.ServerJSInitDef(); // Recieve JS from server (if exist) and then call anothe url ServerData + //$('.ui.dropdown').dropdown(); + + //////////////////////////////////////////// + // 1.2.7 Debugging + /// Execute ActivityItem - $('.ui.dropdown').dropdown(); + // Debugging onchange def autofill init + var lDropdownOnChange = function(inEvent){ + //lValueStr = inEvent.target.value + lValueStr = inEvent + $.ajax({ + type: "GET", + url: '/pyOpenRPA/Debugging/HelperDefAutofill/'+lValueStr, + data: null, + success: + function(lData,l2,l3) + { + var lResponseJSON=JSON.parse(lData) + console.log("HelperDefAutofill:") + console.log(lResponseJSON) + //ArgDict merge + var lArgDictTargetDict = lResponseJSON["ArgDict"] + var lArgDictStr = $(".mGlobal-pyOpenRPA-Debugging-ArgDict")[0].value + if (lArgDictStr !="" && lArgDictStr !=null) { + lArgDictLastDict = JSON.parse(lArgDictStr) + lArgDictTargetDict = mGlobal.pyOpenRPA.DebuggingAutofillMerge(lArgDictTargetDict, lArgDictLastDict) + } + + $(".mGlobal-pyOpenRPA-Debugging-ArgList")[0].value = JSON.stringify(lResponseJSON["ArgList"]) + $(".mGlobal-pyOpenRPA-Debugging-ArgDict")[0].value = JSON.stringify(lArgDictTargetDict) + $(".mGlobal-pyOpenRPA-Debugging-ArgGSettingsStr")[0].value = JSON.stringify(lResponseJSON["ArgGSettingsStr"]) + $(".mGlobal-pyOpenRPA-Debugging-ArgLoggerStr")[0].value = JSON.stringify(lResponseJSON["ArgLoggerStr"]) + + }, + dataType: "text" + }); + } + //$('.ui.dropdown.mGlobal-pyOpenRPA-Debugging-Def-Dropdown')[0].onchange=lDropdownOnChange + + + + mGlobal.pyOpenRPA.DebuggingExecute=function() { + ///EXAMPLE + // { + // "Def":"OSCMD", // def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + // "ArgList":[], // Args list + // "ArgDict":{"inCMDStr":lCMDCode,"inRunAsyncBool":false}, // Args dictionary + // "ArgGSettings": null, // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + // "ArgLogger": "inLogger" // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + // } + ///Подготовить конфигурацию + lArgListStr = $(".mGlobal-pyOpenRPA-Debugging-ArgList")[0].value + lArgDictStr = $(".mGlobal-pyOpenRPA-Debugging-ArgDict")[0].value + lArgGSettingsStr = $(".mGlobal-pyOpenRPA-Debugging-ArgGSettingsStr")[0].value + lArgLoggerStr = $(".mGlobal-pyOpenRPA-Debugging-ArgLoggerStr")[0].value + lActivityItem = { + "Def":$(".mGlobal-pyOpenRPA-Debugging-Def")[0].value, // def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList":(lArgListStr == "" ? [] : JSON.parse(lArgListStr)), // Args list + "ArgDict":(lArgDictStr == "" ? {} : JSON.parse(lArgDictStr)), // Args dictionary + "ArgGSettingsStr": (lArgGSettingsStr == "" ? null : lArgGSettingsStr), // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLoggerStr": (lArgLoggerStr == "" ? null : lArgLoggerStr) // Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + lData = [lActivityItem] + $.ajax({ + type: "POST", + url: '/pyOpenRPA/ActivityListExecute', + data: JSON.stringify(lData), + success: + function(lData,l2,l3) + { + var lResponseJSON=JSON.parse(lData) + console.log(lResponseJSON) + $(".mGlobal-pyOpenRPA-Debugging-Output")[0].value = JSON.stringify(lResponseJSON[0]) + }, + dataType: "text" + }); + } + mGlobal.pyOpenRPA.DebuggingAutofillMerge=function(inTargetDict, inLastDict) { + // Merge 2 dict (get values from Last dict if key exists in new dict + for (const [lKeyStr, lValue] of Object.entries(inTargetDict)) { + //Check if key exists in LastDict + if (lKeyStr in inLastDict) { + inTargetDict[lKeyStr] = inLastDict[lKeyStr] + } + } + return inTargetDict + } + // 1.2.7 Debugging toolbox init + $('.ui.dropdown.mGlobal-pyOpenRPA-Debugging-Def-Dropdown') + .dropdown({ + apiSettings: { + // this url parses query server side and returns filtered results + url: '/pyOpenRPA/Debugging/HelperDefList/{query}' + }, + onChange: lDropdownOnChange + }) + ; }); \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml index 74e4976d..ec06ac11 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/Web/Index.xhtml @@ -45,6 +45,16 @@ margin: 5em 0em 0em; padding: 5em 0em; } + .ui.search.dropdown>input.search { + width:100%; + font-family:monospace; + font-weight: bold; + } + .ui.search.dropdown>.text { + width:100%; + font-family:monospace; + font-weight: bold; + } @@ -298,6 +308,57 @@ +

diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py index ac155ad3..edbb7c0f 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -1,13 +1,15 @@ import subprocess, json, psutil, time, os, win32security, sys, base64, logging, ctypes, copy #Get input argument import pickle - -from partd import Server +import inspect +import schedule +#from partd import Server from . import Server from . import Timer from . import Processor from . import BackwardCompatibility # Backward compatibility from v1.1.13 from . import Core +from . import Managers from subprocess import CREATE_NEW_CONSOLE from .Utils import LoggerHandlerDumpLogList @@ -27,13 +29,15 @@ from . import SettingsTemplate # Settings template import uuid # Generate uuid import datetime # datetime import math +import glob # search the files +import urllib #Единый глобальный словарь (За основу взять из Settings.py) gSettingsDict = None # AGENT DEFS -def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDict): +def AgentActivityItemAdd(inHostNameStr, inUserStr, inActivityItemDict, inGSettings=None): """ Add activity in AgentDict. Check if item is created @@ -43,6 +47,8 @@ def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDi :param inActivityItemDict: ActivityItem :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ + + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = copy.deepcopy(inActivityItemDict) # Add GUIDStr if not exist lGUIDStr = None @@ -62,7 +68,7 @@ def AgentActivityItemAdd(inGSettings, inHostNameStr, inUserStr, inActivityItemDi return lGUIDStr -def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): +def AgentActivityItemExists(inHostNameStr, inUserStr, inGUIDStr, inGSettings = None): """ Check by GUID if ActivityItem has exists in request list. If exist - the result response has not been recieved from the agent @@ -71,6 +77,7 @@ def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): :return: True - ActivityItem is exist in AgentDict ; False - else case """ # Check if GUID is exists in dict - has been recieved + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Main alg lAgentDictItemKeyTurple = (inHostNameStr.upper(),inUserStr.upper()) lResultBool = False @@ -81,7 +88,7 @@ def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): break return lResultBool -def AgentActivityItemReturnExists(inGSettings, inGUIDStr): +def AgentActivityItemReturnExists(inGUIDStr, inGSettings = None): """ Check by GUID if ActivityItem has been executed and result has come to the Orchestrator @@ -89,11 +96,13 @@ def AgentActivityItemReturnExists(inGSettings, inGUIDStr): :param inGUIDStr: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! :return: True - result has been received from the Agent to orc; False - else case """ + + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check if GUID is exists in dict - has been recieved return inGUIDStr in inGSettings["AgentActivityReturnDict"] -def AgentActivityItemReturnGet(inGSettings, inGUIDStr, inCheckIntervalSecFloat = 0.5): +def AgentActivityItemReturnGet(inGUIDStr, inCheckIntervalSecFloat = 0.5, inGSettings=None): """ Work synchroniously! Wait while result will be recieved. Get the result of the ActivityItem execution on the Agent side. Before this please check by the def AgentActivityItemReturnExists that result has come to the Orchestrator @@ -104,6 +113,7 @@ def AgentActivityItemReturnGet(inGSettings, inGUIDStr, inCheckIntervalSecFloat = :param inCheckIntervalSecFloat: Interval in sec of the check Activity Item result :return: Result of the ActivityItem executed on the Agent side anr transmitted to the Orchestrator. IMPORTANT! ONLY JSON ENABLED Types CAN BE TRANSMITTED TO ORCHESTRATOR! """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings #Check if Orchestrator has been initialized - else raise exception if Core.IsOrchestratorInitialized(inGSettings=inGSettings) == True: # Wait while result will not come here @@ -114,7 +124,7 @@ def AgentActivityItemReturnGet(inGSettings, inGUIDStr, inCheckIntervalSecFloat = else: raise Exception(f"__Orchestrator__.AgentActivityItemReturnGet !ATTENTION! Use this function only after Orchestrator initialization! Before orchestrator init exception will be raised.") -def AgentOSCMD(inGSettings, inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251"): +def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251", inGSettings=None, inCaptureBool=True): """ Send CMD to OS thought the pyOpenRPA.Agent daemon. Result return to log + Orchestrator by the A2O connection @@ -125,21 +135,39 @@ def AgentOSCMD(inGSettings, inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=T :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list - "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr}, # Args dictionary + "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr, "inCaptureBool":inCaptureBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) +def AgentOSLogoff(inHostNameStr, inUserStr): + """ + Logoff the agent user session -def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePathStr, inAgentFilePathStr): + :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! + """ + inGSettings = GSettingsGet() # Set the global settings + lCMDStr = "shutdown /l" + lActivityItemDict = { + "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) + "ArgList":[], # Args list + "ArgDict":{"inCMDStr":lCMDStr,"inRunAsyncBool":False, "inSendOutputToOrchestratorLogsBool": True, "inCMDEncodingStr": "cp1251"}, # Args dictionary + "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + #Send item in AgentDict for the futher data transmition + return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) + +def AgentOSFileSend(inHostNameStr, inUserStr, inOrchestratorFilePathStr, inAgentFilePathStr, inGSettings = None): """ Send the file from the Orchestrator to Agent (synchroniously) pyOpenRPA.Agent daemon process (safe for JSON transmition). Work safety with big files @@ -153,7 +181,7 @@ def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePat :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if inGSettings["ServerDict"]["ServerThread"] is None: if inGSettings["Logger"]: inGSettings["Logger"].warning(f"AgentOSFileSend run before server init - activity will be append in the processor queue.") @@ -205,7 +233,7 @@ def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePat # Close the file lFile.close() -def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBytes): +def AgentOSFileBinaryDataBytesCreate(inHostNameStr, inUserStr, inFilePathStr, inFileDataBytes, inGSettings=None): """ Create binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmition) @@ -216,7 +244,7 @@ def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFi :param inFileDataBytes: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lFileDataBase64Str = base64.b64encode(inFileDataBytes).decode("utf-8") lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) @@ -229,7 +257,7 @@ def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFi return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str): +def AgentOSFileBinaryDataBase64StrCreate(inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str, inGSettings=None): """ Create binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmission) @@ -240,7 +268,7 @@ def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, :param inFileDataBase64Str: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -252,7 +280,7 @@ def AgentOSFileBinaryDataBase64StrCreate(inGSettings, inHostNameStr, inUserStr, return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileBinaryDataBase64StrAppend(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str): +def AgentOSFileBinaryDataBase64StrAppend(inHostNameStr, inUserStr, inFilePathStr, inFileDataBase64Str, inGSettings = None): """ Append binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmission) @@ -263,7 +291,7 @@ def AgentOSFileBinaryDataBase64StrAppend(inGSettings, inHostNameStr, inUserStr, :param inFileDataBase64Str: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrAppend", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -276,7 +304,7 @@ def AgentOSFileBinaryDataBase64StrAppend(inGSettings, inHostNameStr, inUserStr, # Send text file to Agent (string) -def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataStr, inEncodingStr = "utf-8"): +def AgentOSFileTextDataStrCreate(inHostNameStr, inUserStr, inFilePathStr, inFileDataStr, inEncodingStr = "utf-8",inGSettings=None): """ Create text file by the string by the pyOpenRPA.Agent daemon process @@ -288,7 +316,7 @@ def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePa :param inEncodingStr: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileTextDataStrCreate", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -299,7 +327,7 @@ def AgentOSFileTextDataStrCreate(inGSettings, inHostNameStr, inUserStr, inFilePa #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileBinaryDataBase64StrReceive(inGSettings, inHostNameStr, inUserStr, inFilePathStr): +def AgentOSFileBinaryDataBase64StrReceive(inHostNameStr, inUserStr, inFilePathStr, inGSettings = None): """ Read binary file and encode in base64 to transmit (safe for JSON transmition) @@ -309,7 +337,7 @@ def AgentOSFileBinaryDataBase64StrReceive(inGSettings, inHostNameStr, inUserStr, :param inFilePathStr: File path to read :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileBinaryDataBase64StrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -320,7 +348,46 @@ def AgentOSFileBinaryDataBase64StrReceive(inGSettings, inHostNameStr, inUserStr, #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentOSFileTextDataStrReceive(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inEncodingStr="utf-8"): + +def AgentOSFileBinaryDataReceive(inHostNameStr, inUserStr, inFilePathStr): + """ + Read binary file from agent (synchronious) + + :param inGSettings: Global settings dict (singleton) + :param inHostNameStr: + :param inUserStr: + :param inFilePathStr: File path to read + :return: file data bytes + """ + lFileDataBytes = None + inGSettings = GSettingsGet() # Set the global settings + # Check thread + if OrchestratorIsInited() == False: + if inGSettings["Logger"]: inGSettings["Logger"].warning(f"AgentOSFileBinaryDataReceive run before orc init - activity will be append in the processor queue.") + lResult = { + "Def": AgentOSFileBinaryDataReceive, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList": [], # Args list + "ArgDict": {"inHostNameStr":inHostNameStr, "inUserStr":inUserStr, "inFilePathStr":inFilePathStr}, # Args dictionary + "ArgGSettings": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + inGSettings["ProcessorDict"]["ActivityList"].append(lResult) + else: # In processor - do execution + lActivityItemDict = { + "Def":"OSFileBinaryDataBase64StrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) + "ArgList":[], # Args list + "ArgDict":{"inFilePathStr":inFilePathStr}, # Args dictionary + "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + + #Send item in AgentDict for the futher data transmition + lGUIDStr = AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) + lFileBase64Str = AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if lFileBase64Str is not None: lFileDataBytes = base64.b64decode(lFileBase64Str) + return lFileDataBytes + +def AgentOSFileTextDataStrReceive(inHostNameStr, inUserStr, inFilePathStr, inEncodingStr="utf-8", inGSettings = None): """ Read text file in the agent GUI session @@ -331,7 +398,7 @@ def AgentOSFileTextDataStrReceive(inGSettings, inHostNameStr, inUserStr, inFileP :param inEncodingStr: Text file encoding. Default 'utf-8' :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSFileTextDataStrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -342,7 +409,7 @@ def AgentOSFileTextDataStrReceive(inGSettings, inHostNameStr, inUserStr, inFileP #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) -def AgentProcessWOExeUpperUserListGet(inGSettings, inHostNameStr, inUserStr): +def AgentProcessWOExeUpperUserListGet(inHostNameStr, inUserStr, inGSettings = None): """ Return the process list only for the current user (where Agent is running) without .EXE in upper case. Can use in ActivityItem from Orchestrator to Agent @@ -351,7 +418,7 @@ def AgentProcessWOExeUpperUserListGet(inGSettings, inHostNameStr, inUserStr): :param inUserStr: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"ProcessWOExeUpperUserListGet", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list @@ -363,6 +430,14 @@ def AgentProcessWOExeUpperUserListGet(inGSettings, inHostNameStr, inUserStr): return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) # OS DEFS + +def OSLogoff(): + """ + Logoff the current orchestrator session + :return: + """ + os.system("shutdown /l") + def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ## """ Verify user credentials in windows. Return bool @@ -382,7 +457,7 @@ def OSCredentialsVerify(inUserStr, inPasswordStr, inDomainStr=""): ## else: return True -def OSRemotePCRestart(inLogger, inHostStr, inForceBool=True): +def OSRemotePCRestart(inHostStr, inForceBool=True, inLogger = None): """ Send signal via power shell to restart remote PC ATTENTION: Orchestrator user need to have restart right on the Remote machine to restart PC. @@ -392,6 +467,7 @@ def OSRemotePCRestart(inLogger, inHostStr, inForceBool=True): :param inForceBool: True - send signal to force retart PC; False - else case :return: """ + if inLogger is None: inLogger = OrchestratorLoggerGet() lCMDStr = f"powershell -Command Restart-Computer -ComputerName {inHostStr}" if inForceBool == True: lCMDStr = lCMDStr + " -Force" OSCMD(inCMDStr=lCMDStr,inLogger=inLogger) @@ -405,7 +481,11 @@ def OSCMD(inCMDStr, inRunAsyncBool=True, inLogger = None): :param inLogger: :return: CMD result string """ + if inLogger is None: inLogger = OrchestratorLoggerGet() lResultStr = "" + # New feature + if inRunAsyncBool == True: + inCMDStr = f"start {inCMDStr}" # Subdef to listen OS result def _CMDRunAndListenLogs(inCMDStr, inLogger): lResultStr = "" @@ -443,6 +523,7 @@ def OrchestratorRestart(inGSettings=None): :param inGSettings: Global settings dict (singleton) """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings OrchestratorSessionSave(inGSettings=inGSettings) # Dump RDP List in file json if inGSettings is not None: lL = inGSettings["Logger"] @@ -451,6 +532,47 @@ def OrchestratorRestart(inGSettings=None): os.execl(sys.executable, os.path.abspath(__file__), *sys.argv) sys.exit(0) +def OrchestratorLoggerGet() -> logging.Logger: + """ + Get the logger from the Orchestrator + + :return: + """ + return GSettingsGet().get("Logger",None) + + +def OrchestratorScheduleGet() -> schedule: + """ + Get the schedule (schedule.readthedocs.io) from the Orchestrator + + Fro example you can use: + + .. code-block:: python + # One schedule threaded + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(lProcess.StatusCheckStart) + + #New schedule thread # See def description Orchestrator.OrchestratorThreadStart + Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(Orchestrator.OrchestratorThreadStart,lProcess.StatusCheckStart) + + :return: schedule module. Example see here https://schedule.readthedocs.io/en/stable/examples.html + """ + if GSettingsGet().get("SchedulerDict",{}).get("Schedule",None) is None: + GSettingsGet()["SchedulerDict"]["Schedule"]=schedule + return GSettingsGet().get("SchedulerDict",{}).get("Schedule",None) + +def OrchestratorThreadStart(inDef, *inArgList, **inArgDict): + """ + Execute def in new thread and pass some args with list and dict types + + :param inDef: Python Def + :param inArgList: args as list + :param inArgDict: args as dict + :return: threading.Thread object + """ + lDefThread = threading.Thread(target=inDef,args=inArgList,kwargs=inArgDict) + lDefThread.start() + return lDefThread + def OrchestratorIsAdmin(): """ Check if Orchestrator process is running as administrator @@ -462,6 +584,24 @@ def OrchestratorIsAdmin(): except: return False +def OrchestratorIsInited() -> bool: + """Check if Orchestrator initial actions were processed + + :return: True - orc is already inited; False - else + :rtype: bool + """ + + return Core.IsOrchestratorInitialized(inGSettings=GSettingsGet()) + +def OrchestratorInitWait() -> None: + """Wait thread while orc will process initial action. + ATTENTION: DO NOT CALL THIS DEF IN THREAD WHERE ORCHESTRATOR MUST BE INITIALIZED - INFINITE LOOP + """ + lIntervalSecFloat = 0.5 + while not OrchestratorIsInited(): + time.sleep(lIntervalSecFloat) + + def OrchestratorRerunAsAdmin(): """ Check if not admin - then rerun orchestrator as administrator @@ -474,44 +614,132 @@ def OrchestratorRerunAsAdmin(): else: print(f"!SKIPPED! Already run as administrator!") -def OrchestratorSessionSave(inGSettings): +def OrchestratorPySearchInit(inGlobPatternStr, inDefStr = None, inDefArgNameGSettingsStr = None, inAsyncInitBool = False): + """ + Search the py files by the glob and do the safe init (in try except). Also add inited module in sys.modules as imported (module name = file name without extension). + You can init CP in async way! + .. code-block:: python + + # USAGE VAR 1 (without the def auto call) + # Autoinit control panels starts with CP_ + Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py") + + # USAGE VAR 2 (with the def auto call) - for the backward compatibility CP for the Orchestrator ver. < 1.2.7 + # Autoinit control panels starts with CP_ + Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings") + + # INFO: The code above will replace the code below + ## !!! For Relative import !!! CP Version Check + try: + sys.path.insert(0,os.path.abspath(os.path.join(r""))) + from ControlPanel import CP_VersionCheck + CP_VersionCheck.SettingsUpdate(inGSettings=gSettings) + except Exception as e: + gSettings["Logger"].exception(f"Exception when init CP. See below.") + + + :param inGlobPatternStr: example"..\\*\\*\\*X64*.cmd" + :param inDefStr: OPTIONAL The string name of the def. For backward compatibility if you need to auto call some def from initialized module + :param inDefArgNameGSettingsStr: OPTIONAL The name of the GSettings argument in def (if exists) + :param inAsyncInitBool: OPTIONAL True - init py modules in many threads - parallel execution. False (default) - sequence execution + :return: { "ModuleNameStr":{"PyPathStr": "", "Module": ...}, ...} + """ + + # # # # # # # # + def __execute__(inResultDict, inPyPathItemStr, inDefStr = None, inDefArgNameGSettingsStr = None): + try: + lPyPathItemStr = inPyPathItemStr + lModuleNameStr = os.path.basename(lPyPathItemStr)[0:-3] + lTechSpecification = importlib.util.spec_from_file_location(lModuleNameStr, lPyPathItemStr) + lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) + sys.modules[lModuleNameStr] = lTechModuleFromSpec # Add initialized module in sys - python will not init this module enought + lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) + lItemDict = {"ModuleNameStr": lModuleNameStr, "PyPathStr": lPyPathItemStr, "Module": lTechModuleFromSpec} + if lL: lL.info(f"Py module {lModuleNameStr} has been successfully initialized.") + inResultDict[lModuleNameStr]=lItemDict + # Backward compatibility to call def with gsettings when init + if inDefStr is not None and inDefStr is not "": + lDef = getattr(lTechModuleFromSpec, inDefStr) + lArgDict = {} + if inDefArgNameGSettingsStr is not None and inDefArgNameGSettingsStr is not "": + lArgDict = {inDefArgNameGSettingsStr:GSettingsGet()} + lDef(**lArgDict) + except Exception as e: + if lL: lL.exception(f"Exception when init the .py file {os.path.abspath(lPyPathItemStr)}") + # # # # # # # # + + lResultDict = {} + + lPyPathStrList = glob.glob(inGlobPatternStr) # get the file list + lL = OrchestratorLoggerGet() # get the logger + for lPyPathItemStr in lPyPathStrList: + if inAsyncInitBool == True: + # ASYNC EXECUTION + lThreadInit = threading.Thread(target=__execute__,kwargs={ + "inResultDict":lResultDict, "inPyPathItemStr": lPyPathItemStr, + "inDefStr": inDefStr, "inDefArgNameGSettingsStr": inDefArgNameGSettingsStr}, daemon=True) + lThreadInit.start() + else: + # SYNC EXECUTION + __execute__(inResultDict=lResultDict, inPyPathItemStr=lPyPathItemStr, inDefStr = inDefStr, inDefArgNameGSettingsStr = inDefArgNameGSettingsStr) + return lResultDict + +def OrchestratorSessionSave(inGSettings=None): """ Orchestrator session save in file + (from version 1.2.7) + _SessionLast_GSettings.pickle (binary) + + (above the version 1.2.7) _SessionLast_RDPList.json (encoding = "utf-8") _SessionLast_StorageDict.pickle (binary) :param inGSettings: Global settings dict (singleton) :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lL = inGSettings["Logger"] try: # Dump RDP List in file json - lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") - lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file - lFile.close() # Close the file - if inGSettings is not None: + #lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") + #lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file + #lFile.close() # Close the file + #if inGSettings is not None: + # if lL: lL.info( + # f"Orchestrator has dump the RDP list before the restart.") + ## _SessionLast_StorageDict.pickle (binary) + #if "StorageDict" in inGSettings: + # with open('_SessionLast_StorageDict.pickle', 'wb') as lFile: + # pickle.dump(inGSettings["StorageDict"], lFile) + # if lL: lL.info( + # f"Orchestrator has dump the StorageDict before the restart.") + + #SessionLast + lDumpDict = {"StorageDict":inGSettings["StorageDict"], "ManagersProcessDict":inGSettings["ManagersProcessDict"], + "RobotRDPActive":{"RDPList": inGSettings["RobotRDPActive"]["RDPList"]}} + with open('_SessionLast_GSettings.pickle', 'wb') as lFile: + pickle.dump(lDumpDict, lFile) if lL: lL.info( - f"Orchestrator has dump the RDP list before the restart.") - # _SessionLast_StorageDict.pickle (binary) - if "StorageDict" in inGSettings: - with open('_SessionLast_StorageDict.pickle', 'wb') as lFile: - pickle.dump(inGSettings["StorageDict"], lFile) - if lL: lL.info( - f"Orchestrator has dump the StorageDict before the restart.") + f"Orchestrator has dump the GSettings (new dump mode from v1.2.7) before the restart.") + except Exception as e: if lL: lL.exception(f"Exception when dump data before restart the Orchestrator") return True -def OrchestratorSessionRestore(inGSettings): +def OrchestratorSessionRestore(inGSettings=None): """ - Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working directory. if exist - load into gsettings - # _SessionLast_StorageDict.pickle (binary) + Check _SessioLast... files in working directory. if exist - load into gsettings + (from version 1.2.7) + _SessionLast_GSettings.pickle (binary) + + (above the version 1.2.7) _SessionLast_RDPList.json (encoding = "utf-8") _SessionLast_StorageDict.pickle (binary) :param inGSettings: Global settings dict (singleton) :return: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lL = inGSettings.get("Logger",None) # _SessionLast_RDPList.json (encoding = "utf-8") if os.path.exists("_SessionLast_RDPList.json"): @@ -531,6 +759,18 @@ def OrchestratorSessionRestore(inGSettings): in2Dict=lStorageDictDumpDict) # Merge dict 2 into dict 1 if lL: lL.warning(f"StorageDict was restored from previous Orchestrator session") os.remove("_SessionLast_StorageDict.pickle") # remove the temp file + # _SessionLast_Gsettings.pickle (binary) + if os.path.exists("_SessionLast_GSettings.pickle"): + if "StorageDict" not in inGSettings: + inGSettings["StorageDict"] = {} + if "ManagersProcessDict" not in inGSettings: + inGSettings["ManagersProcessDict"] = {} + with open('_SessionLast_GSettings.pickle', 'rb') as lFile: + lStorageDictDumpDict = pickle.load(lFile) + Server.__ComplexDictMerge2to1Overwrite__(in1Dict=inGSettings, + in2Dict=lStorageDictDumpDict) # Merge dict 2 into dict 1 + if lL: lL.warning(f"GSettings was restored from previous Orchestrator session") + os.remove("_SessionLast_GSettings.pickle") # remove the temp file def UACKeyListCheck(inRequest, inRoleKeyList) -> bool: """ @@ -551,7 +791,7 @@ def UACUserDictGet(inRequest) -> dict: """ return inRequest.UserRoleHierarchyGet() # get the Hierarchy -def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inURLList=None, inRoleHierarchyAllowedDict=None): +def UACUpdate(inADLoginStr, inADStr="", inADIsDefaultBool=True, inURLList=None, inRoleHierarchyAllowedDict=None, inGSettings = None): """ Update user access (UAC) @@ -562,6 +802,7 @@ def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inU :param inURLList: :param inRoleHierarchyAllowedDict: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lUserTurple = (inADStr.upper(),inADLoginStr.upper()) # Create turple key for inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"] if inURLList is None: inURLList = [] # Check if None if inRoleHierarchyAllowedDict is None: inRoleHierarchyAllowedDict = {} # Check if None @@ -586,13 +827,14 @@ def UACUpdate(inGSettings, inADLoginStr, inADStr="", inADIsDefaultBool=True, inU # Case add default domain + user inGSettings["ServerDict"]["AccessUsers"]["RuleDomainUserDict"].update({("",inADLoginStr.upper()):lRuleDomainUserDict}) -def UACSuperTokenUpdate(inGSettings, inSuperTokenStr): +def UACSuperTokenUpdate(inSuperTokenStr, inGSettings=None): """ Add supertoken for the all access (it is need for the robot communication without human) :param inGSettings: Global settings dict (singleton) :param inSuperTokenStr: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lLoginStr = "SUPERTOKEN" UACUpdate(inGSettings=inGSettings, inADLoginStr=lLoginStr) inGSettings["ServerDict"]["AccessUsers"]["AuthTokensDict"].update( @@ -604,7 +846,7 @@ def UACSuperTokenUpdate(inGSettings, inSuperTokenStr): # # # # # # # # # # # # # # # # # # # # # # # -def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream"): +def WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream", inGSettings = None, inUACBool = None): """ Connect URL to DEF "inMethodStr":"GET|POST", @@ -614,12 +856,14 @@ def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, "inDef": None #Function with str result :param inGSettings: Global settings dict (singleton) - :param inMethodStr: - :param inURLStr: - :param inMatchTypeStr: - :param inDef: - :param inContentTypeStr: - """ + :param inMethodStr: "GET|POST", + :param inURLStr: example "/index", #URL of the request + :param inMatchTypeStr: #"BeginWith|Contains|Equal|EqualCase", + :param inDef: def arg allowed list: 2:[inRequest, inGSettings], 1: [inRequest], 0: [] + :param inContentTypeStr: default: "application/octet-stream" + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user + """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lURLItemDict = { "Method": inMethodStr.upper(), "URL": inURLStr, # URL of the request @@ -628,25 +872,29 @@ def WebURLConnectDef(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inDef, #"ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", # Absolute or relative path "ResponseContentType": inContentTypeStr, #HTTP Content-type - "ResponseDefRequestGlobal": inDef #Function with str result + "ResponseDefRequestGlobal": inDef, #Function with str result + "UACBool": inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) -def WebURLConnectFolder(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr): +def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings = None, inUACBool = None): """ Connect URL to Folder "inMethodStr":"GET|POST", "inURLStr": "/Folder/", #URL of the request "inMatchTypeStr": "", #"BeginWith|Contains|Equal|EqualCase", "inFolderPathStr": "", #Absolute or relative path + "inUACBool" :param inGSettings: Global settings dict (singleton) :param inMethodStr: :param inURLStr: :param inMatchTypeStr: :param inFolderPathStr: + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check if last symbol is "/" - append if not exist lFolderPathStr = os.path.abspath(inFolderPathStr) if lFolderPathStr[-1]!="/":lFolderPathStr+="/" @@ -659,11 +907,12 @@ def WebURLConnectFolder(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFo "ResponseFolderPath": lFolderPathStr, # Absolute or relative path "ResponseContentType": "application/octet-stream", #HTTP Content-type #"ResponseDefRequestGlobal": inDef #Function with str result + "UACBool": inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) -def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream"): +def WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream", inGSettings = None, inUACBool = None): """ Connect URL to File "inMethodStr":"GET|POST", @@ -677,7 +926,9 @@ def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFile :param inMatchTypeStr: :param inFilePathStr: :param inContentTypeStr: + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lURLItemDict = { "Method": inMethodStr.upper(), "URL": inURLStr, # URL of the request @@ -686,10 +937,11 @@ def WebURLConnectFile(inGSettings, inMethodStr, inURLStr, inMatchTypeStr, inFile #"ResponseFolderPath": os.path.abspath(inFilePathStr), # Absolute or relative path "ResponseContentType": inContentTypeStr, #HTTP Content-type #"ResponseDefRequestGlobal": inDef #Function with str result + "UACBool":inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict) -def WebListenCreate(inGSettings, inServerKeyStr="Default", inAddressStr="", inPortInt=80, inCertFilePEMPathStr=None, inKeyFilePathStr=None): +def WebListenCreate(inServerKeyStr="Default", inAddressStr="", inPortInt=80, inCertFilePEMPathStr=None, inKeyFilePathStr=None, inGSettings = None): """ Create listen interface for the web server @@ -700,7 +952,7 @@ def WebListenCreate(inGSettings, inServerKeyStr="Default", inAddressStr="", inPo :param inKeyFilePathStr: Path to the private key file :return: """ - + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings inGSettings["ServerDict"]["ListenDict"][inServerKeyStr]={ "AddressStr":inAddressStr, "PortInt":inPortInt, @@ -710,7 +962,7 @@ def WebListenCreate(inGSettings, inServerKeyStr="Default", inAddressStr="", inPo } -def WebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None): +def WebCPUpdate(inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDef=None, inJSInitGeneratorDef=None, inGSettings = None): """ Add control panel HTML, JSON generator or JS when page init @@ -720,21 +972,16 @@ def WebCPUpdate(inGSettings, inCPKeyStr, inHTMLRenderDef=None, inJSONGeneratorDe :param inJSONGeneratorDef: :param inJSInitGeneratorDef: """ - # Create Struct if the re is current key - if inCPKeyStr not in inGSettings["CPDict"]: - inGSettings["CPDict"][inCPKeyStr] = {"HTMLRenderDef": None,"JSONGeneratorDef": None, "JSInitGeneratorDef": None} + lCPManager = Managers.ControlPanel(inControlPanelNameStr=inCPKeyStr, inRefreshHTMLJinja2TemplatePathStr=None) # CASE HTMLRender - if inHTMLRenderDef is not None: - inGSettings["CPDict"][inCPKeyStr]["HTMLRenderDef"]=inHTMLRenderDef + if inHTMLRenderDef is not None: lCPManager.mBackwardCompatibilityHTMLDef = inHTMLRenderDef # CASE JSONGenerator - if inJSONGeneratorDef is not None: - inGSettings["CPDict"][inCPKeyStr]["JSONGeneratorDef"] = inJSONGeneratorDef + if inJSONGeneratorDef is not None: lCPManager.mBackwardCompatibilityJSONDef = inJSONGeneratorDef # CASE JSInitGeneratorDef - if inJSInitGeneratorDef is not None: - inGSettings["CPDict"][inCPKeyStr]["JSInitGeneratorDef"] = inJSInitGeneratorDef + if inJSInitGeneratorDef is not None: lCPManager.mBackwardCompatibilityJSDef = inJSInitGeneratorDef -def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): +def WebAuditMessageCreate(inRequest=None, inOperationCodeStr="-", inMessageStr="-"): """ Create message string with request user details (IP, Login etc...). Very actual for IT security in big company. @@ -751,12 +998,13 @@ def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): # Log the WebAudit message lLogger.info(lWebAuditMessageStr) - :param inRequest: HTTP request handler + :param inRequest: HTTP request handler. Optional if call def from request thread :param inOperationCodeStr: operation code in string format (actual for IT audit in control panels) :param inMessageStr: additional message after :return: format "WebAudit :: DOMAIN\\USER@101.121.123.12 :: operation code :: message" """ try: + if inRequest is None: inRequest = WebRequestGet() lClientIPStr = inRequest.client_address[0] lUserDict = WebUserInfoGet(inRequest=inRequest) lDomainUpperStr = lUserDict["DomainUpperStr"] @@ -767,45 +1015,58 @@ def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): lResultStr = inMessageStr return lResultStr -def WebRequestParseBodyBytes(inRequest): +def WebRequestParseBodyBytes(inRequest=None): """ Extract the body in bytes from the request - :param inRequest: inRequest from the server + :param inRequest: inRequest from the server. Optional if call def from request thread :return: Bytes or None """ + if inRequest is None: inRequest = WebRequestGet() lBodyBytes=None if inRequest.headers.get('Content-Length') is not None: lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) lBodyBytes = inRequest.rfile.read(lInputByteArrayLength) return lBodyBytes -def WebRequestParseBodyStr(inRequest): +def WebRequestParseBodyStr(inRequest=None): """ Extract the body in str from the request - :param inRequest: inRequest from the server + :param inRequest: inRequest from the server. Optional if call def from request thread :return: str or None """ - + if inRequest is None: inRequest = WebRequestGet() return WebRequestParseBodyBytes(inRequest=inRequest).decode('utf-8') -def WebRequestParseBodyJSON(inRequest): +def WebRequestParseBodyJSON(inRequest=None): """ Extract the body in dict/list from the request - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: dict or list """ + if inRequest is None: inRequest = WebRequestGet() return json.loads(WebRequestParseBodyStr(inRequest=inRequest)) -def WebRequestParseFile(inRequest): +def WebRequestParsePath(inRequest=None): + """ + Parse the request - extract the url. Example: /pyOpenRPA/Debugging/DefHelper/... + + :param inRequest: inRequest from the server. Optional if call def from request thread + :return: Str, Example: /pyOpenRPA/Debugging/DefHelper/... + """ + if inRequest is None: inRequest = WebRequestGet() + return urllib.parse.unquote(inRequest.path) + +def WebRequestParseFile(inRequest=None): """ Parse the request - extract the file (name, body in bytes) - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: (FileNameStr, FileBodyBytes) or (None, None) """ + if inRequest is None: inRequest = WebRequestGet() lResultTurple=(None,None) if inRequest.headers.get('Content-Length') is not None: lInputByteArray = WebRequestParseBodyBytes(inRequest=inRequest) @@ -825,41 +1086,90 @@ def WebRequestParseFile(inRequest): return lResultTurple -def WebUserInfoGet(inRequest): +def WebRequestResponseSend(inResponeStr, inRequest=None): + """ + Send response for the request + + :param inRequest: inRequest from the server. Optional if call def from request thread + :return: + """ + if inRequest is None: inRequest = WebRequestGet() + inRequest.OpenRPAResponseDict["Body"] = bytes(inResponeStr, "utf8") + + +def WebRequestGet(): + """ + Return the web request instance if current thread was created by web request from client. else return None + + """ + lCurrentThread = threading.current_thread() + if hasattr(lCurrentThread, "request"): + return lCurrentThread.request + +def WebUserInfoGet(inRequest=None): """ Return User info about request - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: {"DomainUpperStr": "", "UserNameUpperStr": ""} """ + if inRequest is None: inRequest = WebRequestGet() lDomainUpperStr = inRequest.OpenRPA["Domain"].upper() lUserUpperStr = inRequest.OpenRPA["User"].upper() return {"DomainUpperStr": lDomainUpperStr, "UserNameUpperStr": lUserUpperStr} -def WebUserIsSuperToken(inRequest, inGSettings): +def WebUserIsSuperToken(inRequest = None, inGSettings = None): """ Return bool if request is authentificated with supetoken (token which is never expires) - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :param inGSettings: Global settings dict (singleton) :return: bool True - is supertoken; False - is not supertoken """ + if inRequest is None: inRequest = WebRequestGet() + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lIsSuperTokenBool = False # Get Flag is supertoken (True|False) lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inRequest.OpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False) return lIsSuperTokenBool -def WebUserUACHierarchyGet(inRequest): +def WebUserUACHierarchyGet(inRequest = None): """ Return User UAC Hierarchy DICT Return {...} - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: UAC Dict {} """ + if inRequest is None: inRequest = WebRequestGet() return inRequest.UserRoleHierarchyGet() + ## GSettings defs -def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None): + +from . import SettingsTemplate + +GSettings = SettingsTemplate.Create(inModeStr = "BASIC") +# Modules alias for pyOpenRPA.Orchestrator and pyOpenRPA.Orchestrator.__Orchestrator__ +lCurrentModule = sys.modules[__name__] +if __name__ == "pyOpenRPA.Orchestrator" and "pyOpenRPA.Orchestrator.__Orchestrator__" not in sys.modules: + sys.modules["pyOpenRPA.Orchestrator.__Orchestrator__"] = lCurrentModule +if __name__ == "pyOpenRPA.Orchestrator.__Orchestrator__" and "pyOpenRPA.Orchestrator" not in sys.modules: + sys.modules["pyOpenRPA.Orchestrator"] = lCurrentModule + +def GSettingsGet(inGSettings=None): + """ + Get the GSettings from the singleton module. + + :param inGSettings: You can pass some GSettings to check if it equal to base gsettings. If not equal - def will merge it + :return: GSettings + """ + global GSettings # identify the global variable + # Merge dictionaries if some new dictionary has come + if inGSettings is not None and GSettings is not inGSettings: + GSettings = Server.__ComplexDictMerge2to1Overwrite__(in1Dict = inGSettings, in2Dict = GSettings) + return GSettings # Return the result + +def GSettingsKeyListValueSet(inValue, inKeyList=None, inGSettings = None): """ Set value in GSettings by the key list @@ -868,6 +1178,7 @@ def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None): :param inKeyList: :return: bool """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -880,7 +1191,7 @@ def GSettingsKeyListValueSet(inGSettings, inValue, inKeyList=None): lDict[inKeyList[-1]] = inValue #Set value return True -def GSettingsKeyListValueGet(inGSettings, inKeyList=None): +def GSettingsKeyListValueGet(inKeyList=None, inGSettings = None): """ Get the value from the GSettings by the key list @@ -888,6 +1199,7 @@ def GSettingsKeyListValueGet(inGSettings, inKeyList=None): :param inKeyList: :return: value any type """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -899,7 +1211,7 @@ def GSettingsKeyListValueGet(inGSettings, inKeyList=None): lDict=lDict[lItem2] return lDict.get(inKeyList[-1],None) -def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None): +def GSettingsKeyListValueAppend(inValue, inKeyList=None, inGSettings = None): """ Append value in GSettings by the key list @@ -926,6 +1238,7 @@ def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None): :param inKeyList: List of the nested keys (see example) :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -938,7 +1251,7 @@ def GSettingsKeyListValueAppend(inGSettings, inValue, inKeyList=None): lDict[inKeyList[-1]].append(inValue) #Set value return True -def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None): +def GSettingsKeyListValueOperatorPlus(inValue, inKeyList=None, inGSettings = None): """ Execute plus operation between 2 lists (1:inValue and 2:gSettings by the inKeyList) @@ -968,6 +1281,7 @@ def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None): :param inKeyList: List of the nested keys (see example) :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inKeyList is None: inKeyList = [] lDict = inGSettings for lItem2 in inKeyList[:-1]: @@ -980,10 +1294,31 @@ def GSettingsKeyListValueOperatorPlus(inGSettings, inValue, inKeyList=None): lDict[inKeyList[-1]] += inValue #Set value return True -def ProcessorAliasDefCreate(inGSettings, inDef, inAliasStr=None): +def StorageRobotExists(inRobotNameStr): + """ + Check if robot storage exists + + :param inRobotNameStr: Robot name (case sensitive) + :return: True - robot storage exist; False - does not exist + """ + return inRobotNameStr in GSettingsGet()["StorageDict"] + +def StorageRobotGet(inRobotNameStr): + """ + Get the robot storage by the robot name. If Robot storage is not exist - function will create it + + :param inRobotNameStr: Robot name (case sensitive) + :return: Dict + """ + if inRobotNameStr not in GSettingsGet()["StorageDict"]: + GSettingsGet()["StorageDict"][inRobotNameStr]={} + return GSettingsGet()["StorageDict"][inRobotNameStr] + +def ProcessorAliasDefCreate(inDef, inAliasStr=None, inGSettings = None): """ Create alias for def (can be used in ActivityItem in field Def) !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + Deprecated. See ActivityItemDefAliasCreate .. code-block:: python @@ -1003,20 +1338,13 @@ def ProcessorAliasDefCreate(inGSettings, inDef, inAliasStr=None): :param inAliasStr: String alias for associated def :return: str Alias string (Alias can be regenerated if previous alias was occupied) """ - #TODO Pay attention - New alias can be used too - need to create more complex algorythm to create new alias! - lL = inGSettings["Logger"] - if inAliasStr is None: inAliasStr = str(inDef) - # Check if key is not exists - if inAliasStr in inGSettings["ProcessorDict"]["AliasDefDict"]: - inAliasStr = str(inDef) - if lL: lL.warning(f"Orchestrator.ProcessorAliasDefCreate: Alias {inAliasStr} already exists in alias dictionary. Another alias will be generated and returned") - inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef - return inAliasStr + return ActivityItemDefAliasCreate(inDef=inDef, inAliasStr=inAliasStr, inGSettings = inGSettings) -def ProcessorAliasDefUpdate(inGSettings, inDef, inAliasStr): +def ProcessorAliasDefUpdate(inDef, inAliasStr, inGSettings = None): """ Update alias for def (can be used in ActivityItem in field Def). !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + Deprecated. See ActivityItemDefAliasUpdate .. code-block:: python @@ -1036,11 +1364,59 @@ def ProcessorAliasDefUpdate(inGSettings, inDef, inAliasStr): :param inAliasStr: String alias for associated def :return: str Alias string """ - if callable(inDef): inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef - else: raise Exception(f"pyOpenRPA Exception: You can't use Orchestrator.ProcessorAliasDefUpdate with arg 'inDef' string value. inDef is '{inDef}', inAliasStr is '{inAliasStr}'") - return inAliasStr + return ActivityItemDefAliasUpdate(inDef=inDef, inAliasStr=inAliasStr, inGSettings = inGSettings) -def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inGUIDStr = None): +# ActivityItem defs +def ActivityItemHelperDefList(inDefQueryStr=None): + """ + Create list of the available Def names in activity item. You can use query def filter via arg inDefQueryStr + + :param inDefStr: + :return: ["ActivityItemDefAliasUpdate", "ActivityItemDefAliasCreate", etc...] + """ + lResultList = [] + if inDefQueryStr is not None: # do search alg + for lKeyStr in GSettingsGet()["ProcessorDict"]["AliasDefDict"]: + if inDefQueryStr.upper() in lKeyStr.upper(): + lResultList.append(lKeyStr) + else: + for lKeyStr in GSettingsGet()["ProcessorDict"]["AliasDefDict"]: + lResultList.append(lKeyStr) + return lResultList + +def ActivityItemHelperDefAutofill(inDef): + """ + Detect def by the name and prepare the activity item dict with values. + + :param inDef: + :return: + """ + lResultDict = { + "Def": None, + "ArgList": [], + "ArgDict": {}, + "ArgGSettingsStr": None, + "ArgLoggerStr": None + } + lResultDict["Def"] = inDef + lGS = GSettingsGet() + if inDef in lGS["ProcessorDict"]["AliasDefDict"]: + lDefSignature = inspect.signature(lGS["ProcessorDict"]["AliasDefDict"][inDef]) + for lItemKeyStr in lDefSignature.parameters: + lItemValue = lDefSignature.parameters[lItemKeyStr] + # Check if arg name contains "GSetting" or "Logger" + if "GSETTING" in lItemKeyStr.upper(): + lResultDict["ArgGSettingsStr"] = lItemKeyStr + elif "LOGGER" in lItemKeyStr.upper(): + lResultDict["ArgLoggerStr"] = lItemKeyStr + else: + if lItemValue.default is inspect._empty: + lResultDict["ArgDict"][lItemKeyStr] = None + else: + lResultDict["ArgDict"][lItemKeyStr] = lItemValue.default + return lResultDict + +def ActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inGUIDStr = None, inThreadBool = False): """ Create activity item. Activity item can be used as list item in ProcessorActivityItemAppend or in Processor.ActivityListExecute. @@ -1052,7 +1428,7 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet # EXAMPLE 1 def TestDef(inArg1Str, inGSettings, inLogger): pass - lActivityItem = Orchestrator.ProcessorActivityItemCreate( + lActivityItem = Orchestrator.ActivityItemCreate( inDef = TestDef, inArgList=[], inArgDict={"inArg1Str": "ArgValueStr"}, @@ -1070,11 +1446,11 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet # EXAMPLE 2 def TestDef(inArg1Str): pass - Orchestrator.ProcessorAliasDefUpdate( + Orchestrator.ActivityItemDefAliasUpdate( inGSettings = gSettings, inDef = TestDef, inAliasStr="TestDefAlias") - lActivityItem = Orchestrator.ProcessorActivityItemCreate( + lActivityItem = Orchestrator.ActivityItemCreate( inDef = "TestDefAlias", inArgList=[], inArgDict={"inArg1Str": "ArgValueStr"}, @@ -1095,6 +1471,7 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet :param inArgGSettingsStr: Name of def argument of the GSettings dict :param inArgLoggerStr: Name of def argument of the logging object :param inGUIDStr: GUID which you can specify. If None the GUID will be generated + :param inThreadBool: True - execute ActivityItem in new thread; False - in processor thread :return: {} """ # Work about GUID in Activity items @@ -1108,11 +1485,158 @@ def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSet "ArgDict":inArgDict, # Args dictionary "ArgGSettings": inArgGSettingsStr, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": inArgLoggerStr, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) - "GUIDStr": inGUIDStr + "GUIDStr": inGUIDStr, + "ThreadBool": inThreadBool } return lActivityItemDict -def ProcessorActivityItemAppend(inGSettings, inDef=None, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inActivityItemDict=None): + +def ActivityItemDefAliasCreate(inDef, inAliasStr=None, inGSettings = None): + """ + Create alias for def (can be used in ActivityItem in field Def) + !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + + .. code-block:: python + + # USAGE + from pyOpenRPA import Orchestrator + + def TestDef(): + pass + lAliasStr = Orchestrator.ActivityItemDefAliasCreate( + inGSettings = gSettings, + inDef = TestDef, + inAliasStr="TestDefAlias") + # Now you can call TestDef by the alias from var lAliasStr with help of ActivityItem (key Def = lAliasStr) + + :param inGSettings: Global settings dict (singleton) + :param inDef: Def + :param inAliasStr: String alias for associated def + :return: str Alias string (Alias can be regenerated if previous alias was occupied) + """ + #TODO Pay attention - New alias can be used too - need to create more complex algorythm to create new alias! + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings + lL = inGSettings["Logger"] + if inAliasStr is None: inAliasStr = str(inDef) + # Check if key is not exists + if inAliasStr in inGSettings["ProcessorDict"]["AliasDefDict"]: + inAliasStr = str(inDef) + if lL: lL.warning(f"Orchestrator.ProcessorAliasDefCreate: Alias {inAliasStr} already exists in alias dictionary. Another alias will be generated and returned") + inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef + return inAliasStr + +def ActivityItemDefAliasModulesLoad(): + """ + Load all def from sys.modules... in ActivityItem def alias dict + + :return: None + """ + lL = OrchestratorLoggerGet() + lL.info(f"ActivityItem aliases: start to load sys.modules") + lSysModulesSnapshot = copy.copy(sys.modules) # Actual when start from jupyter + for lModuleItemStr in lSysModulesSnapshot: + lModuleItem = lSysModulesSnapshot[lModuleItemStr] + for lDefItemStr in dir(lModuleItem): + try: + lDefItem = getattr(lModuleItem,lDefItemStr) + if callable(lDefItem) and not lDefItemStr.startswith("_"): + ActivityItemDefAliasCreate(inDef=lDefItem, inAliasStr=f"{lModuleItemStr}.{lDefItemStr}") + except ModuleNotFoundError: + pass + lL.info(f"ActivityItem aliases: finish to load sys.modules") + +def ActivityItemDefAliasUpdate(inDef, inAliasStr, inGSettings = None): + """ + Update alias for def (can be used in ActivityItem in field Def). + !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can't transmit Python def object out of the Python environment) + + .. code-block:: python + + # USAGE + from pyOpenRPA import Orchestrator + + def TestDef(): + pass + Orchestrator.ActivityItemDefAliasUpdate( + inGSettings = gSettings, + inDef = TestDef, + inAliasStr="TestDefAlias") + # Now you can call TestDef by the alias "TestDefAlias" with help of ActivityItem (key Def = "TestDefAlias") + + :param inGSettings: Global settings dict (singleton) + :param inDef: Def + :param inAliasStr: String alias for associated def + :return: str Alias string + """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings + if callable(inDef): inGSettings["ProcessorDict"]["AliasDefDict"][inAliasStr] = inDef + else: raise Exception(f"pyOpenRPA Exception: You can't use Orchestrator.ActivityItemDefAliasUpdate with arg 'inDef' string value. inDef is '{inDef}', inAliasStr is '{inAliasStr}'") + return inAliasStr + + + +def ProcessorActivityItemCreate(inDef, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inGUIDStr = None, inThreadBool = False): + """ + Create activity item. Activity item can be used as list item in ProcessorActivityItemAppend or in Processor.ActivityListExecute. + Deprecated. See ActivityItemCreate + .. code-block:: python + + # USAGE + from pyOpenRPA import Orchestrator + + # EXAMPLE 1 + def TestDef(inArg1Str, inGSettings, inLogger): + pass + lActivityItem = Orchestrator.ProcessorActivityItemCreate( + inDef = TestDef, + inArgList=[], + inArgDict={"inArg1Str": "ArgValueStr"}, + inArgGSettingsStr = "inGSettings", + inArgLoggerStr = "inLogger") + # lActivityItem: + # { + # "Def":TestDef, + # "ArgList":inArgList, + # "ArgDict":inArgDict, + # "ArgGSettings": "inArgGSettings", + # "ArgLogger": "inLogger" + # } + + # EXAMPLE 2 + def TestDef(inArg1Str): + pass + Orchestrator.ProcessorAliasDefUpdate( + inGSettings = gSettings, + inDef = TestDef, + inAliasStr="TestDefAlias") + lActivityItem = Orchestrator.ProcessorActivityItemCreate( + inDef = "TestDefAlias", + inArgList=[], + inArgDict={"inArg1Str": "ArgValueStr"}, + inArgGSettingsStr = None, + inArgLoggerStr = None) + # lActivityItem: + # { + # "Def":"TestDefAlias", + # "ArgList":inArgList, + # "ArgDict":inArgDict, + # "ArgGSettings": None, + # "ArgLogger": None + # } + + :param inDef: def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + :param inArgList: Args list for the Def + :param inArgDict: Args dict for the def + :param inArgGSettingsStr: Name of def argument of the GSettings dict + :param inArgLoggerStr: Name of def argument of the logging object + :param inGUIDStr: GUID which you can specify. If None the GUID will be generated + :param inThreadBool: True - execute ActivityItem in new thread; False - in processor thread + :return: {} + """ + return ActivityItemCreate(inDef=inDef, inArgList=inArgList, inArgDict=inArgDict, inArgGSettingsStr=inArgGSettingsStr, inArgLoggerStr=inArgLoggerStr, + inGUIDStr=inGUIDStr, inThreadBool=inThreadBool) + +def ProcessorActivityItemAppend(inGSettings = None, inDef=None, inArgList=None, inArgDict=None, inArgGSettingsStr=None, inArgLoggerStr=None, inActivityItemDict=None): """ Create and add activity item in processor queue. @@ -1160,6 +1684,7 @@ def ProcessorActivityItemAppend(inGSettings, inDef=None, inArgList=None, inArgDi :param inActivityItemDict: Fill if you already have ActivityItemDict (don't fill inDef, inArgList, inArgDict, inArgGSettingsStr, inArgLoggerStr) :return ActivityItem GUIDStr """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inActivityItemDict is None: if inArgList is None: inArgList=[] if inArgDict is None: inArgDict={} @@ -1344,7 +1869,7 @@ def ProcessListGet(inProcessNameWOExeList=None): return lResult -def ProcessDefIntervalCall(inGSettings, inDef, inIntervalSecFloat, inIntervalAsyncBool=False, inDefArgList=None, inDefArgDict=None, inDefArgGSettingsNameStr=None, inDefArgLoggerNameStr=None, inExecuteInNewThreadBool=True, inLogger=None): +def ProcessDefIntervalCall(inDef, inIntervalSecFloat, inIntervalAsyncBool=False, inDefArgList=None, inDefArgDict=None, inDefArgGSettingsNameStr=None, inDefArgLoggerNameStr=None, inExecuteInNewThreadBool=True, inLogger=None, inGSettings = None): """ Use this procedure if you need to run periodically some def. Set def, args, interval and enjoy :) @@ -1360,6 +1885,8 @@ def ProcessDefIntervalCall(inGSettings, inDef, inIntervalSecFloat, inIntervalAsy :param inLogger: logging def if some case is appear :return: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings + if inLogger is None: inLogger = OrchestratorLoggerGet() #Some edits on start if inDefArgDict is None: inDefArgDict = {} if inDefArgList is None: inDefArgList = [] @@ -1448,6 +1975,7 @@ def PythonStart(inModulePathStr, inDefNameStr, inArgList=None, inArgDict=None, i :param inLogger: Logger instance to log some information when PythonStart def is running :return: None """ + if inLogger is None: inLogger = OrchestratorLoggerGet() if inArgList is None: inArgList=[] if inArgDict is None: inArgDict={} try: @@ -1461,7 +1989,7 @@ def PythonStart(inModulePathStr, inDefNameStr, inArgList=None, inArgDict=None, i # Scheduler # # # # # # # # # # # # # # # # # # # # # # # -def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekdayList=None, inActivityList=None): +def SchedulerActivityTimeAddWeekly(inTimeHHMMStr="23:55:", inWeekdayList=None, inActivityList=None, inGSettings = None): """ Add activity item list in scheduler. You can set weekday list and set time when launch. Activity list will be executed at planned time/day. @@ -1492,6 +2020,7 @@ def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekda :param inActivityList: Activity list structure :return: None """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inWeekdayList is None: inWeekdayList=[0,1,2,3,4,5,6] if inActivityList is None: inActivityList=[] Processor.__ActivityListVerify__(inActivityList=inActivityList) # DO VERIFICATION FOR THE inActivityList @@ -1508,7 +2037,7 @@ def SchedulerActivityTimeAddWeekly(inGSettings, inTimeHHMMStr="23:55:", inWeekda # # # # # # # # # # # # # # # # # # # # # # # def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortInt = 3389, inWidthPXInt = 1680, inHeightPXInt = 1050, - inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None): + inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None, inRedirectClipboardBool=True): """ Create RDP connect dict item/ Use it connect/reconnect (Orchestrator.RDPSessionConnect) @@ -1531,6 +2060,7 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn # "Host": "127.0.0.1", "Port": "3389", "Login": "USER_99", "Password": "USER_PASS_HERE", # "Screen": { "Width": 1680, "Height": 1050, "FlagUseAllMonitors": False, "DepthBit": "32" }, # "SharedDriveList": ["c"], + # "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else # ###### Will updated in program ############ # "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" # "SessionIsWindowExistBool": False, "SessionIsWindowResponsibleBool": False, "SessionIsIgnoredBool": False @@ -1545,6 +2075,7 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn :param inUseBothMonitorBool: True - connect to the RDP with both monitors. False - else case :param inDepthBitInt: Remote desktop bitness. Available: 32 or 24 or 16 or 15, example 32 :param inSharedDriveList: Host local disc to connect to the RDP session. Example: ["c", "d"] + :param inRedirectClipboardBool: # True - share clipboard to RDP; False - else :return: { "Host": inHostStr, # Host address, example "77.77.22.22" @@ -1559,6 +2090,7 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn "DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32" }, "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"] + "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, @@ -1570,6 +2102,8 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn """ if inSharedDriveList is None: inSharedDriveList = ["c"] + if inPortInt is None: inPortInt = 3389 + if inRedirectClipboardBool is None: inRedirectClipboardBool = True lRDPTemplateDict= { # Init the configuration item "Host": inHostStr, # Host address, example "77.77.22.22" "Port": str(inPortInt), # RDP Port, example "3389" @@ -1582,7 +2116,8 @@ def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortIn "FlagUseAllMonitors": inUseBothMonitorBool, # True or False, example False "DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32" }, - "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"] + "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"], + "RedirectClipboardBool": inRedirectClipboardBool, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, @@ -1607,7 +2142,7 @@ def RDPSessionDublicatesResolve(inGSettings): #for lItemKeyStr in inGSettings["RobotRDPActive"]["RDPList"]: # lItemDict = inGSettings["RobotRDPActive"]["RDPList"][lItemKeyStr] -def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None): +def RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings = None, inRedirectClipboardBool=True): """ Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if RDP key is already exists 2 way of the use @@ -1638,6 +2173,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i :param inPasswordStr: Backward compatibility from Orchestrator v 1.1.20. Use inRDPTemplateDict :return: True every time :) """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -1645,7 +2181,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i "Def": RDPSessionConnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inRDPTemplateDict":inRDPTemplateDict, "inHostStr": inHostStr, "inPortStr": inPortStr, - "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr}, # Args dictionary + "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr, "inRedirectClipboardBool": inRedirectClipboardBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } @@ -1656,7 +2192,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i # Var 2 - backward compatibility if lRDPConfigurationItem is None: lRDPConfigurationItem = RDPTemplateCreate(inLoginStr=inLoginStr, inPasswordStr=inPasswordStr, - inHostStr=inHostStr, inPortInt = int(inPortStr)) # ATTENTION - dont connect if RDP session is exist + inHostStr=inHostStr, inPortInt = int(inPortStr), inRedirectClipboardBool=inRedirectClipboardBool) # ATTENTION - dont connect if RDP session is exist # Start the connect if inRDPSessionKeyStr not in inGSettings["RobotRDPActive"]["RDPList"]: inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem # Add item in RDPList @@ -1666,7 +2202,7 @@ def RDPSessionConnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None, i if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP session was not created because it is alredy exists in the RDPList. Use RDPSessionReconnect if you want to update RDP configuration.") return True -def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None): +def RDPSessionDisconnect(inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None, inGSettings = None): """ Disconnect the RDP session and stop monitoring it. @@ -1689,6 +2225,7 @@ def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessW Orchestrator look processes on the current machine :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inBreakTriggerProcessWOExeList is None: inBreakTriggerProcessWOExeList = [] # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): @@ -1713,7 +2250,7 @@ def RDPSessionDisconnect(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessW Connector.SystemRDPWarningClickOk() # Click all warning messages return True -def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None): +def RDPSessionReconnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inGSettings = None): """ Reconnect the RDP session @@ -1737,6 +2274,7 @@ def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None) :param inRDPTemplateDict: RDP configuration dict with settings (see def Orchestrator.RDPTemplateCreate) :return: """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -1759,7 +2297,7 @@ def RDPSessionReconnect(inGSettings, inRDPSessionKeyStr, inRDPTemplateDict=None) Connector.Session(lRDPConfigurationItem) return True -def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr): +def RDPSessionMonitorStop(inRDPSessionKeyStr, inGSettings = None): """ Stop monitoring the RDP session by the Orchestrator process. Current def don't kill RDP session - only stop to track it (it can give ) @@ -1777,11 +2315,12 @@ def RDPSessionMonitorStop(inGSettings, inRDPSessionKeyStr): :param inRDPSessionKeyStr: RDP Session string key - need for the further identification :return: True every time :> """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lResult = True inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList return lResult -def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None): +def RDPSessionLogoff(inRDPSessionKeyStr, inBreakTriggerProcessWOExeList = None, inGSettings = None): """ Logoff the RDP session from the Orchestrator process (close all apps in session when logoff) @@ -1801,6 +2340,7 @@ def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExe :param inBreakTriggerProcessWOExeList: List of the processes, which will stop the execution. Example ["notepad"] :return: True - logoff is successful """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings if inBreakTriggerProcessWOExeList is None: inBreakTriggerProcessWOExeList = [] lResult = True # Check thread @@ -1828,7 +2368,7 @@ def RDPSessionLogoff(inGSettings, inRDPSessionKeyStr, inBreakTriggerProcessWOExe inGSettings["RobotRDPActive"]["RDPList"].pop(inRDPSessionKeyStr,None) # Remove item from RDPList return lResult -def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): +def RDPSessionResponsibilityCheck(inRDPSessionKeyStr, inGSettings = None): """ DEVELOPING, MAYBE NOT USEFUL Check RDP Session responsibility TODO NEED DEV + TEST @@ -1836,6 +2376,7 @@ def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): :param inRDPSessionKeyStr: RDP Session string key - need for the further identification :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -1871,7 +2412,7 @@ def RDPSessionResponsibilityCheck(inGSettings, inRDPSessionKeyStr): lDoCheckResponsibilityCountCurrent+=1 return True -def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True): +def RDPSessionProcessStartIfNotRunning(inRDPSessionKeyStr, inProcessNameWEXEStr, inFilePathStr, inFlagGetAbsPathBool=True, inGSettings = None): """ Start process in RDP if it is not running (check by the arg inProcessNameWEXEStr) @@ -1895,6 +2436,7 @@ def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProces :param inFlagGetAbsPathBool: True - get abs path from the relative path in inFilePathStr. False - else case :return: True every time :) """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread lResult = True if not Core.IsProcessorThread(inGSettings=inGSettings): @@ -1917,7 +2459,7 @@ def RDPSessionProcessStartIfNotRunning(inGSettings, inRDPSessionKeyStr, inProces inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSSCHECK"): +def RDPSessionCMDRun(inRDPSessionKeyStr, inCMDStr, inModeStr="CROSSCHECK", inGSettings = None): """ Send CMD command to the RDP session "RUN" window @@ -1945,6 +2487,7 @@ def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSS "IsResponsibleBool": True|False # Flag is RDP is responsible - works only when inModeStr = CROSSCHECK } """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lResult = { "OutStr": None, # Result string "IsResponsibleBool": False # Flag is RDP is responsible - works only when inModeStr = CROSSCHECK @@ -1970,7 +2513,7 @@ def RDPSessionCMDRun(inGSettings, inRDPSessionKeyStr, inCMDStr, inModeStr="CROSS inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool): +def RDPSessionProcessStop(inRDPSessionKeyStr, inProcessNameWEXEStr, inFlagForceCloseBool, inGSettings = None): """ Send CMD command to the RDP session "RUN" window. @@ -1992,6 +2535,7 @@ def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, :param inFlagForceCloseBool: True - force close the process. False - safe close the process :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -2015,7 +2559,7 @@ def RDPSessionProcessStop(inGSettings, inRDPSessionKeyStr, inProcessNameWEXEStr, Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="CROSSCHECK", inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr): +def RDPSessionFileStoredSend(inRDPSessionKeyStr, inHostFilePathStr, inRDPFilePathStr, inGSettings = None): """ Send file from Orchestrator session to the RDP session using shared drive in RDP (see RDP Configuration Dict, Shared drive) @@ -2037,6 +2581,7 @@ def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, :param inRDPFilePathStr: !Absolute! path to the destination file location on the RDP side. Example: "C:\\RPA\\TESTDIR\\Test.py" :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -2059,7 +2604,7 @@ def RDPSessionFileStoredSend(inGSettings, inRDPSessionKeyStr, inHostFilePathStr, Connector.SessionCMDRun(inSessionHex=lSessionHex, inCMDCommandStr=lCMDStr, inModeStr="LISTEN", inClipboardTimeoutSec = 120, inLogger=inGSettings["Logger"], inRDPConfigurationItem=inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr]) return lResult -def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr): +def RDPSessionFileStoredRecieve(inRDPSessionKeyStr, inRDPFilePathStr, inHostFilePathStr, inGSettings = None): """ Recieve file from RDP session to the Orchestrator session using shared drive in RDP (see RDP Configuration Dict, Shared drive) @@ -2081,6 +2626,7 @@ def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathSt :param inHostFilePathStr: Relative or absolute path to the file location on the Orchestrator side. Example: "TESTDIR\\Test.py" :return: True every time """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check thread if not Core.IsProcessorThread(inGSettings=inGSettings): if inGSettings["Logger"]: inGSettings["Logger"].warning(f"RDP def was called not from processor queue - activity will be append in the processor queue.") @@ -2106,17 +2652,17 @@ def RDPSessionFileStoredRecieve(inGSettings, inRDPSessionKeyStr, inRDPFilePathSt # # # # # Start orchestrator # # # # # # # # # # # # # # # # # # # # # # # -def GSettingsAutocleaner(inGSettings): +def GSettingsAutocleaner(inGSettings=None): """ HIDDEN Interval gSettings auto cleaner def to clear some garbage. :param inGSettings: Global settings dict (singleton) :return: None """ + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings while True: time.sleep(inGSettings["Autocleaner"]["IntervalSecFloat"]) # Wait for the next iteration lL = inGSettings["Logger"] - if lL: lL.info(f"Autocleaner is running") # Info lNowDatetime = datetime.datetime.now() # Get now time # Clean old items in Client > Session > TechnicalSessionGUIDCache lTechnicalSessionGUIDCacheNew = {} @@ -2140,13 +2686,32 @@ def GSettingsAutocleaner(inGSettings): from .. import __version__ # Get version from the package -def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool = True): +def Start(inDumpRestoreBool = True, inRunAsAdministratorBool = True): + """ + Start the orchestrator threads execution + + :param inDumpRestoreBool: True - restore data from the dumo + :param inRunAsAdministratorBool: True - rerun as admin if not + :return: + """ + Orchestrator(inDumpRestoreBool = True, inRunAsAdministratorBool = True) + +def Orchestrator(inGSettings=None, inDumpRestoreBool = True, inRunAsAdministratorBool = True): + """ + Main def to start orchestrator + + :param inGSettings: + :param inDumpRestoreBool: + :param inRunAsAdministratorBool: + :return: + """ lL = inGSettings["Logger"] # https://stackoverflow.com/questions/130763/request-uac-elevation-from-within-a-python-script if not OrchestratorIsAdmin() and inRunAsAdministratorBool==True: OrchestratorRerunAsAdmin() else: # Code of your program here + inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings #mGlobalDict = Settings.Settings(sys.argv[1]) global gSettingsDict gSettingsDict = inGSettings # Alias for old name in alg @@ -2194,10 +2759,10 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lItemDef = getattr(lModule,lItemDefNameStr) if callable(lItemDef): inGSettings["ProcessorDict"]["AliasDefDict"][lItemDefNameStr]=lItemDef + #Load all defs from sys.modules + ActivityItemDefAliasModulesLoad() + #Инициализация настроечных параметров - lDaemonLoopSeconds=gSettingsDict["SchedulerDict"]["CheckIntervalSecFloat"] - lDaemonActivityLogDict={} #Словарь отработанных активностей, ключ - кортеж (, , , ) - lDaemonLastDateTime=datetime.datetime.now() gSettingsDict["ServerDict"]["WorkingDirectoryPathStr"] = os.getcwd() # Set working directory in g settings #Инициализация сервера (инициализация всех интерфейсов) @@ -2222,12 +2787,17 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lRobotRDPActiveThread.start() # Start the thread execution. if lL: lL.info("Robot RDP active has been started") #Logging + + # Init autocleaner in another thread lAutocleanerThread = threading.Thread(target= GSettingsAutocleaner, kwargs={"inGSettings":gSettingsDict}) lAutocleanerThread.daemon = True # Run the thread in daemon mode. lAutocleanerThread.start() # Start the thread execution. if lL: lL.info("Autocleaner thread has been started") #Logging + # Set flag that orchestrator has been initialized + inGSettings["HiddenIsOrchestratorInitializedBool"] = True + # Orchestrator start activity if lL: lL.info("Orchestrator start activity run") #Logging for lActivityItem in gSettingsDict["OrchestratorStart"]["ActivityList"]: @@ -2245,70 +2815,102 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lProcessorMonitorThread.start() # Start the thread execution. if lL: lL.info("Processor monitor has been started") #Logging - if lL: lL.info("Scheduler loop start") #Logging - gDaemonActivityLogDictRefreshSecInt = 10 # The second period for clear lDaemonActivityLogDict from old items - gDaemonActivityLogDictLastTime = time.time() # The second perioad for clean lDaemonActivityLogDict from old items - - # Set flag that orchestrator has been initialized - inGSettings["HiddenIsOrchestratorInitializedBool"] = True + # Scheduler loop + lSchedulerThread = threading.Thread(target= __deprecated_orchestrator_loop__) + lSchedulerThread.daemon = True # Run the thread in daemon mode. + lSchedulerThread.start() # Start the thread execution. + if lL: lL.info("Scheduler (old) loop start") #Logging + + # Schedule (new) loop + lScheduleThread = threading.Thread(target= __schedule_loop__) + lScheduleThread.daemon = True # Run the thread in daemon mode. + lScheduleThread.start() # Start the thread execution. + if lL: lL.info("Schedule module (new) loop start") #Logging + + # Restore state for process + for lProcessKeyTuple in inGSettings["ManagersProcessDict"]: + lProcess = inGSettings["ManagersProcessDict"][lProcessKeyTuple] + lProcess.StatusCheckIntervalRestore() + lThread = threading.Thread(target= lProcess.StatusRestore) + lThread.start() - while True: - try: - lCurrentDateTime = datetime.datetime.now() - #Циклический обход правил - lFlagSearchActivityType=True - # Periodically clear the lDaemonActivityLogDict - if time.time()-gDaemonActivityLogDictLastTime>=gDaemonActivityLogDictRefreshSecInt: - gDaemonActivityLogDictLastTime = time.time() # Update the time - for lIndex, lItem in enumerate(lDaemonActivityLogDict): - if lItem["ActivityEndDateTime"] and lCurrentDateTime<=lItem["ActivityEndDateTime"]: - pass - # Activity is actual - do not delete now - else: - # remove the activity - not actual - lDaemonActivityLogDict.pop(lIndex,None) - lIterationLastDateTime = lDaemonLastDateTime # Get current datetime before iterator (need for iterate all activities in loop) - # Iterate throught the activity list - for lIndex, lItem in enumerate(gSettingsDict["SchedulerDict"]["ActivityTimeList"]): - try: - # Prepare GUID of the activity - lGUID = None - if "GUID" in lItem and lItem["GUID"]: - lGUID = lItem["GUID"] - else: - lGUID = str(uuid.uuid4()) - lItem["GUID"]=lGUID - - #Проверка дней недели, в рамках которых можно запускать активность - lItemWeekdayList=lItem.get("WeekdayList", [0, 1, 2, 3, 4, 5, 6]) - if lCurrentDateTime.weekday() in lItemWeekdayList: - if lFlagSearchActivityType: - ####################################################################### - #Branch 1 - if has TimeHH:MM - ####################################################################### - if "TimeHH:MMStr" in lItem: - #Вид активности - запуск процесса - #Сформировать временной штамп, относительно которого надо будет проверять время - #часовой пояс пока не учитываем - lActivityDateTime=datetime.datetime.strptime(lItem["TimeHH:MMStr"],"%H:%M") - lActivityDateTime=lActivityDateTime.replace(year=lCurrentDateTime.year,month=lCurrentDateTime.month,day=lCurrentDateTime.day) - #Убедиться в том, что время наступило - if ( - lActivityDateTime>=lDaemonLastDateTime and - lCurrentDateTime>=lActivityDateTime): - # Log info about activity - if lL: lL.info(f"Scheduler:: Activity list is started in new thread. Parameters are not available to see.") #Logging - # Do the activity - lThread = threading.Thread(target=Processor.ActivityListExecute, kwargs={"inGSettings": inGSettings, "inActivityList":lItem["ActivityList"]}) - lThread.start() - lIterationLastDateTime = datetime.datetime.now() # Set the new datetime for the new processor activity - except Exception as e: - if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module when activity time item was initialising. ActivityTimeItem is {lItem}") - lDaemonLastDateTime = lIterationLastDateTime # Set the new datetime for the new processor activity - #Уснуть до следующего прогона - time.sleep(lDaemonLoopSeconds) - except Exception as e: - if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module. Global error") +def __schedule_loop__(): + while True: + schedule.run_pending() + time.sleep(3) + +# Backward compatibility below to 1.2.7 +def __deprecated_orchestrator_loop__(): + lL = OrchestratorLoggerGet() + inGSettings = GSettingsGet() + lDaemonLoopSeconds = gSettingsDict["SchedulerDict"]["CheckIntervalSecFloat"] + lDaemonActivityLogDict = {} # Словарь отработанных активностей, ключ - кортеж (, , , ) + lDaemonLastDateTime = datetime.datetime.now() + gDaemonActivityLogDictRefreshSecInt = 10 # The second period for clear lDaemonActivityLogDict from old items + gDaemonActivityLogDictLastTime = time.time() # The second perioad for clean lDaemonActivityLogDict from old items + while True: + try: + lCurrentDateTime = datetime.datetime.now() + # Циклический обход правил + lFlagSearchActivityType = True + # Periodically clear the lDaemonActivityLogDict + if time.time() - gDaemonActivityLogDictLastTime >= gDaemonActivityLogDictRefreshSecInt: + gDaemonActivityLogDictLastTime = time.time() # Update the time + for lIndex, lItem in enumerate(lDaemonActivityLogDict): + if lItem["ActivityEndDateTime"] and lCurrentDateTime <= lItem["ActivityEndDateTime"]: + pass + # Activity is actual - do not delete now + else: + # remove the activity - not actual + lDaemonActivityLogDict.pop(lIndex, None) + lIterationLastDateTime = lDaemonLastDateTime # Get current datetime before iterator (need for iterate all activities in loop) + # Iterate throught the activity list + for lIndex, lItem in enumerate(gSettingsDict["SchedulerDict"]["ActivityTimeList"]): + try: + # Prepare GUID of the activity + lGUID = None + if "GUID" in lItem and lItem["GUID"]: + lGUID = lItem["GUID"] + else: + lGUID = str(uuid.uuid4()) + lItem["GUID"] = lGUID + + # Проверка дней недели, в рамках которых можно запускать активность + lItemWeekdayList = lItem.get("WeekdayList", [0, 1, 2, 3, 4, 5, 6]) + if lCurrentDateTime.weekday() in lItemWeekdayList: + if lFlagSearchActivityType: + ####################################################################### + # Branch 1 - if has TimeHH:MM + ####################################################################### + if "TimeHH:MMStr" in lItem: + # Вид активности - запуск процесса + # Сформировать временной штамп, относительно которого надо будет проверять время + # часовой пояс пока не учитываем + lActivityDateTime = datetime.datetime.strptime(lItem["TimeHH:MMStr"], "%H:%M") + lActivityDateTime = lActivityDateTime.replace(year=lCurrentDateTime.year, + month=lCurrentDateTime.month, + day=lCurrentDateTime.day) + # Убедиться в том, что время наступило + if ( + lActivityDateTime >= lDaemonLastDateTime and + lCurrentDateTime >= lActivityDateTime): + # Log info about activity + if lL: lL.info( + f"Scheduler:: Activity list is started in new thread. Parameters are not available to see.") # Logging + # Do the activity + lThread = threading.Thread(target=Processor.ActivityListExecute, + kwargs={"inGSettings": inGSettings, + "inActivityList": lItem["ActivityList"]}) + lThread.start() + lIterationLastDateTime = datetime.datetime.now() # Set the new datetime for the new processor activity + except Exception as e: + if lL: lL.exception( + f"Scheduler: Exception has been catched in Scheduler module when activity time item was initialising. ActivityTimeItem is {lItem}") + lDaemonLastDateTime = lIterationLastDateTime # Set the new datetime for the new processor activity + # Уснуть до следующего прогона + time.sleep(lDaemonLoopSeconds) + except Exception as e: + if lL: lL.exception(f"Scheduler: Exception has been catched in Scheduler module. Global error") # Backward compatibility below to 1.2.0 def __deprecated_orchestrator_start__(): diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py index 897e7782..8a3500e4 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Orchestrator/__init__.py @@ -5,5 +5,6 @@ The pyOpenRPA package (from UnicodeLabs) """ from .Web import Basic from .__Orchestrator__ import * +from . import Managers __all__ = [] __author__ = 'Ivan Maslov ' \ No newline at end of file diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/Terminator.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/StopSafe.py similarity index 67% rename from Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/Terminator.py rename to Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/StopSafe.py index 4ebe5929..8b350cf4 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/Terminator.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/Tools/StopSafe.py @@ -1,8 +1,10 @@ +""" # How to use -# from pyOpenRPA.Tools import Terminator -# Terminator.Init(inLogger=None) -# Terminator.IsSignalClose() # True - WM_CLOSE SIGNAL has come -# Terminator.SessionLogoff() # Logoff the session +# from pyOpenRPA.Tools import StopSafe +# StopSafe.Init(inLogger=None) +# StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe +""" + import win32con import win32gui @@ -12,33 +14,35 @@ gLogger = None gWindowTitleStr = "PythonTerminator" # Title of the phantom window gWindowDescriptionStr = "pyOpenRPA library for safe turn off the program (by send the WM_CLOSE signal from task kill)" # Description of the phantom window -# Init the terminator def Init(inLogger=None): + """ + Init the StopSafe module. After that you can use def IsStopSafe() to check if close signal has come. + + :param inLogger: Logger to log messages about StopSafe + :return: + """ global gLogger global gIsSignalCloseBool gIsSignalCloseBool = False # Init default gLogger = inLogger - #import sys - #import time - #import atexit import threading - #atexit.register(print, 'PYTHON SPAM APP: SHUTDOWN') - shutdown_thread = threading.Thread(target=shutdown_monitor) + if gLogger: gLogger.info(f"StopSafe: Init termination catch thread") + shutdown_thread = threading.Thread(target=_shutdown_monitor) shutdown_thread.start() #shutdown_thread.join() #shutdown_monitor() -# Terminator.IsSignalClose() # True - WM_CLOSE SIGNAL has come -def IsSignalClose(): + +def IsStopSafe(): + """ + Check if stop signal has come. + + :return: + """ global gIsSignalCloseBool # Init the global variable return gIsSignalCloseBool # Return the result -# Terminator.SessionLogoff() # Logoff the session -def SessionLogoff(): - os.system("shutdown /l") - -# Technical function -def shutdown_monitor(): +def _shutdown_monitor(): global gIsSignalCloseBool # Init the global variable global gLogger def wndproc(hwnd, msg, wparam, lparam): @@ -58,5 +62,5 @@ def shutdown_monitor(): win32gui.PumpMessages() gIsSignalCloseBool = True # WM_CLOSE message has come if gLogger: - gLogger.info(f"Terminator: Program has recieve the close signal - safe exit") + gLogger.info(f"StopSafe: Program has catch VM_CLOSE signal - do safe exit") diff --git a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/__init__.py b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/__init__.py index 8167039a..bcb3231f 100644 --- a/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/__init__.py +++ b/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/pyOpenRPA/__init__.py @@ -3,7 +3,7 @@ r""" The OpenRPA package (from UnicodeLabs) """ -__version__ = 'v1.2.6' +__version__ = 'v1.2.7' __all__ = [] __author__ = 'Ivan Maslov ' #from .Core import Robot \ No newline at end of file diff --git a/Sources/GuideSphinx/03_Copyrights_Contacts.rst b/Sources/GuideSphinx/03_Copyrights_Contacts.rst index 85f88700..b02d3ee3 100644 --- a/Sources/GuideSphinx/03_Copyrights_Contacts.rst +++ b/Sources/GuideSphinx/03_Copyrights_Contacts.rst @@ -5,7 +5,7 @@ #################################### pyOpenRPA is created by Ivan Maslov (Russia). -Use it absolutely for free! +Hosted by PYOPENRPA LLC (Russia) My purpose is to create #IT4Business models in the companies. I can help you to create the new #IT4Business in your company. diff --git a/Sources/GuideSphinx/index.rst b/Sources/GuideSphinx/index.rst index 01ad1aae..884bcbac 100644 --- a/Sources/GuideSphinx/index.rst +++ b/Sources/GuideSphinx/index.rst @@ -14,25 +14,19 @@ Welcome to pyOpenRPA's wiki ! ATTENTION ! pyOpenRPA works only on MS Windows 7+/Server 2008+. Guys from Unix/Mac - sorry. We will come to you soon :) -************************************************** -Donate -************************************************** - -pyOpenRPA is absolutely non-commercial project. - -Please donate some $ if pyOpenRPA project is actual for you. Link to online donations. -https://yoomoney.ru/to/4100115560661986 ************************************************** About ************************************************** Dear RPA-tors. Let me congratulate you with great change in the RPA world. Since 2019 the first enterprise level open source RPA platform is here! +pyOpenRPA is absolutely open source commercial project. Hosted by LLC PYOPENRPA (RUSSIA) + The pyOpenRPA - free, fast and reliable Powerful OpenSource RPA tool for business (based on python 3). Best performance and absolutely free! The pyOpenRPA is based on Python and using well known OpenSource solutions such as Selenium, OpenCV, Win32, UI automation and others. Thanks to it we were able to create consolidated platform with all possible features. -The pyOpenRPA is distributed under the MIT license which allows you to use it in any way you want and any time you need without any restrictions. +The pyOpenRPA is distributed under the PYOPENRPA license. At the time of this writing the pyOpenRPA is successfully using in several big Russian companies. Companies in which it was decided to develop own RPA division with no dependencies on expensive licenses. ************************************************** diff --git a/Sources/pyOpenRPA/__init__.py b/Sources/pyOpenRPA/__init__.py index 8167039a..bcb3231f 100644 --- a/Sources/pyOpenRPA/__init__.py +++ b/Sources/pyOpenRPA/__init__.py @@ -3,7 +3,7 @@ r""" The OpenRPA package (from UnicodeLabs) """ -__version__ = 'v1.2.6' +__version__ = 'v1.2.7' __all__ = [] __author__ = 'Ivan Maslov ' #from .Core import Robot \ No newline at end of file diff --git a/Sources/setup.py b/Sources/setup.py index 0b2fc603..94f953e6 100644 --- a/Sources/setup.py +++ b/Sources/setup.py @@ -32,7 +32,9 @@ setup(name='pyOpenRPA', long_description_content_type='text/markdown', classifiers=[ 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: MIT License', + 'License :: Free For Educational Use', + 'License :: Free For Home Use', + 'License :: Free for non-commercial use', 'Intended Audience :: Developers', 'Environment :: Win32 (MS Windows)', 'Environment :: X11 Applications', @@ -44,11 +46,11 @@ setup(name='pyOpenRPA', 'Topic :: Software Development :: Quality Assurance', 'Topic :: Home Automation' ], - keywords='OpenRPA RPA Robot Automation Robotization OpenSource', - url='https://gitlab.com/UnicodeLabs/OpenRPA', + keywords='pyOpenRPA OpenRPA RPA Robot Automation Robotization OpenSource IT4Business', + url='https://pyopenrpa.ru/', author='Ivan Maslov', - author_email='Ivan.Maslov@unicodelabs.ru', - license='MIT', + author_email='Ivan.Maslov@pyopenrpa.ru', + license='PYOPENRPA', packages=find_packages(), install_requires=[ 'pywinauto>=0.6.8;platform_system=="win32" and python_version>="3.0"', diff --git a/Wiki/ENG_Guide/html/03_Copyrights_Contacts.html b/Wiki/ENG_Guide/html/03_Copyrights_Contacts.html index ad8f7daf..4242e83a 100644 --- a/Wiki/ENG_Guide/html/03_Copyrights_Contacts.html +++ b/Wiki/ENG_Guide/html/03_Copyrights_Contacts.html @@ -194,7 +194,7 @@

3. Copyrights & Contacts

pyOpenRPA is created by Ivan Maslov (Russia). -Use it absolutely for free!

+Hosted by PYOPENRPA LLC (Russia)

My purpose is to create #IT4Business models in the companies. I can help you to create the new #IT4Business in your company. #IT4Business homepage - https://www.facebook.com/RU.IT4Business diff --git a/Wiki/ENG_Guide/html/Agent/02_Defs.html b/Wiki/ENG_Guide/html/Agent/02_Defs.html index 6cdedd5e..4c51cf28 100644 --- a/Wiki/ENG_Guide/html/Agent/02_Defs.html +++ b/Wiki/ENG_Guide/html/Agent/02_Defs.html @@ -214,20 +214,23 @@

OSFileBinaryDataBase64StrReceive(inFilePathStr)

Read binary file and encode in base64 to transmit (safe for JSON transmition)

-

OSFileTextDataStrCreate(inFilePathStr, …)

+

OSFileMTimeGet(inFilePathStr)

+

Read file modification time timestamp format (float)

+ +

OSFileTextDataStrCreate(inFilePathStr, …)

Create text file in the agent GUI session

-

OSFileTextDataStrReceive(inFilePathStr[, …])

+

OSFileTextDataStrReceive(inFilePathStr[, …])

Read text file in the agent GUI session

-

ProcessWOExeUpperUserListGet()

+

ProcessWOExeUpperUserListGet()

Return the process list only for the current user (where Agent is running) without .EXE in upper case.

-pyOpenRPA.Agent.__Agent__.OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings=None, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr='cp1251')[source]
+pyOpenRPA.Agent.__Agent__.OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings=None, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr='cp1251', inCaptureBool=True)[source]

Execute CMD on the Agent daemonic process

Parameters
@@ -236,12 +239,14 @@
  • inRunAsyncBool – True - Agent processor don’t wait execution; False - Agent processor wait cmd execution

  • inGSettings – Agent global settings dict

  • inSendOutputToOrchestratorLogsBool – True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True

  • +
  • inCMDEncodingStr – Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is “cp1251” early was “cp866” - need test

  • +
  • inCaptureBool – !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent

  • +
    Returns
    +

    +
    -

    !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent -:param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is “cp1251” early was “cp866” - need test -:return:

    @@ -273,6 +278,20 @@
    +
    +
    +pyOpenRPA.Agent.__Agent__.OSFileMTimeGet(inFilePathStr: str) → float[source]
    +

    Read file modification time timestamp format (float)

    +
    +
    Parameters
    +

    inFilePathStr – File path to read

    +
    +
    Returns
    +

    timestamp (float) Return None if file is not exist

    +
    +
    +
    +
    pyOpenRPA.Agent.__Agent__.OSFileTextDataStrCreate(inFilePathStr, inFileDataStr, inEncodingStr='utf-8', inGSettings=None)[source]
    diff --git a/Wiki/ENG_Guide/html/Orchestrator/02_Defs.html b/Wiki/ENG_Guide/html/Orchestrator/02_Defs.html index e3639819..d58c7260 100644 --- a/Wiki/ENG_Guide/html/Orchestrator/02_Defs.html +++ b/Wiki/ENG_Guide/html/Orchestrator/02_Defs.html @@ -313,200 +313,212 @@

    AgentOSFileBinaryDataBytesCreate(…[, …])

    Create binary file by the base64 string by the pyOpenRPA.Agent daemon process (safe for JSON transmition)

    -

    AgentOSFileSend(inHostNameStr, inUserStr, …)

    +

    AgentOSFileBinaryDataReceive(inHostNameStr, …)

    +

    Read binary file from agent (synchronious)

    + +

    AgentOSFileSend(inHostNameStr, inUserStr, …)

    Send the file from the Orchestrator to Agent (synchroniously) pyOpenRPA.Agent daemon process (safe for JSON transmition).

    -

    AgentOSFileTextDataStrCreate(inHostNameStr, …)

    +

    AgentOSFileTextDataStrCreate(inHostNameStr, …)

    Create text file by the string by the pyOpenRPA.Agent daemon process

    -

    AgentOSFileTextDataStrReceive(inHostNameStr, …)

    +

    AgentOSFileTextDataStrReceive(inHostNameStr, …)

    Read text file in the agent GUI session

    -

    AgentOSLogoff(inHostNameStr, inUserStr)

    +

    AgentOSLogoff(inHostNameStr, inUserStr)

    Logoff the agent user session

    -

    AgentProcessWOExeUpperUserListGet(…[, …])

    +

    AgentProcessWOExeUpperUserListGet(…[, …])

    Return the process list only for the current user (where Agent is running) without .EXE in upper case.

    -

    GSettingsAutocleaner([inGSettings])

    +

    GSettingsAutocleaner([inGSettings])

    HIDDEN Interval gSettings auto cleaner def to clear some garbage.

    -

    GSettingsGet([inGSettings])

    +

    GSettingsGet([inGSettings])

    Get the GSettings from the singleton module.

    -

    GSettingsKeyListValueAppend(inValue[, …])

    +

    GSettingsKeyListValueAppend(inValue[, …])

    Append value in GSettings by the key list

    -

    GSettingsKeyListValueGet([inKeyList, …])

    +

    GSettingsKeyListValueGet([inKeyList, …])

    Get the value from the GSettings by the key list

    -

    GSettingsKeyListValueOperatorPlus(inValue[, …])

    +

    GSettingsKeyListValueOperatorPlus(inValue[, …])

    Execute plus operation between 2 lists (1:inValue and 2:gSettings by the inKeyList)

    -

    GSettingsKeyListValueSet(inValue[, …])

    +

    GSettingsKeyListValueSet(inValue[, …])

    Set value in GSettings by the key list

    -

    OSCMD(inCMDStr[, inRunAsyncBool, inLogger])

    +

    OSCMD(inCMDStr[, inRunAsyncBool, inLogger])

    OS send command in shell locally

    -

    OSCredentialsVerify(inUserStr, inPasswordStr)

    +

    OSCredentialsVerify(inUserStr, inPasswordStr)

    Verify user credentials in windows.

    -

    OSLogoff()

    +

    OSLogoff()

    Logoff the current orchestrator session :return:

    -

    OSRemotePCRestart(inHostStr[, inForceBool, …])

    +

    OSRemotePCRestart(inHostStr[, inForceBool, …])

    Send signal via power shell to restart remote PC ATTENTION: Orchestrator user need to have restart right on the Remote machine to restart PC.

    -

    Orchestrator([inGSettings, …])

    +

    Orchestrator([inGSettings, …])

    Main def to start orchestrator

    +

    OrchestratorInitWait()

    +

    Wait thread while orc will process initial action.

    +

    OrchestratorIsAdmin()

    Check if Orchestrator process is running as administrator

    -

    OrchestratorLoggerGet()

    +

    OrchestratorIsInited()

    +

    Check if Orchestrator initial actions were processed

    + +

    OrchestratorLoggerGet()

    Get the logger from the Orchestrator

    -

    OrchestratorPySearchInit(inGlobPatternStr[, …])

    +

    OrchestratorPySearchInit(inGlobPatternStr[, …])

    Search the py files by the glob and do the safe init (in try except).

    -

    OrchestratorRerunAsAdmin()

    +

    OrchestratorRerunAsAdmin()

    Check if not admin - then rerun orchestrator as administrator

    -

    OrchestratorRestart([inGSettings])

    +

    OrchestratorRestart([inGSettings])

    Orchestrator restart

    -

    OrchestratorScheduleGet()

    +

    OrchestratorScheduleGet()

    Get the schedule (schedule.readthedocs.io) from the Orchestrator

    -

    OrchestratorSessionRestore([inGSettings])

    -

    Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working directory. if exist - load into gsettings # _SessionLast_StorageDict.pickle (binary) _SessionLast_RDPList.json (encoding = “utf-8”) _SessionLast_StorageDict.pickle (binary).

    +

    OrchestratorSessionRestore([inGSettings])

    +

    Check _SessioLast… files in working directory. if exist - load into gsettings (from version 1.2.7) _SessionLast_GSettings.pickle (binary).

    -

    OrchestratorSessionSave([inGSettings])

    -

    Orchestrator session save in file

    +

    OrchestratorSessionSave([inGSettings])

    +

    Orchestrator session save in file (from version 1.2.7) _SessionLast_GSettings.pickle (binary)

    -

    OrchestratorThreadStart(inDef, *inArgList, …)

    +

    OrchestratorThreadStart(inDef, *inArgList, …)

    Execute def in new thread and pass some args with list and dict types

    -

    ProcessDefIntervalCall(inDef, inIntervalSecFloat)

    +

    ProcessDefIntervalCall(inDef, inIntervalSecFloat)

    Use this procedure if you need to run periodically some def.

    -

    ProcessIsStarted(inProcessNameWOExeStr)

    +

    ProcessIsStarted(inProcessNameWOExeStr)

    Check if there is any running process that contains the given name processName.

    -

    ProcessListGet([inProcessNameWOExeList])

    +

    ProcessListGet([inProcessNameWOExeList])

    Return process list on the orchestrator machine sorted by Memory Usage.

    -

    ProcessStart(inPathStr, inArgList[, …])

    +

    ProcessStart(inPathStr, inArgList[, …])

    Start process locally.

    -

    ProcessStop(inProcessNameWOExeStr, …[, …])

    +

    ProcessStop(inProcessNameWOExeStr, …[, …])

    Stop process on the orchestrator machine.

    -

    ProcessorActivityItemAppend([inGSettings, …])

    +

    ProcessorActivityItemAppend([inGSettings, …])

    Create and add activity item in processor queue.

    -

    ProcessorActivityItemCreate(inDef[, …])

    +

    ProcessorActivityItemCreate(inDef[, …])

    Create activity item.

    -

    ProcessorAliasDefCreate(inDef[, inAliasStr, …])

    +

    ProcessorAliasDefCreate(inDef[, inAliasStr, …])

    Create alias for def (can be used in ActivityItem in field Def) !WHEN DEF ALIAS IS REQUIRED! - Def alias is required when you try to call Python def from the Orchestrator WEB side (because you can’t transmit Python def object out of the Python environment) Deprecated.

    -

    ProcessorAliasDefUpdate(inDef, inAliasStr[, …])

    +

    ProcessorAliasDefUpdate(inDef, inAliasStr[, …])

    Update alias for def (can be used in ActivityItem in field Def).

    -

    PythonStart(inModulePathStr, inDefNameStr[, …])

    +

    PythonStart(inModulePathStr, inDefNameStr[, …])

    Import module and run def in the Orchestrator process.

    -

    RDPSessionCMDRun(inRDPSessionKeyStr, inCMDStr)

    +

    RDPSessionCMDRun(inRDPSessionKeyStr, inCMDStr)

    Send CMD command to the RDP session “RUN” window

    -

    RDPSessionConnect(inRDPSessionKeyStr[, …])

    +

    RDPSessionConnect(inRDPSessionKeyStr[, …])

    Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if RDP key is already exists

    -

    RDPSessionDisconnect(inRDPSessionKeyStr[, …])

    +

    RDPSessionDisconnect(inRDPSessionKeyStr[, …])

    Disconnect the RDP session and stop monitoring it.

    -

    RDPSessionDublicatesResolve(inGSettings)

    +

    RDPSessionDublicatesResolve(inGSettings)

    DEVELOPING Search duplicates in GSettings RDPlist !def is developing!

    -

    RDPSessionFileStoredRecieve(…[, inGSettings])

    +

    RDPSessionFileStoredRecieve(…[, inGSettings])

    Recieve file from RDP session to the Orchestrator session using shared drive in RDP (see RDP Configuration Dict, Shared drive)

    -

    RDPSessionFileStoredSend(inRDPSessionKeyStr, …)

    +

    RDPSessionFileStoredSend(inRDPSessionKeyStr, …)

    Send file from Orchestrator session to the RDP session using shared drive in RDP (see RDP Configuration Dict, Shared drive)

    -

    RDPSessionLogoff(inRDPSessionKeyStr[, …])

    +

    RDPSessionLogoff(inRDPSessionKeyStr[, …])

    Logoff the RDP session from the Orchestrator process (close all apps in session when logoff)

    -

    RDPSessionMonitorStop(inRDPSessionKeyStr[, …])

    +

    RDPSessionMonitorStop(inRDPSessionKeyStr[, …])

    Stop monitoring the RDP session by the Orchestrator process.

    -

    RDPSessionProcessStartIfNotRunning(…[, …])

    +

    RDPSessionProcessStartIfNotRunning(…[, …])

    Start process in RDP if it is not running (check by the arg inProcessNameWEXEStr)

    -

    RDPSessionProcessStop(inRDPSessionKeyStr, …)

    +

    RDPSessionProcessStop(inRDPSessionKeyStr, …)

    Send CMD command to the RDP session “RUN” window.

    -

    RDPSessionReconnect(inRDPSessionKeyStr[, …])

    +

    RDPSessionReconnect(inRDPSessionKeyStr[, …])

    Reconnect the RDP session

    -

    RDPSessionResponsibilityCheck(inRDPSessionKeyStr)

    +

    RDPSessionResponsibilityCheck(inRDPSessionKeyStr)

    DEVELOPING, MAYBE NOT USEFUL Check RDP Session responsibility TODO NEED DEV + TEST

    -

    RDPTemplateCreate(inLoginStr, inPasswordStr)

    +

    RDPTemplateCreate(inLoginStr, inPasswordStr)

    Create RDP connect dict item/ Use it connect/reconnect (Orchestrator.RDPSessionConnect)

    -

    SchedulerActivityTimeAddWeekly([…])

    +

    SchedulerActivityTimeAddWeekly([…])

    Add activity item list in scheduler.

    -

    Start([inDumpRestoreBool, …])

    +

    Start([inDumpRestoreBool, …])

    Start the orchestrator threads execution

    -

    StorageRobotExists(inRobotNameStr)

    +

    StorageRobotExists(inRobotNameStr)

    Check if robot storage exists

    -

    StorageRobotGet(inRobotNameStr)

    +

    StorageRobotGet(inRobotNameStr)

    Get the robot storage by the robot name.

    -

    UACKeyListCheck(inRequest, inRoleKeyList)

    +

    UACKeyListCheck(inRequest, inRoleKeyList)

    Check is client is has access for the key list

    -

    UACSuperTokenUpdate(inSuperTokenStr[, …])

    +

    UACSuperTokenUpdate(inSuperTokenStr[, …])

    Add supertoken for the all access (it is need for the robot communication without human)

    -

    UACUpdate(inADLoginStr[, inADStr, …])

    +

    UACUpdate(inADLoginStr[, inADStr, …])

    Update user access (UAC)

    -

    UACUserDictGet(inRequest)

    +

    UACUserDictGet(inRequest)

    Return user UAC hierarchy dict of the inRequest object.

    -

    WebAuditMessageCreate(inRequest[, …])

    +

    WebAuditMessageCreate([inRequest, …])

    Create message string with request user details (IP, Login etc…).

    -

    WebCPUpdate(inCPKeyStr[, inHTMLRenderDef, …])

    +

    WebCPUpdate(inCPKeyStr[, inHTMLRenderDef, …])

    Add control panel HTML, JSON generator or JS when page init

    -

    WebListenCreate([inServerKeyStr, …])

    +

    WebListenCreate([inServerKeyStr, …])

    Create listen interface for the web server

    -

    WebRequestParseBodyBytes(inRequest)

    +

    WebRequestGet()

    +

    Return the web request instance if current thread was created by web request from client.

    + +

    WebRequestParseBodyBytes([inRequest])

    Extract the body in bytes from the request

    -

    WebRequestParseBodyJSON(inRequest)

    +

    WebRequestParseBodyJSON([inRequest])

    Extract the body in dict/list from the request

    -

    WebRequestParseBodyStr(inRequest)

    +

    WebRequestParseBodyStr([inRequest])

    Extract the body in str from the request

    -

    WebRequestParseFile(inRequest)

    +

    WebRequestParseFile([inRequest])

    Parse the request - extract the file (name, body in bytes)

    -

    WebRequestParsePath(inRequest)

    +

    WebRequestParsePath([inRequest])

    Parse the request - extract the url.

    -

    WebRequestResponseSend(inRequest, inResponeStr)

    -

    Send response for the request :return:

    +

    WebRequestResponseSend(inResponeStr[, inRequest])

    +

    Send response for the request

    WebURLConnectDef(inMethodStr, inURLStr, …)

    Connect URL to DEF

    @@ -517,13 +529,13 @@

    WebURLConnectFolder(inMethodStr, inURLStr, …)

    Connect URL to Folder

    -

    WebUserInfoGet(inRequest)

    +

    WebUserInfoGet([inRequest])

    Return User info about request

    -

    WebUserIsSuperToken(inRequest[, inGSettings])

    +

    WebUserIsSuperToken([inRequest, inGSettings])

    Return bool if request is authentificated with supetoken (token which is never expires)

    -

    WebUserUACHierarchyGet(inRequest)

    +

    WebUserUACHierarchyGet([inRequest])

    Return User UAC Hierarchy DICT Return {…}

    @@ -771,7 +783,7 @@
    -pyOpenRPA.Orchestrator.__Orchestrator__.AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr='cp1251', inGSettings=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr='cp1251', inGSettings=None, inCaptureBool=True)[source]

    Send CMD to OS thought the pyOpenRPA.Agent daemon. Result return to log + Orchestrator by the A2O connection

    Parameters
    @@ -783,6 +795,7 @@
  • inRunAsyncBool – True - Agent processor don’t wait execution; False - Agent processor wait cmd execution

  • inSendOutputToOrchestratorLogsBool – True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True

  • inCMDEncodingStr – Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is “cp1251” early was “cp866” - need test

  • +
  • inCaptureBool – !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent

  • Returns
    @@ -870,6 +883,25 @@
    +
    +
    +pyOpenRPA.Orchestrator.__Orchestrator__.AgentOSFileBinaryDataReceive(inHostNameStr, inUserStr, inFilePathStr)[source]
    +

    Read binary file from agent (synchronious)

    +
    +
    Parameters
    +
      +
    • inGSettings – Global settings dict (singleton)

    • +
    • inHostNameStr

    • +
    • inUserStr

    • +
    • inFilePathStr – File path to read

    • +
    +
    +
    Returns
    +

    file data bytes

    +
    +
    +
    +
    pyOpenRPA.Orchestrator.__Orchestrator__.AgentOSFileSend(inHostNameStr, inUserStr, inOrchestratorFilePathStr, inAgentFilePathStr, inGSettings=None)[source]
    @@ -1182,6 +1214,13 @@ ATTENTION: Orchestrator user need to have restart right on the Remote machine to
    +
    +
    +pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorInitWait() → None[source]
    +

    Wait thread while orc will process initial action. +ATTENTION: DO NOT CALL THIS DEF IN THREAD WHERE ORCHESTRATOR MUST BE INITIALIZED - INFINITE LOOP

    +
    +
    pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorIsAdmin()[source]
    @@ -1193,9 +1232,23 @@ ATTENTION: Orchestrator user need to have restart right on the Remote machine to
    +
    +
    +pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorIsInited() → bool[source]
    +

    Check if Orchestrator initial actions were processed

    +
    +
    Returns
    +

    True - orc is already inited; False - else

    +
    +
    Return type
    +

    bool

    +
    +
    +
    +
    -pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorLoggerGet()[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorLoggerGet() → logging.Logger[source]

    Get the logger from the Orchestrator

    Returns
    @@ -1206,32 +1259,37 @@ ATTENTION: Orchestrator user need to have restart right on the Remote machine to
    -pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorPySearchInit(inGlobPatternStr, inDefStr=None, inDefArgNameGSettingsStr=None)[source]
    -

    Search the py files by the glob and do the safe init (in try except). Also add inited module in sys.modules as imported (module name = file name without extension).

    -
    # USAGE VAR 1 (without the def  auto call)
    -# Autoinit control panels starts with CP_
    -Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\CP_*.py")
    -
    -# USAGE VAR 2 (with the def auto call) - for the backward compatibility CP for the Orchestrator ver. < 1.2.7
    -# Autoinit control panels starts with CP_
    -Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings")
    -
    -# INFO: The code above will replace the code below
    -## !!! For Relative import !!! CP Version Check
    -try:
    -    sys.path.insert(0,os.path.abspath(os.path.join(r"")))
    -    from ControlPanel import CP_VersionCheck
    -    CP_VersionCheck.SettingsUpdate(inGSettings=gSettings)
    -except Exception as e:
    -    gSettings["Logger"].exception(f"Exception when init CP. See below.")
    -
    -
    +pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorPySearchInit(inGlobPatternStr, inDefStr=None, inDefArgNameGSettingsStr=None, inAsyncInitBool=False)[source] +

    Search the py files by the glob and do the safe init (in try except). Also add inited module in sys.modules as imported (module name = file name without extension). +You can init CP in async way! +.. code-block:: python

    +
    +

    # USAGE VAR 1 (without the def auto call) +# Autoinit control panels starts with CP_ +Orchestrator.OrchestratorPySearchInit(inGlobPatternStr=”ControlPanelCP_*.py”)

    +

    # USAGE VAR 2 (with the def auto call) - for the backward compatibility CP for the Orchestrator ver. < 1.2.7 +# Autoinit control panels starts with CP_ +Orchestrator.OrchestratorPySearchInit(inGlobPatternStr=”ControlPanelCP_*.py”, inDefStr=”SettingsUpdate”, inDefArgNameGSettingsStr=”inGSettings”)

    +

    # INFO: The code above will replace the code below +## !!! For Relative import !!! CP Version Check +try:

    +
    +

    sys.path.insert(0,os.path.abspath(os.path.join(r””))) +from ControlPanel import CP_VersionCheck +CP_VersionCheck.SettingsUpdate(inGSettings=gSettings)

    +
    +
    +
    except Exception as e:

    gSettings[“Logger”].exception(f”Exception when init CP. See below.”)

    +
    +
    +
    Parameters
    • inGlobPatternStr – example”..***X64*.cmd”

    • inDefStr – OPTIONAL The string name of the def. For backward compatibility if you need to auto call some def from initialized module

    • inDefArgNameGSettingsStr – OPTIONAL The name of the GSettings argument in def (if exists)

    • +
    • inAsyncInitBool – OPTIONAL True - init py modules in many threads - parallel execution. False (default) - sequence execution

    Returns
    @@ -1277,12 +1335,16 @@ ATTENTION: Orchestrator user need to have restart right on the Remote machine to
    pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorSessionRestore(inGSettings=None)[source]
    -

    Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working directory. if exist - load into gsettings -# _SessionLast_StorageDict.pickle (binary)

    +

    Check _SessioLast… files in working directory. if exist - load into gsettings +(from version 1.2.7)

    -

    _SessionLast_RDPList.json (encoding = “utf-8”) -_SessionLast_StorageDict.pickle (binary)

    +

    _SessionLast_GSettings.pickle (binary)

    +
    +
    (above the version 1.2.7)

    _SessionLast_RDPList.json (encoding = “utf-8”) +_SessionLast_StorageDict.pickle (binary)

    +
    +
    Parameters

    inGSettings – Global settings dict (singleton)

    @@ -1296,8 +1358,13 @@ _SessionLast_StorageDict.pickle (binary)

    pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorSessionSave(inGSettings=None)[source]
    -
    -
    Orchestrator session save in file

    _SessionLast_RDPList.json (encoding = “utf-8”) +

    Orchestrator session save in file +(from version 1.2.7)

    +
    +

    _SessionLast_GSettings.pickle (binary)

    +
    +
    +
    (above the version 1.2.7)

    _SessionLast_RDPList.json (encoding = “utf-8”) _SessionLast_StorageDict.pickle (binary)

    @@ -1737,7 +1804,7 @@ Deprecated. See ActivityItemDefAliasUpdate

    -pyOpenRPA.Orchestrator.__Orchestrator__.RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings=None, inRedirectClipboardBool=True)[source]
    Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if RDP key is already exists

    2 way of the use

    @@ -2047,7 +2114,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr='127.0.0.1', inPortInt=3389, inWidthPXInt=1680, inHeightPXInt=1050, inUseBothMonitorBool=False, inDepthBitInt=32, inSharedDriveList=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr='127.0.0.1', inPortInt=3389, inWidthPXInt=1680, inHeightPXInt=1050, inUseBothMonitorBool=False, inDepthBitInt=32, inSharedDriveList=None, inRedirectClipboardBool=True)[source]

    Create RDP connect dict item/ Use it connect/reconnect (Orchestrator.RDPSessionConnect)

    # USAGE
     from pyOpenRPA import Orchestrator
    @@ -2066,6 +2133,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
     #         "Host": "127.0.0.1", "Port": "3389", "Login": "USER_99", "Password": "USER_PASS_HERE",
     #         "Screen": { "Width": 1680, "Height": 1050, "FlagUseAllMonitors": False, "DepthBit": "32" },
     #         "SharedDriveList": ["c"],
    +#         "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else
     #         ###### Will updated in program ############
     #         "SessionHex": "77777sdfsdf77777dsfdfsf77777777",  # Hex is created when robot runs, example ""
     #         "SessionIsWindowExistBool": False, "SessionIsWindowResponsibleBool": False, "SessionIsIgnoredBool": False
    @@ -2084,6 +2152,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
     
  • inUseBothMonitorBool – True - connect to the RDP with both monitors. False - else case

  • inDepthBitInt – Remote desktop bitness. Available: 32 or 24 or 16 or 15, example 32

  • inSharedDriveList – Host local disc to connect to the RDP session. Example: [“c”, “d”]

  • +
  • inRedirectClipboardBool – # True - share clipboard to RDP; False - else

  • Returns
    @@ -2102,6 +2171,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo

    }, “SharedDriveList”: inSharedDriveList, # List of the Root sesion hard drives, example [“c”] +“RedirectClipboardBool”: True, # True - share clipboard to RDP; False - else ###### Will updated in program ############ “SessionHex”: “77777sdfsdf77777dsfdfsf77777777”, # Hex is created when robot runs, example “” “SessionIsWindowExistBool”: False, @@ -2266,7 +2336,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo

    -pyOpenRPA.Orchestrator.__Orchestrator__.WebAuditMessageCreate(inRequest, inOperationCodeStr='-', inMessageStr='-')[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebAuditMessageCreate(inRequest=None, inOperationCodeStr='-', inMessageStr='-')[source]

    Create message string with request user details (IP, Login etc…). Very actual for IT security in big company.

    # USAGE
     from pyOpenRPA import Orchestrator
    @@ -2283,7 +2353,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
     
    Parameters
      -
    • inRequest – HTTP request handler

    • +
    • inRequest – HTTP request handler. Optional if call def from request thread

    • inOperationCodeStr – operation code in string format (actual for IT audit in control panels)

    • inMessageStr – additional message after

    @@ -2331,13 +2401,19 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    +
    +
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestGet()[source]
    +

    Return the web request instance if current thread was created by web request from client. else return None

    +
    +
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyBytes(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyBytes(inRequest=None)[source]

    Extract the body in bytes from the request

    Parameters
    -

    inRequest – inRequest from the server

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    Bytes or None

    @@ -2347,11 +2423,11 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyJSON(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyJSON(inRequest=None)[source]

    Extract the body in dict/list from the request

    Parameters
    -

    inRequest

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    dict or list

    @@ -2361,11 +2437,11 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyStr(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyStr(inRequest=None)[source]

    Extract the body in str from the request

    Parameters
    -

    inRequest – inRequest from the server

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    str or None

    @@ -2375,11 +2451,11 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseFile(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseFile(inRequest=None)[source]

    Parse the request - extract the file (name, body in bytes)

    Parameters
    -

    inRequest

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    (FileNameStr, FileBodyBytes) or (None, None)

    @@ -2389,11 +2465,11 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParsePath(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParsePath(inRequest=None)[source]

    Parse the request - extract the url. Example: /pyOpenRPA/Debugging/DefHelper/…

    Parameters
    -

    inRequest

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    Str, Example: /pyOpenRPA/Debugging/DefHelper/…

    @@ -2403,14 +2479,21 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestResponseSend(inRequest, inResponeStr)[source]
    -

    Send response for the request -:return:

    +pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestResponseSend(inResponeStr, inRequest=None)[source] +

    Send response for the request

    +
    +
    Parameters
    +

    inRequest – inRequest from the server. Optional if call def from request thread

    +
    +
    Returns
    +

    +
    +
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr='application/octet-stream', inGSettings=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr='application/octet-stream', inGSettings=None, inUACBool=None)[source]
    Connect URL to DEF

    “inMethodStr”:”GET|POST”, @@ -2425,11 +2508,12 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo

    Parameters
    • inGSettings – Global settings dict (singleton)

    • -
    • inMethodStr

    • -
    • inURLStr

    • -
    • inMatchTypeStr

    • -
    • inDef

    • -
    • inContentTypeStr

    • +
    • inMethodStr – “GET|POST”,

    • +
    • inURLStr – example “/index”, #URL of the request

    • +
    • inMatchTypeStr – #”BeginWith|Contains|Equal|EqualCase”,

    • +
    • inDef – def arg allowed list: 2:[inRequest, inGSettings], 1: [inRequest], 0: []

    • +
    • inContentTypeStr – default: “application/octet-stream”

    • +
    • inUACBool – default: None; True - check user access before do this URL item. None - get Server flag if ask user

    @@ -2437,7 +2521,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr='application/octet-stream', inGSettings=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr='application/octet-stream', inGSettings=None, inUACBool=None)[source]
    Connect URL to File

    “inMethodStr”:”GET|POST”, “inURLStr”: “/index”, #URL of the request @@ -2454,6 +2538,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo

  • inMatchTypeStr

  • inFilePathStr

  • inContentTypeStr

  • +
  • inUACBool – default: None; True - check user access before do this URL item. None - get Server flag if ask user

  • @@ -2461,12 +2546,13 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings=None, inUACBool=None)[source]
    Connect URL to Folder

    “inMethodStr”:”GET|POST”, “inURLStr”: “/Folder/”, #URL of the request “inMatchTypeStr”: “”, #”BeginWith|Contains|Equal|EqualCase”, -“inFolderPathStr”: “”, #Absolute or relative path

    +“inFolderPathStr”: “”, #Absolute or relative path +“inUACBool”

    @@ -2477,6 +2563,7 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
  • inURLStr

  • inMatchTypeStr

  • inFolderPathStr

  • +
  • inUACBool – default: None; True - check user access before do this URL item. None - get Server flag if ask user

  • @@ -2484,11 +2571,11 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebUserInfoGet(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebUserInfoGet(inRequest=None)[source]

    Return User info about request

    Parameters
    -

    inRequest

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    {“DomainUpperStr”: “”, “UserNameUpperStr”: “”}

    @@ -2498,12 +2585,12 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebUserIsSuperToken(inRequest, inGSettings=None)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebUserIsSuperToken(inRequest=None, inGSettings=None)[source]

    Return bool if request is authentificated with supetoken (token which is never expires)

    Parameters
      -
    • inRequest

    • +
    • inRequest – inRequest from the server. Optional if call def from request thread

    • inGSettings – Global settings dict (singleton)

    @@ -2515,11 +2602,11 @@ Var 2 (Backward compatibility): inGSettings, inRDPSessionKeyStr, inHostStr, inPo
    -pyOpenRPA.Orchestrator.__Orchestrator__.WebUserUACHierarchyGet(inRequest)[source]
    +pyOpenRPA.Orchestrator.__Orchestrator__.WebUserUACHierarchyGet(inRequest=None)[source]

    Return User UAC Hierarchy DICT Return {…}

    Parameters
    -

    inRequest

    +

    inRequest – inRequest from the server. Optional if call def from request thread

    Returns

    UAC Dict {}

    diff --git a/Wiki/ENG_Guide/html/Orchestrator/03_gSettingsTemplate.html b/Wiki/ENG_Guide/html/Orchestrator/03_gSettingsTemplate.html index 4372efaf..f6b34add 100644 --- a/Wiki/ENG_Guide/html/Orchestrator/03_gSettingsTemplate.html +++ b/Wiki/ENG_Guide/html/Orchestrator/03_gSettingsTemplate.html @@ -301,18 +301,20 @@ # "ResponseFilePath": "", #Absolute or relative path # "ResponseFolderPath": "", #Absolute or relative path # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result + # "ResponseDefRequestGlobal": None ,#Function with str result + # "UACBool": True # True - check user access before do this URL item. None - get Server flag if ask user # } - { - "Method": "GET", - "URL": "/test/", # URL of the request - "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", - # "ResponseFilePath": "", #Absolute or relative path - "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", - # Absolute or relative path - # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result - } + #{ + # "Method": "GET", + # "URL": "/test/", # URL of the request + # "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", + # # "ResponseFilePath": "", #Absolute or relative path + # "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", + # # Absolute or relative path + # # "ResponseContentType": "", #HTTP Content-type + # # "ResponseDefRequestGlobal": None #Function with str result + # # "UACBool": True # True - check user access before do this URL item + #} ], }, @@ -343,6 +345,7 @@ ], }, "ManagersProcessDict":{}, # The key of the Process is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mProcessNameWOExeStr.upper()) + "ManagersGitDict":{}, # The key of the Git instance is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mAbsPathUpperStr.upper()) "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 "ActivityList": [ # List of the activities # { @@ -354,26 +357,13 @@ # "GUIDStr": ..., # GUID of the activity # }, ], + "ActivityItemNowDict": None, # Activity Item which is executing now "AliasDefDict": {}, # Storage for def with Str alias. To use it see pyOpenRPA.Orchestrator.ControlPanel "CheckIntervalSecFloat": 1.0, # Interval for check gSettings in ProcessorDict > ActivityList "ExecuteBool": True, # Flag to execute thread processor "ThreadIdInt": None, # Technical field - will be setup when processor init "WarningExecutionMoreThanSecFloat": 60.0 # Push warning if execution more than n seconds }, - # TODO REMOVE DEPRECATED KEYS IN v.2.0.0 - "ControlPanelDict": { # Old structure > CPDict - "RefreshSeconds": 5, # deprecated parameter - "RobotList": [ - #{ - # "RenderFunction": RenderRobotR01, - # "KeyStr": "TestControlPanelKey" - #} - ] - }, - # TODO REMOVE DEPRECATED KEYS IN v.2.0.0 - "CPDict": { - # "CPKey": {"HTMLRenderDef":None, "JSONGeneratorDef":None, "JSInitGeneratorDef":None} - }, # # # # # # # # # # # # # # "RobotRDPActive": { "RecoveryDict": { diff --git a/Wiki/ENG_Guide/html/Orchestrator/04_HowToUse.html b/Wiki/ENG_Guide/html/Orchestrator/04_HowToUse.html index 26789ee7..03ba4ea0 100644 --- a/Wiki/ENG_Guide/html/Orchestrator/04_HowToUse.html +++ b/Wiki/ENG_Guide/html/Orchestrator/04_HowToUse.html @@ -198,7 +198,8 @@

    If you need more configurations - so you can see here:

    -
    import psutil, datetime, logging, os, sys # stdout from logging
    +
    import psutil, datetime, logging, os, sys
    +
     
     # Config settings
     lPyOpenRPASourceFolderPathStr = r"..\Sources" # Path for test pyOpenRPA package
    @@ -213,10 +214,10 @@
     if not Orchestrator.OrchestratorIsAdmin():
         Orchestrator.OrchestratorRerunAsAdmin()
         print(f"Orchestrator will be run as administrator!")
    -elif __name__ == "__main__": # New init way - allow run as module -m PyOpenRPA.Orchestrator
    +else:
         gSettings = Orchestrator.GSettingsGet()
         #gSettings = SettingsTemplate.Create(inModeStr="BASIC") # Create GSettings with basic configuration - no more config is available from the box - you can create own
    -
    +    Orchestrator.OrchestratorLoggerGet().setLevel(logging.INFO)
         # TEST Add User ND - Add Login ND to superuser of the Orchestrator
         lUACClientDict = SettingsTemplate.__UACClientAdminCreate__()
         Orchestrator.UACUpdate(inGSettings=gSettings, inADLoginStr="ND", inADStr="", inADIsDefaultBool=True, inURLList=[], inRoleHierarchyAllowedDict=lUACClientDict)
    @@ -229,35 +230,11 @@
         # Restore DUMP
         Orchestrator.OrchestratorSessionRestore(inGSettings=gSettings)
         # Autoinit control panels starts with CP_
    -    lPyModules = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings")
    +    lPyModules = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings", inAsyncInitBool=True)
         # Call the orchestrator def
         Orchestrator.Orchestrator(inGSettings=gSettings, inDumpRestoreBool=False)
    -    #import atexit
    -    #def test():
    -    #    Orchestrator.OrchestratorLoggerGet().info(123)
    -    #atexit.register(test)
    -    import signal
    -    import sys
    -
    -
     
     
    -    from pyOpenRPA.Tools import Terminator
    -    Terminator.Init(inLogger=Orchestrator.OrchestratorLoggerGet())
    -
    -    #def signal_term_handler(signal, frame):
    -    #    Orchestrator.OrchestratorLoggerGet().info('got SIGTERM')
    -    #    sys.exit(0)
    -
    -    #signal.signal(signal.SIGTERM, signal_term_handler)
    -    import time
    -    while not Terminator.IsSignalClose():
    -        time.sleep(2)
    -    print(999)
    -    time.sleep(2)
    -
    -else:
    -    print("!WARNING! Current orchestrator settings do not support old type of the Orchestrator start. Use new Orchestrator type start (see v1.2.0)")
     
     
    diff --git a/Wiki/ENG_Guide/html/Orchestrator/06_Defs Managers.html b/Wiki/ENG_Guide/html/Orchestrator/06_Defs Managers.html index f54d3a46..73301e09 100644 --- a/Wiki/ENG_Guide/html/Orchestrator/06_Defs Managers.html +++ b/Wiki/ENG_Guide/html/Orchestrator/06_Defs Managers.html @@ -221,40 +221,57 @@ -

    ProcessGet(inAgentHostNameStr, …)

    +

    ProcessExists(inAgentHostNameStr, …)

    +

    Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)

    + +

    ProcessGet(inAgentHostNameStr, …)

    Return the process instance by the inProcessNameWOExeStr

    -

    ProcessManual2Auto(inAgentHostNameStr, …)

    +

    ProcessInitSafe(inAgentHostNameStr, …[, …])

    +

    Exception safe function.

    + +

    ProcessManual2Auto(inAgentHostNameStr, …)

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    -

    ProcessManualStopListClear(…)

    +

    ProcessManualStopListClear(…)

    Clear the last start tries list.

    -

    ProcessManualStopTriggerSet(…)

    +

    ProcessManualStopTriggerSet(…)

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    -

    ProcessStart(inAgentHostNameStr, …[, …])

    +

    ProcessScheduleStatusCheckEverySeconds(…)

    +

    Run status check every interval in second you specify.

    + +

    ProcessStart(inAgentHostNameStr, …[, …])

    Manual/Auto start.

    -

    ProcessStatusCheck(inAgentHostNameStr, …)

    +

    ProcessStatusCheck(inAgentHostNameStr, …)

    Check if process is alive.

    -

    ProcessStatusStrGet(inAgentHostNameStr, …)

    +

    ProcessStatusRestore(inAgentHostNameStr, …)

    +

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave).

    + +

    ProcessStatusSave(inAgentHostNameStr, …)

    +

    Save current status of the process.

    + +

    ProcessStatusStrGet(inAgentHostNameStr, …)

    Get the status of the Process instance.

    -

    ProcessStopForce(inAgentHostNameStr, …[, …])

    +

    ProcessStopForce(inAgentHostNameStr, …[, …])

    Manual/Auto stop force.

    -

    ProcessStopSafe(inAgentHostNameStr, …[, …])

    +

    ProcessStopSafe(inAgentHostNameStr, …[, …])

    Manual/Auto stop safe.

    -class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=120)[source]
    +class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30)[source]

    Manager process, which is need to be started / stopped / restarted

    With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not.

    +

    All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . +After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet

    Process instance has the following statuses:
    • 0_STOPPED

    • @@ -266,13 +283,7 @@
    -

    How to use StopSafe on the robot side -.. code-block:: python

    -
    -

    from pyOpenRPA.Tools import StopSafe -StopSafe.Init(inLogger=None) -StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe

    -
    +

    How to use StopSafe on the robot side

    Methods:

    @@ -280,31 +291,31 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces - + + + + - + - + - + - + - + - + - - - - + @@ -316,29 +327,44 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces - + + + + - + - + - + + + + + + + - + - + - +

    Manual2Auto()

    KeyTurpleGet()

    Get the key turple of the current process

    Manual2Auto()

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    ManualStopListClear()

    ManualStopListClear()

    Clear the last start tries list.

    ManualStopTriggerNewStart()

    ManualStopTriggerNewStart()

    Log new start event.

    ManualStopTriggerSet(inMSTdTSecFloat, inMSTdNInt)

    ManualStopTriggerSet(inMSTdTSecFloat, inMSTdNInt)

    Set ManualStopTrigger (MST) to switch to STOPPED MANUAL if specified count of start fails will be catched in specified time period

    MuteWait()

    MuteWait()

    Internal def.

    RestartForce([inIsManualBool])

    RestartForce([inIsManualBool])

    Manual/Auto restart force.

    RestartSafe([inIsManualBool])

    RestartSafe([inIsManualBool])

    Manual/Auto restart safe.

    ScheduleStatusCheckEverySeconds([…])

    Run status check every interval in second you specify.

    Start([inIsManualBool])

    Start([inIsManualBool, inStartArgDict])

    Manual/Auto start.

    StartCheck()

    StatusCheck()

    Check if process is alive.

    StatusCheckStart()

    StatusCheckIntervalRestore()

    Call from orchestrator when init

    StatusCheckStart()

    Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED”

    StatusCheckStopForce()

    StatusCheckStopForce()

    Check process status and auto stop force it if self.mStatusStr is 4_STARTED

    StatusCheckStopSafe()

    StatusCheckStopSafe()

    Check process status and auto stop safe it if self.mStatusStr is 4_STARTED

    StopForce([inIsManualBool])

    StatusRestore()

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave).

    StatusSave()

    Save current status of the process.

    StopForce([inIsManualBool, inMuteIgnoreBool])

    Manual/Auto stop force.

    StopForceCheck()

    StopForceCheck()

    Stop force program if auto started (4_STARTED).

    StopSafe([inIsManualBool])

    StopSafe([inIsManualBool, …])

    Manual/Auto stop safe.

    StopSafeCheck()

    StopSafeCheck([inStopSafeTimeoutSecFloat])

    Stop safe program if auto started (4_STARTED).

    +
    +
    +KeyTurpleGet()[source]
    +

    Get the key turple of the current process

    +
    +
    Manual2Auto() → str[source]
    @@ -430,23 +456,9 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -
    -ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=120)[source]
    -

    Run status check every interval in second you specify.

    -
    -
    Parameters
    -

    inIntervalSecondsInt – Interval in seconds. Default is 120

    -
    -
    Returns
    -

    None

    -
    -
    -
    -
    -Start(inIsManualBool=True) → str[source]
    +Start(inIsManualBool=True, inStartArgDict=None) → str[source]

    Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. Will not start if STOP SAFE is now and don’t start auto is stopped manual now

    @@ -484,7 +496,7 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    StatusCheck()[source]
    -

    Check if process is alive. The def will save the manual flag is exists.

    +

    Check if process is alive. The def will save the manual flag is exists. Don’t wait mute but set mute if it is not set.

    Returns

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    @@ -492,6 +504,12 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    +
    +
    +StatusCheckIntervalRestore()[source]
    +

    Call from orchestrator when init

    +
    +
    StatusCheckStart()[source]
    @@ -525,9 +543,31 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    +
    +
    +StatusRestore()[source]
    +

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted.

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    + +
    +
    +StatusSave()[source]
    +

    Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED”

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    +
    -StopForce(inIsManualBool=True) → str[source]
    +StopForce(inIsManualBool=True, inMuteIgnoreBool=False) → str[source]

    Manual/Auto stop force. Force stop don’t wait process termination - it just terminate process now. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -553,12 +593,15 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -StopSafe(inIsManualBool=True) → str[source]
    +StopSafe(inIsManualBool=True, inStopSafeTimeoutSecFloat=None) → str[source]

    Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    Parameters
    -

    inIsManualBool – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation

    +
      +
    • inIsManualBool – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation

    • +
    • inStopSafeTimeoutSecFloat – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force

    • +
    Returns

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    @@ -568,17 +611,38 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -StopSafeCheck() → str[source]
    +StopSafeCheck(inStopSafeTimeoutSecFloat=None) → str[source]

    Stop safe program if auto started (4_STARTED).

    -
    Returns
    -

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    Parameters
    +

    inStopSafeTimeoutSecFloat – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → bool[source]
    +

    Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)

    +
    +
    Parameters
    +
      +
    • inAgentHostNameStr – Agent hostname in any case. Required to identify Process

    • +
    • inAgentUserNameStr – Agent user name in any case. Required to identify Process

    • +
    • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

    • +
    +
    +
    Returns
    +

    True - process exists in gsettings; False - else

    +
    +
    +
    +
    pyOpenRPA.Orchestrator.Managers.Process.ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)pyOpenRPA.Orchestrator.Managers.Process.Process[source]
    @@ -597,6 +661,28 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300)pyOpenRPA.Orchestrator.Managers.Process.Process[source]
    +

    Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) +Return existing instance (if exists) or create new instance and return it.

    +
    +
    Parameters
    +
      +
    • inAgentHostNameStr – Agent hostname in any case. Required to identify Process

    • +
    • inAgentUserNameStr – Agent user name in any case. Required to identify Process

    • +
    • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

    • +
    • inStartPathStr – Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute

    • +
    • inStartCMDStr – CMD script to start program (if no start file is exists)

    • +
    • inStopSafeTimeoutSecFloat – Time to wait for stop safe. After that do the stop force (if process is not stopped)

    • +
    +
    +
    Returns
    +

    Process instance

    +
    +
    +
    +
    pyOpenRPA.Orchestrator.Managers.Process.ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]
    @@ -661,6 +747,25 @@ Process instance has the following statuses:
    +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIntervalSecondsInt: int = 120)[source]
    +

    Run status check every interval in second you specify.

    +
    +
    Parameters
    +
      +
    • inAgentHostNameStr – Agent hostname in any case. Required to identify Process

    • +
    • inAgentUserNameStr – Agent user name in any case. Required to identify Process

    • +
    • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

    • +
    • inIntervalSecondsInt – Interval in seconds. Default is 120

    • +
    +
    +
    Returns
    +

    None

    +
    +
    +
    +
    pyOpenRPA.Orchestrator.Managers.Process.ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]
    @@ -714,6 +819,28 @@ Process instance has the following statuses:
    +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)[source]
    +

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted.

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    + +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)[source]
    +

    Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED”

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    +
    pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]
    @@ -770,7 +897,7 @@ Process instance has the following statuses:
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat=None) → str[source]

    Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -780,6 +907,7 @@ Manual stop safe will block scheduling execution. To return schedule execution u
  • inAgentUserNameStr – Agent user name in any case. Required to identify Process

  • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

  • inIsManualBool – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation

  • +
  • inStopSafeTimeoutSecFloat – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force

  • Returns
    @@ -818,40 +946,57 @@ Process instance has the following statuses: -

    ProcessGet(inAgentHostNameStr, …)

    +

    ProcessExists(inAgentHostNameStr, …)

    +

    Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)

    + +

    ProcessGet(inAgentHostNameStr, …)

    Return the process instance by the inProcessNameWOExeStr

    -

    ProcessManual2Auto(inAgentHostNameStr, …)

    +

    ProcessInitSafe(inAgentHostNameStr, …[, …])

    +

    Exception safe function.

    + +

    ProcessManual2Auto(inAgentHostNameStr, …)

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    -

    ProcessManualStopListClear(…)

    +

    ProcessManualStopListClear(…)

    Clear the last start tries list.

    -

    ProcessManualStopTriggerSet(…)

    +

    ProcessManualStopTriggerSet(…)

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    -

    ProcessStart(inAgentHostNameStr, …[, …])

    +

    ProcessScheduleStatusCheckEverySeconds(…)

    +

    Run status check every interval in second you specify.

    + +

    ProcessStart(inAgentHostNameStr, …[, …])

    Manual/Auto start.

    -

    ProcessStatusCheck(inAgentHostNameStr, …)

    +

    ProcessStatusCheck(inAgentHostNameStr, …)

    Check if process is alive.

    -

    ProcessStatusStrGet(inAgentHostNameStr, …)

    +

    ProcessStatusRestore(inAgentHostNameStr, …)

    +

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave).

    + +

    ProcessStatusSave(inAgentHostNameStr, …)

    +

    Save current status of the process.

    + +

    ProcessStatusStrGet(inAgentHostNameStr, …)

    Get the status of the Process instance.

    -

    ProcessStopForce(inAgentHostNameStr, …[, …])

    +

    ProcessStopForce(inAgentHostNameStr, …[, …])

    Manual/Auto stop force.

    -

    ProcessStopSafe(inAgentHostNameStr, …[, …])

    +

    ProcessStopSafe(inAgentHostNameStr, …[, …])

    Manual/Auto stop safe.

    -class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=120)[source]
    +class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30)[source]

    Manager process, which is need to be started / stopped / restarted

    With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not.

    +

    All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . +After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet

    Process instance has the following statuses:
    • 0_STOPPED

    • @@ -863,13 +1008,7 @@ Process instance has the following statuses:
    -

    How to use StopSafe on the robot side -.. code-block:: python

    -
    -

    from pyOpenRPA.Tools import StopSafe -StopSafe.Init(inLogger=None) -StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe

    -
    +

    How to use StopSafe on the robot side

    Methods:

    @@ -877,31 +1016,31 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces - + + + + - + - + - + - + - + - + - - - - + @@ -913,32 +1052,47 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces - + + + + - + - + - + + + + + + + - + - + - +

    Manual2Auto()

    KeyTurpleGet()

    Get the key turple of the current process

    Manual2Auto()

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    ManualStopListClear()

    ManualStopListClear()

    Clear the last start tries list.

    ManualStopTriggerNewStart()

    ManualStopTriggerNewStart()

    Log new start event.

    ManualStopTriggerSet(inMSTdTSecFloat, inMSTdNInt)

    ManualStopTriggerSet(inMSTdTSecFloat, inMSTdNInt)

    Set ManualStopTrigger (MST) to switch to STOPPED MANUAL if specified count of start fails will be catched in specified time period

    MuteWait()

    MuteWait()

    Internal def.

    RestartForce([inIsManualBool])

    RestartForce([inIsManualBool])

    Manual/Auto restart force.

    RestartSafe([inIsManualBool])

    RestartSafe([inIsManualBool])

    Manual/Auto restart safe.

    ScheduleStatusCheckEverySeconds([…])

    Run status check every interval in second you specify.

    Start([inIsManualBool])

    Start([inIsManualBool, inStartArgDict])

    Manual/Auto start.

    StartCheck()

    StatusCheck()

    Check if process is alive.

    StatusCheckStart()

    StatusCheckIntervalRestore()

    Call from orchestrator when init

    StatusCheckStart()

    Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED”

    StatusCheckStopForce()

    StatusCheckStopForce()

    Check process status and auto stop force it if self.mStatusStr is 4_STARTED

    StatusCheckStopSafe()

    StatusCheckStopSafe()

    Check process status and auto stop safe it if self.mStatusStr is 4_STARTED

    StopForce([inIsManualBool])

    StatusRestore()

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave).

    StatusSave()

    Save current status of the process.

    StopForce([inIsManualBool, inMuteIgnoreBool])

    Manual/Auto stop force.

    StopForceCheck()

    StopForceCheck()

    Stop force program if auto started (4_STARTED).

    StopSafe([inIsManualBool])

    StopSafe([inIsManualBool, …])

    Manual/Auto stop safe.

    StopSafeCheck()

    StopSafeCheck([inStopSafeTimeoutSecFloat])

    Stop safe program if auto started (4_STARTED).

    -Manual2Auto() → str[source]
    +KeyTurpleGet()[source] +

    Get the key turple of the current process

    +
    + +
    +
    +Manual2Auto() → str[source]

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    Returns
    @@ -948,8 +1102,8 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces
    -
    -ManualStopListClear() → None[source]
    +
    +ManualStopListClear() → None[source]

    Clear the last start tries list.

    Returns
    @@ -959,8 +1113,8 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces
    -
    -ManualStopTriggerNewStart()[source]
    +
    +ManualStopTriggerNewStart()[source]

    Log new start event. Check if it is applicable. Change status if ManualStop trigger criteria is applied

    Returns
    @@ -970,8 +1124,8 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces
    -
    -ManualStopTriggerSet(inMSTdTSecFloat: float, inMSTdNInt: int) → None[source]
    +
    +ManualStopTriggerSet(inMSTdTSecFloat: float, inMSTdNInt: int) → None[source]

    Set ManualStopTrigger (MST) to switch to STOPPED MANUAL if specified count of start fails will be catched in specified time period

    Parameters
    @@ -987,8 +1141,8 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces
    -
    -MuteWait()[source]
    +
    +MuteWait()[source]

    Internal def. Wait when class is apply to send new activities to the agent

    Returns
    @@ -998,8 +1152,8 @@ StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someproces
    -
    -RestartForce(inIsManualBool=True)[source]
    +
    +RestartForce(inIsManualBool=True)[source]

    Manual/Auto restart force. Force restart dont wait process termination - it just terminate process now ant then start it. Manual restart will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -1013,8 +1167,8 @@ Manual restart will block scheduling execution. To return schedule execution use
    -
    -RestartSafe(inIsManualBool=True)[source]
    +
    +RestartSafe(inIsManualBool=True)[source]

    Manual/Auto restart safe. Restart safe is the operation which send signal to process to terminate own work (send term signal to process). Then it run process. Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -1027,23 +1181,9 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -
    -ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=120)[source]
    -

    Run status check every interval in second you specify.

    -
    -
    Parameters
    -

    inIntervalSecondsInt – Interval in seconds. Default is 120

    -
    -
    Returns
    -

    None

    -
    -
    -
    -
    -Start(inIsManualBool=True) → str[source]
    +Start(inIsManualBool=True, inStartArgDict=None) → str[source]

    Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. Will not start if STOP SAFE is now and don’t start auto is stopped manual now

    @@ -1081,7 +1221,7 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    StatusCheck()[source]
    -

    Check if process is alive. The def will save the manual flag is exists.

    +

    Check if process is alive. The def will save the manual flag is exists. Don’t wait mute but set mute if it is not set.

    Returns

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    @@ -1091,7 +1231,13 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    -StatusCheckStart()[source]
    +StatusCheckIntervalRestore()[source] +

    Call from orchestrator when init

    +
    + +
    +
    +StatusCheckStart()[source]

    Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED”

    Returns
    @@ -1101,8 +1247,8 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    -
    -StatusCheckStopForce()[source]
    +
    +StatusCheckStopForce()[source]

    Check process status and auto stop force it if self.mStatusStr is 4_STARTED

    Returns
    @@ -1112,8 +1258,8 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    -
    -StatusCheckStopSafe()[source]
    +
    +StatusCheckStopSafe()[source]

    Check process status and auto stop safe it if self.mStatusStr is 4_STARTED

    Returns
    @@ -1123,8 +1269,30 @@ Will not start if STOP SAFE is now and don’t start auto is stopped manual now<
    -
    -StopForce(inIsManualBool=True) → str[source]
    +
    +StatusRestore()[source]
    +

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted.

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    + +
    +
    +StatusSave()[source]
    +

    Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED”

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    + +
    +
    +StopForce(inIsManualBool=True, inMuteIgnoreBool=False) → str[source]

    Manual/Auto stop force. Force stop don’t wait process termination - it just terminate process now. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -1138,8 +1306,8 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -StopForceCheck() → str[source]
    +
    +StopForceCheck() → str[source]

    Stop force program if auto started (4_STARTED).

    Returns
    @@ -1149,13 +1317,16 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -StopSafe(inIsManualBool=True) → str[source]
    +
    +StopSafe(inIsManualBool=True, inStopSafeTimeoutSecFloat=None) → str[source]

    Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    Parameters
    -

    inIsManualBool – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation

    +
      +
    • inIsManualBool – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation

    • +
    • inStopSafeTimeoutSecFloat – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force

    • +
    Returns

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    @@ -1164,12 +1335,15 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -StopSafeCheck() → str[source]
    +
    +StopSafeCheck(inStopSafeTimeoutSecFloat=None) → str[source]

    Stop safe program if auto started (4_STARTED).

    -
    Returns
    -

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    Parameters
    +

    inStopSafeTimeoutSecFloat – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    @@ -1177,8 +1351,26 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)pyOpenRPA.Orchestrator.Managers.Process.Process[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → bool[source]
    +

    Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)

    +
    +
    Parameters
    +
      +
    • inAgentHostNameStr – Agent hostname in any case. Required to identify Process

    • +
    • inAgentUserNameStr – Agent user name in any case. Required to identify Process

    • +
    • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

    • +
    +
    +
    Returns
    +

    True - process exists in gsettings; False - else

    +
    +
    +
    + +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)pyOpenRPA.Orchestrator.Managers.Process.Process[source]

    Return the process instance by the inProcessNameWOExeStr

    Parameters
    @@ -1195,8 +1387,30 @@ Manual stop safe will block scheduling execution. To return schedule execution u
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300)pyOpenRPA.Orchestrator.Managers.Process.Process[source]
    +

    Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) +Return existing instance (if exists) or create new instance and return it.

    +
    +
    Parameters
    +
      +
    • inAgentHostNameStr – Agent hostname in any case. Required to identify Process

    • +
    • inAgentUserNameStr – Agent user name in any case. Required to identify Process

    • +
    • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

    • +
    • inStartPathStr – Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute

    • +
    • inStartCMDStr – CMD script to start program (if no start file is exists)

    • +
    • inStopSafeTimeoutSecFloat – Time to wait for stop safe. After that do the stop force (if process is not stopped)

    • +
    +
    +
    Returns
    +

    Process instance

    +
    +
    +
    + +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    Parameters
    @@ -1221,8 +1435,8 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessManualStopListClear(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → None[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessManualStopListClear(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → None[source]

    Clear the last start tries list.

    Parameters
    @@ -1239,8 +1453,8 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessManualStopTriggerSet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inMSTdTSecFloat: float, inMSTdNInt: int) → None[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessManualStopTriggerSet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inMSTdTSecFloat: float, inMSTdNInt: int) → None[source]

    Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self):

    Parameters
    @@ -1259,8 +1473,27 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIntervalSecondsInt: int = 120)[source]
    +

    Run status check every interval in second you specify.

    +
    +
    Parameters
    +
      +
    • inAgentHostNameStr – Agent hostname in any case. Required to identify Process

    • +
    • inAgentUserNameStr – Agent user name in any case. Required to identify Process

    • +
    • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

    • +
    • inIntervalSecondsInt – Interval in seconds. Default is 120

    • +
    +
    +
    Returns
    +

    None

    +
    +
    +
    + +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]

    Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto

    Parameters
    @@ -1286,8 +1519,8 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusCheck(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusCheck(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]

    Check if process is alive. The def will save the manual flag is exists.

    Parameters
    @@ -1312,8 +1545,30 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)[source]
    +

    Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted.

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    + +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str)[source]
    +

    Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED”

    +
    +
    Returns
    +

    Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL

    +
    +
    +
    + +
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) → str[source]

    Get the status of the Process instance.

    Parameters
    @@ -1338,8 +1593,8 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessStopForce(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStopForce(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]

    Manual/Auto stop force. Force stop dont wait process termination - it just terminate process now. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -1366,8 +1621,8 @@ Process instance has the following statuses:
    -
    -pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) → str[source]
    +
    +pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat=None) → str[source]

    Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto

    @@ -1377,6 +1632,7 @@ Manual stop safe will block scheduling execution. To return schedule execution u
  • inAgentUserNameStr – Agent user name in any case. Required to identify Process

  • inProcessNameWOExeStr – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case

  • inIsManualBool – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation

  • +
  • inStopSafeTimeoutSecFloat – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force

  • Returns
    @@ -1408,7 +1664,7 @@ Process instance has the following statuses: -

    ControlPanel(inControlPanelNameStr[, …])

    +

    ControlPanel(inControlPanelNameStr[, …])

    Manage your control panel on the orchestrator

    @@ -1437,6 +1693,8 @@ UserUACCheckDef: inRequest.UACClientCheck, EnumerateDef: enumerate, OperatorModule: operator, MathModule: math

    +

    You can modify jinja context by use the function: +Jinja2DataUpdateDictSet

    Methods:

    @@ -1444,7 +1702,7 @@ MathModule: math

    - + - + - + - + - + - + - + - + - + - + - + - + @@ -1657,14 +1915,14 @@ Support backward compatibility for previous versions.

    - +

    DataDictGenerate(inRequest)

    DataDictGenerate(inRequest)

    param inRequest

    request handler (from http.server import BaseHTTPRequestHandler)

    @@ -1452,37 +1710,37 @@ MathModule: math

    InitJSJinja2StrGenerate(inDataDict)

    InitJSJinja2StrGenerate(inDataDict)

    Generate the HTML str from the Jinja2.

    InitJSJinja2TemplatePathSet(…)

    InitJSJinja2TemplatePathSet(…)

    Create Jinja2 env and load the template html

    Jinja2DataUpdateDictSet(inJinja2DataUpdateDict)

    Jinja2DataUpdateDictSet(inJinja2DataUpdateDict)

    Set the data dict from the Jinja2 context (you can add some new params)

    OnInitJSDataDict(inRequest)

    OnInitJSDataDict(inRequest)

    Event to prepare data context for the futher Jinja2 JS init generation.

    OnInitJSStr(inRequest)

    OnInitJSStr(inRequest)

    Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once.

    OnRefreshHTMLDataDict(inRequest)

    OnRefreshHTMLDataDict(inRequest)

    Event to prepare data context for the futher Jinja2 HTML generation.

    OnRefreshHTMLHashStr(inRequest)

    OnRefreshHTMLHashStr(inRequest)

    Generate the hash the result output HTML.

    OnRefreshHTMLStr(inRequest)

    OnRefreshHTMLStr(inRequest)

    Event to generate HTML code of the control panel when refresh time is over.

    OnRefreshJSONDict(inRequest)

    OnRefreshJSONDict(inRequest)

    Event to transmit some data from server side to the client side in JSON format.

    RefreshHTMLJinja2StrGenerate(inDataDict)

    RefreshHTMLJinja2StrGenerate(inDataDict)

    Generate the HTML str from the Jinja2.

    RefreshHTMLJinja2TemplatePathSet(…)

    RefreshHTMLJinja2TemplatePathSet(…)

    Create Jinja2 env and load the template html

    ControlPanel(inControlPanelNameStr[, …])

    ControlPanel(inControlPanelNameStr[, …])

    Manage your control panel on the orchestrator

    -
    -class pyOpenRPA.Orchestrator.Managers.ControlPanel.ControlPanel(inControlPanelNameStr, inRefreshHTMLJinja2TemplatePathStr=None, inJinja2TemplateRefreshBool=False, inRobotNameStr=None)[source]
    +
    +class pyOpenRPA.Orchestrator.Managers.ControlPanel.ControlPanel(inControlPanelNameStr, inRefreshHTMLJinja2TemplatePathStr=None, inJinja2TemplateRefreshBool=False, inRobotNameStr=None)[source]

    Manage your control panel on the orchestrator

    Control panel has 3 events types: - onRefreshHTML - run every n (see settings) second to detect changes in HTML control panel. @@ -1686,6 +1944,8 @@ UserUACCheckDef: inRequest.UACClientCheck, EnumerateDef: enumerate, OperatorModule: operator, MathModule: math

    +

    You can modify jinja context by use the function: +Jinja2DataUpdateDictSet

    Methods:

    @@ -1693,7 +1953,7 @@ MathModule: math

    - + - + - + - + - + - + - + - + - + - + - + - +

    DataDictGenerate(inRequest)

    DataDictGenerate(inRequest)

    param inRequest

    request handler (from http.server import BaseHTTPRequestHandler)

    @@ -1701,44 +1961,44 @@ MathModule: math

    InitJSJinja2StrGenerate(inDataDict)

    InitJSJinja2StrGenerate(inDataDict)

    Generate the HTML str from the Jinja2.

    InitJSJinja2TemplatePathSet(…)

    InitJSJinja2TemplatePathSet(…)

    Create Jinja2 env and load the template html

    Jinja2DataUpdateDictSet(inJinja2DataUpdateDict)

    Jinja2DataUpdateDictSet(inJinja2DataUpdateDict)

    Set the data dict from the Jinja2 context (you can add some new params)

    OnInitJSDataDict(inRequest)

    OnInitJSDataDict(inRequest)

    Event to prepare data context for the futher Jinja2 JS init generation.

    OnInitJSStr(inRequest)

    OnInitJSStr(inRequest)

    Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once.

    OnRefreshHTMLDataDict(inRequest)

    OnRefreshHTMLDataDict(inRequest)

    Event to prepare data context for the futher Jinja2 HTML generation.

    OnRefreshHTMLHashStr(inRequest)

    OnRefreshHTMLHashStr(inRequest)

    Generate the hash the result output HTML.

    OnRefreshHTMLStr(inRequest)

    OnRefreshHTMLStr(inRequest)

    Event to generate HTML code of the control panel when refresh time is over.

    OnRefreshJSONDict(inRequest)

    OnRefreshJSONDict(inRequest)

    Event to transmit some data from server side to the client side in JSON format.

    RefreshHTMLJinja2StrGenerate(inDataDict)

    RefreshHTMLJinja2StrGenerate(inDataDict)

    Generate the HTML str from the Jinja2.

    RefreshHTMLJinja2TemplatePathSet(…)

    RefreshHTMLJinja2TemplatePathSet(…)

    Create Jinja2 env and load the template html

    -
    -DataDictGenerate(inRequest)[source]
    +
    +DataDictGenerate(inRequest)[source]
    Parameters

    inRequest – request handler (from http.server import BaseHTTPRequestHandler)

    @@ -1750,16 +2010,16 @@ MathModule: math

    -
    -InitJSJinja2StrGenerate(inDataDict)[source]
    +
    +InitJSJinja2StrGenerate(inDataDict)[source]

    Generate the HTML str from the Jinja2. Pass the context inDataDict :param inDataDict: :return:

    -
    -InitJSJinja2TemplatePathSet(inJinja2TemplatePathStr)[source]
    +
    +InitJSJinja2TemplatePathSet(inJinja2TemplatePathStr)[source]

    Create Jinja2 env and load the template html

    Parameters
    @@ -1772,8 +2032,8 @@ MathModule: math

    -
    -Jinja2DataUpdateDictSet(inJinja2DataUpdateDict)[source]
    +
    +Jinja2DataUpdateDictSet(inJinja2DataUpdateDict)[source]

    Set the data dict from the Jinja2 context (you can add some new params)

    Parameters
    @@ -1786,8 +2046,8 @@ MathModule: math

    -
    -OnInitJSDataDict(inRequest)[source]
    +
    +OnInitJSDataDict(inRequest)[source]

    Event to prepare data context for the futher Jinja2 JS init generation. You can override this def if you want some thing more data

    Parameters
    @@ -1800,8 +2060,8 @@ MathModule: math

    -
    -OnInitJSStr(inRequest)[source]
    +
    +OnInitJSStr(inRequest)[source]

    Event when orchestrator web page is init on the client side - you can transmit some java script code is str type to execute it once.

    Parameters
    @@ -1815,8 +2075,8 @@ MathModule: math

    -
    -OnRefreshHTMLDataDict(inRequest)[source]
    +
    +OnRefreshHTMLDataDict(inRequest)[source]

    Event to prepare data context for the futher Jinja2 HTML generation. You can override this def if you want some thing more data

    Parameters
    @@ -1829,8 +2089,8 @@ MathModule: math

    -
    -OnRefreshHTMLHashStr(inRequest)[source]
    +
    +OnRefreshHTMLHashStr(inRequest)[source]

    Generate the hash the result output HTML. You can override this function if you know how to optimize HTML rendering. TODO NEED TO MODIFY ServerSettings to work with Hash because of all defs are need do use Hash

    @@ -1844,8 +2104,8 @@ TODO NEED TO MODIFY ServerSettings to work with Hash because of all defs are nee
    -
    -OnRefreshHTMLStr(inRequest)[source]
    +
    +OnRefreshHTMLStr(inRequest)[source]

    Event to generate HTML code of the control panel when refresh time is over. Support backward compatibility for previous versions.

    @@ -1859,8 +2119,8 @@ Support backward compatibility for previous versions.

    -
    -OnRefreshJSONDict(inRequest)[source]
    +
    +OnRefreshJSONDict(inRequest)[source]

    Event to transmit some data from server side to the client side in JSON format. Call when page refresh is initialized

    Parameters
    @@ -1873,16 +2133,16 @@ Support backward compatibility for previous versions.

    -
    -RefreshHTMLJinja2StrGenerate(inDataDict)[source]
    +
    +RefreshHTMLJinja2StrGenerate(inDataDict)[source]

    Generate the HTML str from the Jinja2. Pass the context inDataDict :param inDataDict: :return:

    -
    -RefreshHTMLJinja2TemplatePathSet(inJinja2TemplatePathStr)[source]
    +
    +RefreshHTMLJinja2TemplatePathSet(inJinja2TemplatePathStr)[source]

    Create Jinja2 env and load the template html

    Parameters
    diff --git a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Agent/__Agent__.html b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Agent/__Agent__.html index ce3e3b05..620739e4 100644 --- a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Agent/__Agent__.html +++ b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Agent/__Agent__.html @@ -188,6 +188,9 @@ from . import O2A, A2O # Data flow Orchestrator To Agent from . import Processor # Processor Queue from subprocess import CREATE_NEW_CONSOLE # Flag to create new process in another CMD +import os + +gSettings = None # Create binary file by the base64 string (safe for JSON transmition)
    [docs]def OSFileBinaryDataBase64StrCreate(inFilePathStr, inFileDataBase64Str,inGSettings = None): @@ -244,16 +247,37 @@ :param inGSettings: global settings of the Agent (singleton) :return: File content in string base64 format (use base64.b64decode to decode data). Return None if file is not exist """ - lFile = open(inFilePathStr, "rb") - lFileDataBytes = lFile.read() - lFile.close() - lFileDataBase64Str = base64.b64encode(lFileDataBytes).decode("utf-8") lL = inGSettings.get("Logger", None) if type(inGSettings) is dict else None - lMessageStr = f"AGENT Binary file {inFilePathStr} has been read." - if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + lFileDataBase64Str = None + if os.path.exists(inFilePathStr): + lFile = open(inFilePathStr, "rb") + lFileDataBytes = lFile.read() + lFile.close() + lFileDataBase64Str = base64.b64encode(lFileDataBytes).decode("utf-8") + lMessageStr = f"OSFileBinaryDataBase64StrReceive: file {inFilePathStr} has been read" + if lL: lL.debug(lMessageStr) + #A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + else: + if lL: lL.debug(f"OSFileBinaryDataBase64StrReceive: file {inFilePathStr} is not exists - return None") return lFileDataBase64Str
    +
    [docs]def OSFileMTimeGet(inFilePathStr: str) -> float or None: + """ + Read file modification time timestamp format (float) + + :param inFilePathStr: File path to read + :return: timestamp (float) Return None if file is not exist + """ + global gSettings + lL = gSettings.get("Logger", None) if type(gSettings) is dict else None + lFileMTimeFloat = None + if os.path.exists(inFilePathStr): + lFileMTimeFloat = os.path.getmtime(inFilePathStr) + if lL: lL.debug(f"OSFileMTimeGet: file {inFilePathStr} has been read") + else: + if lL: lL.debug(f"OSFileMTimeGet: file {inFilePathStr} is not exists - return None") + return lFileMTimeFloat
    +
    [docs]def OSFileTextDataStrReceive(inFilePathStr, inEncodingStr="utf-8", inGSettings=None): """ Read text file in the agent GUI session @@ -263,17 +287,21 @@ :param inGSettings: global settings of the Agent (singleton) :return: File text content in string format (use base64.b64decode to decode data). Return None if file is not exist """ - lFile = open(inFilePathStr, "r", encoding=inEncodingStr) - lFileDataStr = lFile.read() - lFile.close() + lFileDataStr = None lL = inGSettings.get("Logger", None) if type(inGSettings) is dict else None - lMessageStr = f"AGENT Text file {inFilePathStr} has been read." - if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if os.path.exists(inFilePathStr): + lFile = open(inFilePathStr, "r", encoding=inEncodingStr) + lFileDataStr = lFile.read() + lFile.close() + lMessageStr = f"OSFileTextDataStrReceive: file {inFilePathStr} has been read" + if lL: lL.info(lMessageStr) + #A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + else: + if lL: lL.info(f"OSFileTextDataStrReceive: file {inFilePathStr} is not exists - return None") return lFileDataStr
    # Send CMD to OS. Result return to log + Orchestrator by the A2O connection -
    [docs]def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251"): +
    [docs]def OSCMD(inCMDStr, inRunAsyncBool=True, inGSettings = None, inSendOutputToOrchestratorLogsBool = True, inCMDEncodingStr = "cp1251", inCaptureBool = True): """ Execute CMD on the Agent daemonic process @@ -281,18 +309,21 @@ :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inGSettings: Agent global settings dict :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True - !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: """ lResultStr = "" + # New feature + if inSendOutputToOrchestratorLogsBool == False and inCaptureBool == False: + inCMDStr = f"start {inCMDStr}" # Subdef to listen OS result - def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None): + def _CMDRunAndListenLogs(inCMDStr, inSendOutputToOrchestratorLogsBool, inCMDEncodingStr, inGSettings = None, inCaptureBool = True): lL = inGSettings.get("Logger",None) if type(inGSettings) is dict else None lResultStr = "" lOSCMDKeyStr = str(uuid.uuid4())[0:4].upper() lCMDProcess = None - if inSendOutputToOrchestratorLogsBool == True: + if inCaptureBool == True: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) else: lCMDProcess = subprocess.Popen(f'cmd /c {inCMDStr}', stdout=None, stderr=None, @@ -300,12 +331,15 @@ lListenBool = True lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been STARTED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings,inLogList=[lMessageStr]) lMessageStr = f"{lOSCMDKeyStr}: {inCMDStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) while lListenBool: - if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + #if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + if inCaptureBool == True: # Capturing can be turned on! lOutputLineBytes = lCMDProcess.stdout.readline() if lOutputLineBytes == b"": lListenBool = False @@ -313,7 +347,8 @@ if lStr.endswith("\n"): lStr = lStr[:-1] lMessageStr = f"{lOSCMDKeyStr}: {lStr}" if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) lResultStr+=lStr else: #Capturing is not turned on - wait until process will be closed lCMDProcessPoll = lCMDProcess.poll() @@ -323,15 +358,16 @@ lListenBool = False lMessageStr = f"{lOSCMDKeyStr}: # # # # AGENT CMD Process has been FINISHED # # # # " if lL: lL.info(lMessageStr) - A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) + if inSendOutputToOrchestratorLogsBool == True: # Capturing can be turned on! + A2O.LogListSend(inGSettings=inGSettings, inLogList=[lMessageStr]) return lResultStr # New call if inRunAsyncBool: - lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr }) + lThread = threading.Thread(target=_CMDRunAndListenLogs, kwargs={"inCMDStr":inCMDStr, "inGSettings":inGSettings, "inSendOutputToOrchestratorLogsBool":inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr":inCMDEncodingStr, "inCaptureBool": inCaptureBool }) lThread.start() lResultStr="ActivityList has been started in async mode - no output is available here." else: - lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr) + lResultStr = _CMDRunAndListenLogs(inCMDStr=inCMDStr, inGSettings=inGSettings, inSendOutputToOrchestratorLogsBool = inSendOutputToOrchestratorLogsBool, inCMDEncodingStr = inCMDEncodingStr, inCaptureBool=inCaptureBool) #lCMDCode = "cmd /c " + inCMDStr #subprocess.Popen(lCMDCode) #lResultCMDRun = 1 # os.system(lCMDCode) @@ -366,7 +402,8 @@ # Main def def Agent(inGSettings): lL = inGSettings["Logger"] - + global gSettings + gSettings = inGSettings # Append Orchestrator def to ProcessorDictAlias lModule = sys.modules[__name__] lModuleDefList = dir(lModule) diff --git a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/ControlPanel.html b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/ControlPanel.html index 822f684e..90a5db27 100644 --- a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/ControlPanel.html +++ b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/ControlPanel.html @@ -206,6 +206,8 @@ # Usage example: lCPManager = Orchestrator.Managers.ControlPanel(inControlPanelNameStr="TestControlPanel", inRefreshHTMLJinja2TemplatePathStr="ControlPanel\\test.html", inJinja2TemplateRefreshBool = True) + + If you use Jinja2 you can use next data context: StorageRobotDict: Orchestrator.StorageRobotGet(inRobotNameStr=self.mRobotNameStr), @@ -219,6 +221,9 @@ OperatorModule: operator, MathModule: math + You can modify jinja context by use the function: + Jinja2DataUpdateDictSet + .. code-block:: html Hello my control panel! You can use any def from Orchestrator module here in Jinja2 HTML template: diff --git a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/Process.html b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/Process.html index 0c227fd0..657a3b1b 100644 --- a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/Process.html +++ b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/Managers/Process.html @@ -188,12 +188,17 @@ from .. import __Orchestrator__ import os import time + +from pyOpenRPA import Orchestrator
    [docs]class Process(): """ Manager process, which is need to be started / stopped / restarted With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not. + All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . + After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet + Process instance has the following statuses: - 0_STOPPED - 1_STOPPED_MANUAL @@ -202,9 +207,9 @@ - 4_STARTED - 5_STARTED_MANUAL - .. code-block:: python - lProcess = Orchestrator.Managers.Process(inAgentHostNameStr="PCNAME",inAgentUserNameStr="USER", + # For the safe init class use ProcessInitSafe + lProcess = Orchestrator.Managers.ProcessInitSafe(inAgentHostNameStr="PCNAME",inAgentUserNameStr="USER", inProcessNameWOExeStr="notepad",inStartCMDStr="notepad",inStopSafeTimeoutSecFloat=3) # Async way to run job lProcess.ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=5) @@ -214,6 +219,7 @@ Orchestrator.OrchestratorScheduleGet().every(5).seconds.do(lProcess.StartCheck) How to use StopSafe on the robot side + .. code-block:: python from pyOpenRPA.Tools import StopSafe StopSafe.Init(inLogger=None) @@ -224,6 +230,8 @@ mAgentUserNameStr = None mStartPathStr = None mStartCMDStr = None + mStartArgDict = None + mStatusCheckIntervalSecFloat = None mProcessNameWOExeStr = None mStopSafeTimeoutSecFloat = None mStatusStr = None # 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL @@ -235,6 +243,8 @@ mAgentMuteBool = False # Mute any sends to agent while some action is perfomed + mStatusSavedStr = None # Saved status to the further restore +
    [docs] def MuteWait(self): """ Internal def. Wait when class is apply to send new activities to the agent @@ -246,16 +256,43 @@ time.sleep(lIntervalSecFloat) return None
    - def __init__(self, inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=120): - self.mAgentHostNameStr = inAgentHostNameStr - self.mAgentUserNameStr = inAgentUserNameStr - self.mStartPathStr = inStartPathStr - self.mStartCMDStr = inStartCMDStr - self.mProcessNameWOExeStr = inProcessNameWOExeStr - self.mStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat - __Orchestrator__.GSettingsGet()["ManagersProcessDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper())]=self - lActivityDict = __Orchestrator__.ProcessorActivityItemCreate(inDef=self.StatusCheck,inArgList=[]) - __Orchestrator__.ProcessorActivityItemAppend(inActivityItemDict=lActivityDict) +
    [docs] def KeyTurpleGet(self): + """ + Get the key turple of the current process + + """ + return (self.mAgentHostNameStr.upper(), self.mAgentUserNameStr.upper(), self.mProcessNameWOExeStr.upper())
    + + + def __init__(self, inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30): + """ + Init the class instance. + !ATTENTION! Function can raise exception if process with the same (inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr) is already exists in GSettings (can be restored from previous Orchestrator session). See ProcessInitSafe to sefaty init the instance or restore previous + !ATTENTION! Schedule options you must + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inStartPathStr: Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + :param inStartCMDStr: CMD script to start program (if no start file is exists) + :param inStopSafeTimeoutSecFloat: Time to wait for stop safe. After that do the stop force (if process is not stopped) + """ + lGS = __Orchestrator__.GSettingsGet() + # Check if Process is not exists in GSettings + if (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()) not in lGS["ManagersProcessDict"]: + self.mStartArgDict = inStartArgDict + self.mAgentHostNameStr = inAgentHostNameStr + self.mAgentUserNameStr = inAgentUserNameStr + self.mStartPathStr = inStartPathStr + self.mStartCMDStr = inStartCMDStr + self.mProcessNameWOExeStr = inProcessNameWOExeStr + self.mStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat + lGS["ManagersProcessDict"][(inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper())]=self + lActivityDict = __Orchestrator__.ProcessorActivityItemCreate(inDef=self.StatusCheck,inArgList=[], inThreadBool=True) + __Orchestrator__.ProcessorActivityItemAppend(inActivityItemDict=lActivityDict) + if inStatusCheckIntervalSecFloat is not None: __Orchestrator__.OrchestratorScheduleGet().every(inStatusCheckIntervalSecFloat).seconds.do(Orchestrator.OrchestratorThreadStart,self.StatusCheck) + self.mStatusCheckIntervalSecFloat = inStatusCheckIntervalSecFloat + else: raise Exception(f"Managers.Process ({inAgentHostNameStr}, {inAgentUserNameStr}, {inProcessNameWOExeStr}): Can't init the Process instance because it already inited in early (see ProcessInitSafe)")
    [docs] def ManualStopTriggerSet(self, inMSTdTSecFloat: float, inMSTdNInt: int) -> None: """ @@ -317,7 +354,7 @@ if lLogBool == True: self.StatusChangeLog() return self.mStatusStr
    -
    [docs] def Start(self, inIsManualBool = True) -> str: +
    [docs] def Start(self, inIsManualBool = True, inStartArgDict=None) -> str: """ Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. Will not start if STOP SAFE is now and don't start auto is stopped manual now @@ -326,19 +363,25 @@ :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL """ if inIsManualBool == False: self.ManualStopTriggerNewStart() # Set the time - if (self.mStatusStr == "1_STOPPED_MANUAL" or "STOP_SAFE" in self.mStatusStr) and inIsManualBool == False: + if self.mStatusStr is not None and (self.mStatusStr == "1_STOPPED_MANUAL" or "STOP_SAFE" in self.mStatusStr) and inIsManualBool == False: lStr = f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Process will not start because of stopped manual or stop safe is now." __Orchestrator__.OrchestratorLoggerGet().warning(lStr) return self.mStatusStr # Send activity item to agent - wait result - if self.mStartPathStr is not None: lCMDStr = f"start {os.path.abspath(self.mStartPathStr)}" - elif self.mStartCMDStr is not None: lCMDStr = f"start {self.mStartCMDStr}" + if self.mStartPathStr is not None: lCMDStr = os.path.abspath(self.mStartPathStr) + elif self.mStartCMDStr is not None: lCMDStr = self.mStartCMDStr + # Append args + if inStartArgDict is not None: self.mStartArgDict = inStartArgDict + if self.mStartArgDict is not None: + for lItemKeyStr in self.mStartArgDict: + lItemValueStr = self.mStartArgDict[lItemKeyStr] + lCMDStr = f"{lCMDStr} {lItemKeyStr} {lItemValueStr}" #import pdb #pdb.set_trace() self.MuteWait() self.mAgentMuteBool=True lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate(inDef="OSCMD", - inArgDict={"inCMDStr":lCMDStr,"inSendOutputToOrchestratorLogsBool":False}, + inArgDict={"inCMDStr":lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool":False}, inArgGSettingsStr="inGSettings") lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, inUserStr=self.mAgentUserNameStr, @@ -364,20 +407,22 @@ self.Start(inIsManualBool=False) return self.mStatusStr
    -
    [docs] def StopSafe(self, inIsManualBool = True) -> str: +
    [docs] def StopSafe(self, inIsManualBool = True, inStopSafeTimeoutSecFloat = None) -> str: """ Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL """ + if inStopSafeTimeoutSecFloat is None: inStopSafeTimeoutSecFloat = self.mStopSafeTimeoutSecFloat self.MuteWait() self.mAgentMuteBool=True # Send activity item to agent - wait result lCMDStr = f'taskkill /im "{self.mProcessNameWOExeStr}.exe" /fi "username eq %USERNAME%"' lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate( - inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False},inArgGSettingsStr="inGSettings") + inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool": False},inArgGSettingsStr="inGSettings") lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, inUserStr=self.mAgentUserNameStr, inActivityItemDict=lActivityItemStart) @@ -391,31 +436,33 @@ # Interval check is stopped lTimeStartFloat = time.time() lIntervalCheckSafeStatusFLoat = 15.0 - while "SAFE" in self.mStatusStr and (time.time() - lTimeStartFloat) < self.mStopSafeTimeoutSecFloat: + while "SAFE" in self.mStatusStr and (time.time() - lTimeStartFloat) < inStopSafeTimeoutSecFloat: self.StatusCheck() + if "SAFE" not in self.mStatusStr: break time.sleep(lIntervalCheckSafeStatusFLoat) if "SAFE" in self.mStatusStr: # Log info about process lL = __Orchestrator__.OrchestratorLoggerGet() - lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe stop has been wait for {self.mStopSafeTimeoutSecFloat} sec. Now do the force stop.") - self.StopForce(inIsManualBool=inIsManualBool) + lL.info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe stop has been wait for {inStopSafeTimeoutSecFloat} sec. Now do the force stop.") + self.StopForce(inIsManualBool=inIsManualBool,inMuteIgnoreBool=True) # Log info about process - self.StatusChangeLog() - self.mAgentMuteBool=False + # self.StatusChangeLog() status check has already log status (see above) + self.mAgentMuteBool = False return self.mStatusStr
    -
    [docs] def StopSafeCheck(self) -> str: +
    [docs] def StopSafeCheck(self, inStopSafeTimeoutSecFloat = None) -> str: """ Stop safe program if auto started (4_STARTED). + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL """ self.MuteWait() if self.mStatusStr == "4_STARTED": - self.StopSafe(inIsManualBool=False) + self.StopSafe(inIsManualBool=False, inStopSafeTimeoutSecFloat = inStopSafeTimeoutSecFloat) return self.mStatusStr
    -
    [docs] def StopForce(self, inIsManualBool = True) -> str: +
    [docs] def StopForce(self, inIsManualBool = True, inMuteIgnoreBool = False) -> str: """ Manual/Auto stop force. Force stop don't wait process termination - it just terminate process now. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto @@ -423,12 +470,13 @@ :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL """ - self.MuteWait() - self.mAgentMuteBool=True + if inMuteIgnoreBool == False: self.MuteWait() + lMuteWorkBool = False + if self.mAgentMuteBool==False: self.mAgentMuteBool=True; lMuteWorkBool=True # Send activity item to agent - wait result lCMDStr = f'taskkill /F /im "{self.mProcessNameWOExeStr}.exe" /fi "username eq %USERNAME%"' lActivityItemStart = __Orchestrator__.ProcessorActivityItemCreate( - inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False},inArgGSettingsStr="inGSettings") + inDef="OSCMD",inArgDict={"inCMDStr": lCMDStr,"inSendOutputToOrchestratorLogsBool":False, "inCaptureBool": False},inArgGSettingsStr="inGSettings") lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr, inUserStr=self.mAgentUserNameStr, inActivityItemDict=lActivityItemStart) @@ -439,7 +487,8 @@ self.mStatusStr = "0_STOPPED" # Log info about process self.StatusChangeLog() - self.mAgentMuteBool=False + if lMuteWorkBool == True: + self.mAgentMuteBool=False return self.mStatusStr
    [docs] def StopForceCheck(self) -> str: @@ -475,6 +524,48 @@ self.StopForce(inIsManualBool=inIsManualBool) return self.Start(inIsManualBool=inIsManualBool)
    +
    [docs] def StatusSave(self): + """ + Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don't save "STOP_SAFE" status > "STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lWarnSafeBool = True + if self.mStatusStr == "2_STOP_SAFE": self.mStatusSavedStr = "0_STOPPED" + elif self.mStatusStr == "3_STOP_SAFE_MANUAL": self.mStatusSavedStr = "1_STOPPED_MANUAL" + else: self.mStatusSavedStr = self.mStatusStr; lWarnSafeBool = False + if lWarnSafeBool==True: __Orchestrator__.OrchestratorLoggerGet().warning(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Safe status has been catched when safe > change saved status to stopped.") + return self.mStatusStr
    + + +
    [docs] def StatusCheckIntervalRestore(self): + """Call from orchestrator when init + """ + if self.mStatusCheckIntervalSecFloat is not None: + __Orchestrator__.OrchestratorLoggerGet().info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Restore schedule to StatusCheck in interval of {self.mStatusCheckIntervalSecFloat} sec.") + __Orchestrator__.OrchestratorScheduleGet().every(self.mStatusCheckIntervalSecFloat).seconds.do(Orchestrator.OrchestratorThreadStart,self.StatusCheck)
    + +
    [docs] def StatusRestore(self): + """ + Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + self.StatusCheck() # check current status + # Do some action + if self.mStatusSavedStr != self.mStatusStr and self.mStatusSavedStr is not None: + #lManualBool = False + #if "MANUAL" in self.mStatusSavedStr: + # lManualBool = True + if "STOPPED" in self.mStatusSavedStr and "STOPPED" not in self.mStatusStr: + self.StopSafe(inIsManualBool=True) + if "STARTED" in self.mStatusSavedStr and "STARTED" not in self.mStatusStr: + self.Start(inIsManualBool=True) + Orchestrator.OrchestratorLoggerGet().info(f"Managers.Process ({self.mAgentHostNameStr}, {self.mAgentUserNameStr}, {self.mProcessNameWOExeStr}): Status has been restored to {self.mStatusSavedStr}") + self.mStatusStr = self.mStatusSavedStr + self.mStatusSavedStr = None + return self.mStatusStr
    +
    [docs] def StatusChangeLog(self): """ Lof information about status change @@ -488,14 +579,14 @@
    [docs] def StatusCheck(self): """ - Check if process is alive. The def will save the manual flag is exists. + Check if process is alive. The def will save the manual flag is exists. Don't wait mute but set mute if it is not set. :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL """ # Send activity item to agent - wait result lLogBool = False lActivityItemUserProcessList = __Orchestrator__.ProcessorActivityItemCreate(inDef="ProcessWOExeUpperUserListGet") - self.MuteWait() + #self.MuteWait() self.mAgentMuteBool=True lGUIDStr = __Orchestrator__.AgentActivityItemAdd(inHostNameStr=self.mAgentHostNameStr,inUserStr=self.mAgentUserNameStr,inActivityItemDict=lActivityItemUserProcessList) lUserProcessList = __Orchestrator__.AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) @@ -543,17 +634,38 @@ lStatusStr = self.StatusCheck() if lStatusStr == "4_STARTED": self.StopSafe(inIsManualBool=False) - return self.mStatusStr
    + return self.mStatusStr
    -
    [docs] def ScheduleStatusCheckEverySeconds(self,inIntervalSecondsInt=120): - """ - Run status check every interval in second you specify. - :param inIntervalSecondsInt: Interval in seconds. Default is 120 - :return: None - """ - # Check job in threaded way - __Orchestrator__.OrchestratorScheduleGet().every(inIntervalSecondsInt).seconds.do(__Orchestrator__.OrchestratorThreadStart,self.StatusCheck)
    +
    [docs]def ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr = None, inStopSafeTimeoutSecFloat=300) -> Process: + """ + Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) + Return existing instance (if exists) or create new instance and return it. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inStartPathStr: Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + :param inStartCMDStr: CMD script to start program (if no start file is exists) + :param inStopSafeTimeoutSecFloat: Time to wait for stop safe. After that do the stop force (if process is not stopped) + :return: Process instance + """ + lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: return lProcess + else: return Process(inAgentHostNameStr=inAgentHostNameStr,inAgentUserNameStr=inAgentUserNameStr,inProcessNameWOExeStr=inProcessNameWOExeStr, + inStartPathStr=inStartPathStr,inStartCMDStr=inStartCMDStr,inStopSafeTimeoutSecFloat=inStopSafeTimeoutSecFloat)
    + +
    [docs]def ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> bool: + """ + Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :return: True - process exists in gsettings; False - else + """ + return (inAgentHostNameStr.upper(), inAgentUserNameStr.upper(), inProcessNameWOExeStr.upper()) in __Orchestrator__.GSettingsGet()["ManagersProcessDict"]
    +
    [docs]def ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> Process: """ @@ -608,7 +720,7 @@ if lProcess is not None: return lProcess.Start(inIsManualBool=inIsManualBool)
    -
    [docs]def ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) -> str: +
    [docs]def ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat = None) -> str: """ Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto @@ -617,6 +729,7 @@ :param inAgentUserNameStr: Agent user name in any case. Required to identify Process :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case :param inIsManualBool: Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + :param inStopSafeTimeoutSecFloat: Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force :return: Process status. See self.mStatusStr. Process instance has the following statuses: - 0_STOPPED @@ -652,6 +765,30 @@ lProcess = ProcessGet(inAgentHostNameStr = inAgentHostNameStr, inAgentUserNameStr = inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) if lProcess is not None: return lProcess.StopForce(inIsManualBool=inIsManualBool)
    +
    [docs]def ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str): + """ + Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don't save "STOP_SAFE" status > "STOPPED" + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusSave() + return lProcess.mStatusStr
    + +
    [docs]def ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str): + """ + Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + :return: Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + if lProcess is not None: + lProcess.StatusRestore() + return lProcess.mStatusStr
    +
    [docs]def ProcessStatusCheck(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) -> str: """ Check if process is alive. The def will save the manual flag is exists. @@ -724,6 +861,21 @@ lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, inProcessNameWOExeStr=inProcessNameWOExeStr) if lProcess is not None: lProcess.ManualStopListClear()
    + +
    [docs]def ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str,inIntervalSecondsInt: int = 120): + """ + Run status check every interval in second you specify. + + :param inAgentHostNameStr: Agent hostname in any case. Required to identify Process + :param inAgentUserNameStr: Agent user name in any case. Required to identify Process + :param inProcessNameWOExeStr: The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + :param inIntervalSecondsInt: Interval in seconds. Default is 120 + :return: None + """ + lProcess = ProcessGet(inAgentHostNameStr=inAgentHostNameStr, inAgentUserNameStr=inAgentUserNameStr, + inProcessNameWOExeStr=inProcessNameWOExeStr) + # Check job in threaded way + __Orchestrator__.OrchestratorScheduleGet().every(inIntervalSecondsInt).seconds.do(__Orchestrator__.OrchestratorThreadStart,lProcess.StatusCheck)
    diff --git a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/__Orchestrator__.html b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/__Orchestrator__.html index e4626a5c..050663f1 100644 --- a/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/__Orchestrator__.html +++ b/Wiki/ENG_Guide/html/_modules/pyOpenRPA/Orchestrator/__Orchestrator__.html @@ -188,7 +188,7 @@ import pickle import inspect import schedule -from partd import Server +#from partd import Server from . import Server from . import Timer @@ -310,7 +310,7 @@ else: raise Exception(f"__Orchestrator__.AgentActivityItemReturnGet !ATTENTION! Use this function only after Orchestrator initialization! Before orchestrator init exception will be raised.")
    -
    [docs]def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251", inGSettings=None): +
    [docs]def AgentOSCMD(inHostNameStr, inUserStr, inCMDStr, inRunAsyncBool=True, inSendOutputToOrchestratorLogsBool=True, inCMDEncodingStr="cp1251", inGSettings=None, inCaptureBool=True): """ Send CMD to OS thought the pyOpenRPA.Agent daemon. Result return to log + Orchestrator by the A2O connection @@ -321,13 +321,14 @@ :param inRunAsyncBool: True - Agent processor don't wait execution; False - Agent processor wait cmd execution :param inSendOutputToOrchestratorLogsBool: True - catch cmd execution output and send it to the Orchestrator logs; Flase - else case; Default True :param inCMDEncodingStr: Set the encoding of the DOS window on the Agent server session. Windows is beautiful :) . Default is "cp1251" early was "cp866" - need test + :param inCaptureBool: !ATTENTION! If you need to start absolutely encapsulated app - set this flag as False. If you set True - the app output will come to Agent :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lActivityItemDict = { "Def":"OSCMD", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) "ArgList":[], # Args list - "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr}, # Args dictionary + "ArgDict":{"inCMDStr":inCMDStr,"inRunAsyncBool":inRunAsyncBool, "inSendOutputToOrchestratorLogsBool": inSendOutputToOrchestratorLogsBool, "inCMDEncodingStr": inCMDEncodingStr, "inCaptureBool":inCaptureBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } @@ -533,6 +534,45 @@ #Send item in AgentDict for the futher data transmition return AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict)
    + +
    [docs]def AgentOSFileBinaryDataReceive(inHostNameStr, inUserStr, inFilePathStr): + """ + Read binary file from agent (synchronious) + + :param inGSettings: Global settings dict (singleton) + :param inHostNameStr: + :param inUserStr: + :param inFilePathStr: File path to read + :return: file data bytes + """ + lFileDataBytes = None + inGSettings = GSettingsGet() # Set the global settings + # Check thread + if OrchestratorIsInited() == False: + if inGSettings["Logger"]: inGSettings["Logger"].warning(f"AgentOSFileBinaryDataReceive run before orc init - activity will be append in the processor queue.") + lResult = { + "Def": AgentOSFileBinaryDataReceive, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList": [], # Args list + "ArgDict": {"inHostNameStr":inHostNameStr, "inUserStr":inUserStr, "inFilePathStr":inFilePathStr}, # Args dictionary + "ArgGSettings": None, # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + inGSettings["ProcessorDict"]["ActivityList"].append(lResult) + else: # In processor - do execution + lActivityItemDict = { + "Def":"OSFileBinaryDataBase64StrReceive", # def alias (look pyOpeRPA.Agent gSettings["ProcessorDict"]["AliasDefDict"]) + "ArgList":[], # Args list + "ArgDict":{"inFilePathStr":inFilePathStr}, # Args dictionary + "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) + } + + #Send item in AgentDict for the futher data transmition + lGUIDStr = AgentActivityItemAdd(inGSettings=inGSettings, inHostNameStr=inHostNameStr, inUserStr=inUserStr, inActivityItemDict=lActivityItemDict) + lFileBase64Str = AgentActivityItemReturnGet(inGUIDStr=lGUIDStr) + if lFileBase64Str is not None: lFileDataBytes = base64.b64decode(lFileBase64Str) + return lFileDataBytes
    +
    [docs]def AgentOSFileTextDataStrReceive(inHostNameStr, inUserStr, inFilePathStr, inEncodingStr="utf-8", inGSettings = None): """ Read text file in the agent GUI session @@ -629,6 +669,9 @@ """ if inLogger is None: inLogger = OrchestratorLoggerGet() lResultStr = "" + # New feature + if inRunAsyncBool == True: + inCMDStr = f"start {inCMDStr}" # Subdef to listen OS result def _CMDRunAndListenLogs(inCMDStr, inLogger): lResultStr = "" @@ -675,7 +718,7 @@ os.execl(sys.executable, os.path.abspath(__file__), *sys.argv) sys.exit(0)
    -
    [docs]def OrchestratorLoggerGet(): +
    [docs]def OrchestratorLoggerGet() -> logging.Logger: """ Get the logger from the Orchestrator @@ -683,6 +726,7 @@ """ return GSettingsGet().get("Logger",None)
    +
    [docs]def OrchestratorScheduleGet() -> schedule: """ Get the schedule (schedule.readthedocs.io) from the Orchestrator @@ -726,6 +770,24 @@ except: return False
    +
    [docs]def OrchestratorIsInited() -> bool: + """Check if Orchestrator initial actions were processed + + :return: True - orc is already inited; False - else + :rtype: bool + """ + + return Core.IsOrchestratorInitialized(inGSettings=GSettingsGet())
    + +
    [docs]def OrchestratorInitWait() -> None: + """Wait thread while orc will process initial action. + ATTENTION: DO NOT CALL THIS DEF IN THREAD WHERE ORCHESTRATOR MUST BE INITIALIZED - INFINITE LOOP + """ + lIntervalSecFloat = 0.5 + while not OrchestratorIsInited(): + time.sleep(lIntervalSecFloat)
    + +
    [docs]def OrchestratorRerunAsAdmin(): """ Check if not admin - then rerun orchestrator as administrator @@ -738,10 +800,10 @@ else: print(f"!SKIPPED! Already run as administrator!")
    -
    [docs]def OrchestratorPySearchInit(inGlobPatternStr, inDefStr = None, inDefArgNameGSettingsStr = None): +
    [docs]def OrchestratorPySearchInit(inGlobPatternStr, inDefStr = None, inDefArgNameGSettingsStr = None, inAsyncInitBool = False): """ Search the py files by the glob and do the safe init (in try except). Also add inited module in sys.modules as imported (module name = file name without extension). - + You can init CP in async way! .. code-block:: python # USAGE VAR 1 (without the def auto call) @@ -765,14 +827,14 @@ :param inGlobPatternStr: example"..\\*\\*\\*X64*.cmd" :param inDefStr: OPTIONAL The string name of the def. For backward compatibility if you need to auto call some def from initialized module :param inDefArgNameGSettingsStr: OPTIONAL The name of the GSettings argument in def (if exists) + :param inAsyncInitBool: OPTIONAL True - init py modules in many threads - parallel execution. False (default) - sequence execution :return: { "ModuleNameStr":{"PyPathStr": "", "Module": ...}, ...} """ - lResultDict = {} - lPyPathStrList = glob.glob(inGlobPatternStr) # get the file list - lL = OrchestratorLoggerGet() # get the logger - for lPyPathItemStr in lPyPathStrList: + # # # # # # # # + def __execute__(inResultDict, inPyPathItemStr, inDefStr = None, inDefArgNameGSettingsStr = None): try: + lPyPathItemStr = inPyPathItemStr lModuleNameStr = os.path.basename(lPyPathItemStr)[0:-3] lTechSpecification = importlib.util.spec_from_file_location(lModuleNameStr, lPyPathItemStr) lTechModuleFromSpec = importlib.util.module_from_spec(lTechSpecification) @@ -780,7 +842,7 @@ lTechSpecificationModuleLoader = lTechSpecification.loader.exec_module(lTechModuleFromSpec) lItemDict = {"ModuleNameStr": lModuleNameStr, "PyPathStr": lPyPathItemStr, "Module": lTechModuleFromSpec} if lL: lL.info(f"Py module {lModuleNameStr} has been successfully initialized.") - lResultDict[lModuleNameStr]=lItemDict + inResultDict[lModuleNameStr]=lItemDict # Backward compatibility to call def with gsettings when init if inDefStr is not None and inDefStr is not "": lDef = getattr(lTechModuleFromSpec, inDefStr) @@ -790,11 +852,31 @@ lDef(**lArgDict) except Exception as e: if lL: lL.exception(f"Exception when init the .py file {os.path.abspath(lPyPathItemStr)}") + # # # # # # # # + + lResultDict = {} + + lPyPathStrList = glob.glob(inGlobPatternStr) # get the file list + lL = OrchestratorLoggerGet() # get the logger + for lPyPathItemStr in lPyPathStrList: + if inAsyncInitBool == True: + # ASYNC EXECUTION + lThreadInit = threading.Thread(target=__execute__,kwargs={ + "inResultDict":lResultDict, "inPyPathItemStr": lPyPathItemStr, + "inDefStr": inDefStr, "inDefArgNameGSettingsStr": inDefArgNameGSettingsStr}, daemon=True) + lThreadInit.start() + else: + # SYNC EXECUTION + __execute__(inResultDict=lResultDict, inPyPathItemStr=lPyPathItemStr, inDefStr = inDefStr, inDefArgNameGSettingsStr = inDefArgNameGSettingsStr) return lResultDict
    [docs]def OrchestratorSessionSave(inGSettings=None): """ Orchestrator session save in file + (from version 1.2.7) + _SessionLast_GSettings.pickle (binary) + + (above the version 1.2.7) _SessionLast_RDPList.json (encoding = "utf-8") _SessionLast_StorageDict.pickle (binary) @@ -805,26 +887,38 @@ lL = inGSettings["Logger"] try: # Dump RDP List in file json - lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") - lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file - lFile.close() # Close the file - if inGSettings is not None: + #lFile = open("_SessionLast_RDPList.json", "w", encoding="utf-8") + #lFile.write(json.dumps(inGSettings["RobotRDPActive"]["RDPList"])) # dump json to file + #lFile.close() # Close the file + #if inGSettings is not None: + # if lL: lL.info( + # f"Orchestrator has dump the RDP list before the restart.") + ## _SessionLast_StorageDict.pickle (binary) + #if "StorageDict" in inGSettings: + # with open('_SessionLast_StorageDict.pickle', 'wb') as lFile: + # pickle.dump(inGSettings["StorageDict"], lFile) + # if lL: lL.info( + # f"Orchestrator has dump the StorageDict before the restart.") + + #SessionLast + lDumpDict = {"StorageDict":inGSettings["StorageDict"], "ManagersProcessDict":inGSettings["ManagersProcessDict"], + "RobotRDPActive":{"RDPList": inGSettings["RobotRDPActive"]["RDPList"]}} + with open('_SessionLast_GSettings.pickle', 'wb') as lFile: + pickle.dump(lDumpDict, lFile) if lL: lL.info( - f"Orchestrator has dump the RDP list before the restart.") - # _SessionLast_StorageDict.pickle (binary) - if "StorageDict" in inGSettings: - with open('_SessionLast_StorageDict.pickle', 'wb') as lFile: - pickle.dump(inGSettings["StorageDict"], lFile) - if lL: lL.info( - f"Orchestrator has dump the StorageDict before the restart.") + f"Orchestrator has dump the GSettings (new dump mode from v1.2.7) before the restart.") + except Exception as e: if lL: lL.exception(f"Exception when dump data before restart the Orchestrator") return True
    [docs]def OrchestratorSessionRestore(inGSettings=None): """ - Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working directory. if exist - load into gsettings - # _SessionLast_StorageDict.pickle (binary) + Check _SessioLast... files in working directory. if exist - load into gsettings + (from version 1.2.7) + _SessionLast_GSettings.pickle (binary) + + (above the version 1.2.7) _SessionLast_RDPList.json (encoding = "utf-8") _SessionLast_StorageDict.pickle (binary) @@ -850,7 +944,19 @@ Server.__ComplexDictMerge2to1Overwrite__(in1Dict=inGSettings["StorageDict"], in2Dict=lStorageDictDumpDict) # Merge dict 2 into dict 1 if lL: lL.warning(f"StorageDict was restored from previous Orchestrator session") - os.remove("_SessionLast_StorageDict.pickle") # remove the temp file
    + os.remove("_SessionLast_StorageDict.pickle") # remove the temp file + # _SessionLast_Gsettings.pickle (binary) + if os.path.exists("_SessionLast_GSettings.pickle"): + if "StorageDict" not in inGSettings: + inGSettings["StorageDict"] = {} + if "ManagersProcessDict" not in inGSettings: + inGSettings["ManagersProcessDict"] = {} + with open('_SessionLast_GSettings.pickle', 'rb') as lFile: + lStorageDictDumpDict = pickle.load(lFile) + Server.__ComplexDictMerge2to1Overwrite__(in1Dict=inGSettings, + in2Dict=lStorageDictDumpDict) # Merge dict 2 into dict 1 + if lL: lL.warning(f"GSettings was restored from previous Orchestrator session") + os.remove("_SessionLast_GSettings.pickle") # remove the temp file
    [docs]def UACKeyListCheck(inRequest, inRoleKeyList) -> bool: """ @@ -926,7 +1032,7 @@ # # # # # # # # # # # # # # # # # # # # # # # -
    [docs]def WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream", inGSettings = None): +
    [docs]def WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr="application/octet-stream", inGSettings = None, inUACBool = None): """ Connect URL to DEF "inMethodStr":"GET|POST", @@ -936,11 +1042,12 @@ "inDef": None #Function with str result :param inGSettings: Global settings dict (singleton) - :param inMethodStr: - :param inURLStr: - :param inMatchTypeStr: - :param inDef: - :param inContentTypeStr: + :param inMethodStr: "GET|POST", + :param inURLStr: example "/index", #URL of the request + :param inMatchTypeStr: #"BeginWith|Contains|Equal|EqualCase", + :param inDef: def arg allowed list: 2:[inRequest, inGSettings], 1: [inRequest], 0: [] + :param inContentTypeStr: default: "application/octet-stream" + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lURLItemDict = { @@ -951,24 +1058,27 @@ #"ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", # Absolute or relative path "ResponseContentType": inContentTypeStr, #HTTP Content-type - "ResponseDefRequestGlobal": inDef #Function with str result + "ResponseDefRequestGlobal": inDef, #Function with str result + "UACBool": inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
    -
    [docs]def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings = None): +
    [docs]def WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings = None, inUACBool = None): """ Connect URL to Folder "inMethodStr":"GET|POST", "inURLStr": "/Folder/", #URL of the request "inMatchTypeStr": "", #"BeginWith|Contains|Equal|EqualCase", "inFolderPathStr": "", #Absolute or relative path + "inUACBool" :param inGSettings: Global settings dict (singleton) :param inMethodStr: :param inURLStr: :param inMatchTypeStr: :param inFolderPathStr: + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings # Check if last symbol is "/" - append if not exist @@ -983,11 +1093,12 @@ "ResponseFolderPath": lFolderPathStr, # Absolute or relative path "ResponseContentType": "application/octet-stream", #HTTP Content-type #"ResponseDefRequestGlobal": inDef #Function with str result + "UACBool": inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
    -
    [docs]def WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream", inGSettings = None): +
    [docs]def WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr="application/octet-stream", inGSettings = None, inUACBool = None): """ Connect URL to File "inMethodStr":"GET|POST", @@ -1001,6 +1112,7 @@ :param inMatchTypeStr: :param inFilePathStr: :param inContentTypeStr: + :param inUACBool: default: None; True - check user access before do this URL item. None - get Server flag if ask user """ inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lURLItemDict = { @@ -1011,6 +1123,7 @@ #"ResponseFolderPath": os.path.abspath(inFilePathStr), # Absolute or relative path "ResponseContentType": inContentTypeStr, #HTTP Content-type #"ResponseDefRequestGlobal": inDef #Function with str result + "UACBool":inUACBool } inGSettings["ServerDict"]["URLList"].append(lURLItemDict)
    @@ -1054,7 +1167,7 @@ if inJSInitGeneratorDef is not None: lCPManager.mBackwardCompatibilityJSDef = inJSInitGeneratorDef
    -
    [docs]def WebAuditMessageCreate(inRequest, inOperationCodeStr="-", inMessageStr="-"): +
    [docs]def WebAuditMessageCreate(inRequest=None, inOperationCodeStr="-", inMessageStr="-"): """ Create message string with request user details (IP, Login etc...). Very actual for IT security in big company. @@ -1071,12 +1184,13 @@ # Log the WebAudit message lLogger.info(lWebAuditMessageStr) - :param inRequest: HTTP request handler + :param inRequest: HTTP request handler. Optional if call def from request thread :param inOperationCodeStr: operation code in string format (actual for IT audit in control panels) :param inMessageStr: additional message after :return: format "WebAudit :: DOMAIN\\USER@101.121.123.12 :: operation code :: message" """ try: + if inRequest is None: inRequest = WebRequestGet() lClientIPStr = inRequest.client_address[0] lUserDict = WebUserInfoGet(inRequest=inRequest) lDomainUpperStr = lUserDict["DomainUpperStr"] @@ -1087,54 +1201,58 @@ lResultStr = inMessageStr return lResultStr
    -
    [docs]def WebRequestParseBodyBytes(inRequest): +
    [docs]def WebRequestParseBodyBytes(inRequest=None): """ Extract the body in bytes from the request - :param inRequest: inRequest from the server + :param inRequest: inRequest from the server. Optional if call def from request thread :return: Bytes or None """ + if inRequest is None: inRequest = WebRequestGet() lBodyBytes=None if inRequest.headers.get('Content-Length') is not None: lInputByteArrayLength = int(inRequest.headers.get('Content-Length')) lBodyBytes = inRequest.rfile.read(lInputByteArrayLength) return lBodyBytes
    -
    [docs]def WebRequestParseBodyStr(inRequest): +
    [docs]def WebRequestParseBodyStr(inRequest=None): """ Extract the body in str from the request - :param inRequest: inRequest from the server + :param inRequest: inRequest from the server. Optional if call def from request thread :return: str or None """ - + if inRequest is None: inRequest = WebRequestGet() return WebRequestParseBodyBytes(inRequest=inRequest).decode('utf-8')
    -
    [docs]def WebRequestParseBodyJSON(inRequest): +
    [docs]def WebRequestParseBodyJSON(inRequest=None): """ Extract the body in dict/list from the request - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: dict or list """ + if inRequest is None: inRequest = WebRequestGet() return json.loads(WebRequestParseBodyStr(inRequest=inRequest))
    -
    [docs]def WebRequestParsePath(inRequest): +
    [docs]def WebRequestParsePath(inRequest=None): """ Parse the request - extract the url. Example: /pyOpenRPA/Debugging/DefHelper/... - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: Str, Example: /pyOpenRPA/Debugging/DefHelper/... """ + if inRequest is None: inRequest = WebRequestGet() return urllib.parse.unquote(inRequest.path)
    -
    [docs]def WebRequestParseFile(inRequest): +
    [docs]def WebRequestParseFile(inRequest=None): """ Parse the request - extract the file (name, body in bytes) - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: (FileNameStr, FileBodyBytes) or (None, None) """ + if inRequest is None: inRequest = WebRequestGet() lResultTurple=(None,None) if inRequest.headers.get('Content-Length') is not None: lInputByteArray = WebRequestParseBodyBytes(inRequest=inRequest) @@ -1154,45 +1272,61 @@ return lResultTurple
    -
    [docs]def WebRequestResponseSend(inRequest, inResponeStr): +
    [docs]def WebRequestResponseSend(inResponeStr, inRequest=None): """ Send response for the request + + :param inRequest: inRequest from the server. Optional if call def from request thread :return: """ + if inRequest is None: inRequest = WebRequestGet() inRequest.OpenRPAResponseDict["Body"] = bytes(inResponeStr, "utf8")
    -
    [docs]def WebUserInfoGet(inRequest): + +
    [docs]def WebRequestGet(): + """ + Return the web request instance if current thread was created by web request from client. else return None + + """ + lCurrentThread = threading.current_thread() + if hasattr(lCurrentThread, "request"): + return lCurrentThread.request
    + +
    [docs]def WebUserInfoGet(inRequest=None): """ Return User info about request - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: {"DomainUpperStr": "", "UserNameUpperStr": ""} """ + if inRequest is None: inRequest = WebRequestGet() lDomainUpperStr = inRequest.OpenRPA["Domain"].upper() lUserUpperStr = inRequest.OpenRPA["User"].upper() return {"DomainUpperStr": lDomainUpperStr, "UserNameUpperStr": lUserUpperStr}
    -
    [docs]def WebUserIsSuperToken(inRequest, inGSettings = None): +
    [docs]def WebUserIsSuperToken(inRequest = None, inGSettings = None): """ Return bool if request is authentificated with supetoken (token which is never expires) - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :param inGSettings: Global settings dict (singleton) :return: bool True - is supertoken; False - is not supertoken """ + if inRequest is None: inRequest = WebRequestGet() inGSettings = GSettingsGet(inGSettings=inGSettings) # Set the global settings lIsSuperTokenBool = False # Get Flag is supertoken (True|False) lIsSuperTokenBool = inGSettings.get("ServerDict", {}).get("AccessUsers", {}).get("AuthTokensDict", {}).get(inRequest.OpenRPA["AuthToken"], {}).get("FlagDoNotExpire", False) return lIsSuperTokenBool
    -
    [docs]def WebUserUACHierarchyGet(inRequest): +
    [docs]def WebUserUACHierarchyGet(inRequest = None): """ Return User UAC Hierarchy DICT Return {...} - :param inRequest: + :param inRequest: inRequest from the server. Optional if call def from request thread :return: UAC Dict {} """ + if inRequest is None: inRequest = WebRequestGet() return inRequest.UserRoleHierarchyGet()
    @@ -1585,12 +1719,16 @@ """ lL = OrchestratorLoggerGet() lL.info(f"ActivityItem aliases: start to load sys.modules") - for lModuleItemStr in sys.modules: - lModuleItem = sys.modules[lModuleItemStr] + lSysModulesSnapshot = copy.copy(sys.modules) # Actual when start from jupyter + for lModuleItemStr in lSysModulesSnapshot: + lModuleItem = lSysModulesSnapshot[lModuleItemStr] for lDefItemStr in dir(lModuleItem): - lDefItem = getattr(lModuleItem,lDefItemStr) - if callable(lDefItem) and not lDefItemStr.startswith("_"): - ActivityItemDefAliasCreate(inDef=lDefItem, inAliasStr=f"{lModuleItemStr}.{lDefItemStr}") + try: + lDefItem = getattr(lModuleItem,lDefItemStr) + if callable(lDefItem) and not lDefItemStr.startswith("_"): + ActivityItemDefAliasCreate(inDef=lDefItem, inAliasStr=f"{lModuleItemStr}.{lDefItemStr}") + except ModuleNotFoundError: + pass lL.info(f"ActivityItem aliases: finish to load sys.modules")
    [docs]def ActivityItemDefAliasUpdate(inDef, inAliasStr, inGSettings = None): @@ -2085,7 +2223,7 @@ # # # # # # # # # # # # # # # # # # # # # # #
    [docs]def RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr="127.0.0.1", inPortInt = 3389, inWidthPXInt = 1680, inHeightPXInt = 1050, - inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None): + inUseBothMonitorBool = False, inDepthBitInt = 32, inSharedDriveList=None, inRedirectClipboardBool=True): """ Create RDP connect dict item/ Use it connect/reconnect (Orchestrator.RDPSessionConnect) @@ -2108,6 +2246,7 @@ # "Host": "127.0.0.1", "Port": "3389", "Login": "USER_99", "Password": "USER_PASS_HERE", # "Screen": { "Width": 1680, "Height": 1050, "FlagUseAllMonitors": False, "DepthBit": "32" }, # "SharedDriveList": ["c"], + # "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else # ###### Will updated in program ############ # "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" # "SessionIsWindowExistBool": False, "SessionIsWindowResponsibleBool": False, "SessionIsIgnoredBool": False @@ -2122,6 +2261,7 @@ :param inUseBothMonitorBool: True - connect to the RDP with both monitors. False - else case :param inDepthBitInt: Remote desktop bitness. Available: 32 or 24 or 16 or 15, example 32 :param inSharedDriveList: Host local disc to connect to the RDP session. Example: ["c", "d"] + :param inRedirectClipboardBool: # True - share clipboard to RDP; False - else :return: { "Host": inHostStr, # Host address, example "77.77.22.22" @@ -2136,6 +2276,7 @@ "DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32" }, "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"] + "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, @@ -2147,6 +2288,8 @@ """ if inSharedDriveList is None: inSharedDriveList = ["c"] + if inPortInt is None: inPortInt = 3389 + if inRedirectClipboardBool is None: inRedirectClipboardBool = True lRDPTemplateDict= { # Init the configuration item "Host": inHostStr, # Host address, example "77.77.22.22" "Port": str(inPortInt), # RDP Port, example "3389" @@ -2159,7 +2302,8 @@ "FlagUseAllMonitors": inUseBothMonitorBool, # True or False, example False "DepthBit": str(inDepthBitInt) # "32" or "24" or "16" or "15", example "32" }, - "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"] + "SharedDriveList": inSharedDriveList, # List of the Root sesion hard drives, example ["c"], + "RedirectClipboardBool": inRedirectClipboardBool, # True - share clipboard to RDP; False - else ###### Will updated in program ############ "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" "SessionIsWindowExistBool": False, @@ -2184,7 +2328,7 @@ #for lItemKeyStr in inGSettings["RobotRDPActive"]["RDPList"]: # lItemDict = inGSettings["RobotRDPActive"]["RDPList"][lItemKeyStr] -
    [docs]def RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings = None): +
    [docs]def RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings = None, inRedirectClipboardBool=True): """ Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if RDP key is already exists 2 way of the use @@ -2223,7 +2367,7 @@ "Def": RDPSessionConnect, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) "ArgList": [], # Args list "ArgDict": {"inRDPSessionKeyStr": inRDPSessionKeyStr, "inRDPTemplateDict":inRDPTemplateDict, "inHostStr": inHostStr, "inPortStr": inPortStr, - "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr}, # Args dictionary + "inLoginStr": inLoginStr, "inPasswordStr": inPasswordStr, "inRedirectClipboardBool": inRedirectClipboardBool}, # Args dictionary "ArgGSettings": "inGSettings", # Name of GSettings attribute: str (ArgDict) or index (for ArgList) "ArgLogger": None # Name of GSettings attribute: str (ArgDict) or index (for ArgList) } @@ -2234,7 +2378,7 @@ # Var 2 - backward compatibility if lRDPConfigurationItem is None: lRDPConfigurationItem = RDPTemplateCreate(inLoginStr=inLoginStr, inPasswordStr=inPasswordStr, - inHostStr=inHostStr, inPortInt = int(inPortStr)) # ATTENTION - dont connect if RDP session is exist + inHostStr=inHostStr, inPortInt = int(inPortStr), inRedirectClipboardBool=inRedirectClipboardBool) # ATTENTION - dont connect if RDP session is exist # Start the connect if inRDPSessionKeyStr not in inGSettings["RobotRDPActive"]["RDPList"]: inGSettings["RobotRDPActive"]["RDPList"][inRDPSessionKeyStr] = lRDPConfigurationItem # Add item in RDPList @@ -2705,7 +2849,6 @@ while True: time.sleep(inGSettings["Autocleaner"]["IntervalSecFloat"]) # Wait for the next iteration lL = inGSettings["Logger"] - if lL: lL.info(f"Autocleaner is running") # Info lNowDatetime = datetime.datetime.now() # Get now time # Clean old items in Client > Session > TechnicalSessionGUIDCache lTechnicalSessionGUIDCacheNew = {} @@ -2830,6 +2973,8 @@ lRobotRDPActiveThread.start() # Start the thread execution. if lL: lL.info("Robot RDP active has been started") #Logging + + # Init autocleaner in another thread lAutocleanerThread = threading.Thread(target= GSettingsAutocleaner, kwargs={"inGSettings":gSettingsDict}) lAutocleanerThread.daemon = True # Run the thread in daemon mode. @@ -2862,13 +3007,18 @@ lSchedulerThread.start() # Start the thread execution. if lL: lL.info("Scheduler (old) loop start") #Logging - # Schedule (new) loop lScheduleThread = threading.Thread(target= __schedule_loop__) lScheduleThread.daemon = True # Run the thread in daemon mode. lScheduleThread.start() # Start the thread execution. - if lL: lL.info("Schedule module (new) loop start") #Logging
    - + if lL: lL.info("Schedule module (new) loop start") #Logging + + # Restore state for process + for lProcessKeyTuple in inGSettings["ManagersProcessDict"]: + lProcess = inGSettings["ManagersProcessDict"][lProcessKeyTuple] + lProcess.StatusCheckIntervalRestore() + lThread = threading.Thread(target= lProcess.StatusRestore) + lThread.start()
    def __schedule_loop__(): while True: diff --git a/Wiki/ENG_Guide/html/_sources/03_Copyrights_Contacts.rst.txt b/Wiki/ENG_Guide/html/_sources/03_Copyrights_Contacts.rst.txt index 85f88700..b02d3ee3 100644 --- a/Wiki/ENG_Guide/html/_sources/03_Copyrights_Contacts.rst.txt +++ b/Wiki/ENG_Guide/html/_sources/03_Copyrights_Contacts.rst.txt @@ -5,7 +5,7 @@ #################################### pyOpenRPA is created by Ivan Maslov (Russia). -Use it absolutely for free! +Hosted by PYOPENRPA LLC (Russia) My purpose is to create #IT4Business models in the companies. I can help you to create the new #IT4Business in your company. diff --git a/Wiki/ENG_Guide/html/_sources/index.rst.txt b/Wiki/ENG_Guide/html/_sources/index.rst.txt index 01ad1aae..884bcbac 100644 --- a/Wiki/ENG_Guide/html/_sources/index.rst.txt +++ b/Wiki/ENG_Guide/html/_sources/index.rst.txt @@ -14,25 +14,19 @@ Welcome to pyOpenRPA's wiki ! ATTENTION ! pyOpenRPA works only on MS Windows 7+/Server 2008+. Guys from Unix/Mac - sorry. We will come to you soon :) -************************************************** -Donate -************************************************** - -pyOpenRPA is absolutely non-commercial project. - -Please donate some $ if pyOpenRPA project is actual for you. Link to online donations. -https://yoomoney.ru/to/4100115560661986 ************************************************** About ************************************************** Dear RPA-tors. Let me congratulate you with great change in the RPA world. Since 2019 the first enterprise level open source RPA platform is here! +pyOpenRPA is absolutely open source commercial project. Hosted by LLC PYOPENRPA (RUSSIA) + The pyOpenRPA - free, fast and reliable Powerful OpenSource RPA tool for business (based on python 3). Best performance and absolutely free! The pyOpenRPA is based on Python and using well known OpenSource solutions such as Selenium, OpenCV, Win32, UI automation and others. Thanks to it we were able to create consolidated platform with all possible features. -The pyOpenRPA is distributed under the MIT license which allows you to use it in any way you want and any time you need without any restrictions. +The pyOpenRPA is distributed under the PYOPENRPA license. At the time of this writing the pyOpenRPA is successfully using in several big Russian companies. Companies in which it was decided to develop own RPA division with no dependencies on expensive licenses. ************************************************** diff --git a/Wiki/ENG_Guide/html/genindex.html b/Wiki/ENG_Guide/html/genindex.html index a9f24948..91a09427 100644 --- a/Wiki/ENG_Guide/html/genindex.html +++ b/Wiki/ENG_Guide/html/genindex.html @@ -193,6 +193,7 @@ | G | I | J + | K | M | O | P @@ -236,6 +237,8 @@
  • AgentOSFileBinaryDataBase64StrReceive() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
  • AgentOSFileBinaryDataBytesCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__) +
  • +
  • AgentOSFileBinaryDataReceive() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
  • AgentOSFileSend() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
  • @@ -253,7 +256,7 @@

    C

    @@ -261,7 +264,7 @@

    D

    @@ -293,11 +296,11 @@
      -
    • InitJSJinja2TemplatePathSet() (pyOpenRPA.Orchestrator.Managers.ControlPanel.ControlPanel method), [1] +
    • InitJSJinja2TemplatePathSet() (pyOpenRPA.Orchestrator.Managers.ControlPanel.ControlPanel method), [1]
    • IsStopSafe() (in module pyOpenRPA.Tools.StopSafe)
    • @@ -307,7 +310,7 @@

      J

        @@ -318,16 +321,24 @@
      +

      K

      + + +
      +

      M

      @@ -358,21 +369,25 @@

      O

      @@ -568,8 +595,6 @@
        -
      • StatusCheckStopForce() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • StatusCheckStopSafe() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • +
      • StatusRestore() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1]
      • -
      • StatusCheckStopSafe() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • StatusSave() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1]
      • -
      • StopForce() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • StopForce() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1]
      • -
      • StopForceCheck() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • StopForceCheck() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1]
      • -
      • StopSafe() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • StopSafe() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1]
      • -
      • StopSafeCheck() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1] +
      • StopSafeCheck() (pyOpenRPA.Orchestrator.Managers.Process.Process method), [1]
      • StorageRobotExists() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
      • @@ -668,6 +699,8 @@
      • WebCPUpdate() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
      • WebListenCreate() (in module pyOpenRPA.Orchestrator.__Orchestrator__) +
      • +
      • WebRequestGet() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
      • WebRequestParseBodyBytes() (in module pyOpenRPA.Orchestrator.__Orchestrator__)
      • diff --git a/Wiki/ENG_Guide/html/index.html b/Wiki/ENG_Guide/html/index.html index 12458958..f10b1d05 100644 --- a/Wiki/ENG_Guide/html/index.html +++ b/Wiki/ENG_Guide/html/index.html @@ -191,19 +191,14 @@ ModalGuide.png

        by Ivan Maslov (Russia) - see 3. Copyrights & Contacts.

        ! ATTENTION ! pyOpenRPA works only on MS Windows 7+/Server 2008+. Guys from Unix/Mac - sorry. We will come to you soon :)

        -

        About

        Dear RPA-tors. Let me congratulate you with great change in the RPA world. Since 2019 the first enterprise level open source RPA platform is here!

        +

        pyOpenRPA is absolutely open source commercial project. Hosted by LLC PYOPENRPA (RUSSIA)

        The pyOpenRPA - free, fast and reliable Powerful OpenSource RPA tool for business (based on python 3). Best performance and absolutely free!

        The pyOpenRPA is based on Python and using well known OpenSource solutions such as Selenium, OpenCV, Win32, UI automation and others. Thanks to it we were able to create consolidated platform with all possible features. -The pyOpenRPA is distributed under the MIT license which allows you to use it in any way you want and any time you need without any restrictions. +The pyOpenRPA is distributed under the PYOPENRPA license. At the time of this writing the pyOpenRPA is successfully using in several big Russian companies. Companies in which it was decided to develop own RPA division with no dependencies on expensive licenses.

        diff --git a/Wiki/ENG_Guide/html/objects.inv b/Wiki/ENG_Guide/html/objects.inv index df87ac0628414787806f308048f703b12bded99e..16f5f6905dd79fca1cd69e8e4d15976b4553c793 100644 GIT binary patch delta 2174 zcmV-^2!Z$c5a1Dzd4Jn>;y4zD?|BNZ)rY&dIVDxqcZ5`Rs15}Slg_1=VsAhbV|gSw z74GIW=Jn=DX3GaKU;>dXU(~ADvi@)Dw6!JKM4L$j;cPmv2TKU0?YhbTu4_e`KiABU z1F+gwa_^6J*S&*A% z@XZ~OS|YyCCI74tt}!skjRj6y2E zJKt&eFVe;CQpu7pwOKNUbrhf(QzBPR+QU9-O}|~qn$!-}K#!4+mZ)aZwWLz7v?pO& zGkIsxGND_G3D`lj=)H>c87w%6)%|?@P^lLB_1SSx#D7Y+IROIMZzUKu%#BaQ-P~n z)^0WCD3rioNuYWZ<66C`)2n8JcF*ectAbgiJ#0JI)#1A1w{xS-uF}q*w5bkx)kEre zsw&O;r3z7d7u&;=S`?V z)bK$Ek+25zMi+V%3v0kXRe|pZqZ)J#cjVwRYC%?T{a;^!ZyfMg=jW3l_MS)@f%~N#~j9S_Yg$D6%+%D1r37ch^o_@ zp{WzkBGS;%ItE=3^VY_ifsA?BJn`JTReuRr_TEIT@*jDlq6x4`Pxdaj{6z$l_}EXa6C{)Qw{ z{f7iG81$H>yhaO$S(Ef>5R2xhj-eu&n^aMmgO8(&l0f_eoGvzoI0oIr#F@dGNq?9Q zv*S54l)|NPfZCqS&r;2T#K2$EumO%L68OeJ*w2>0O*xz5<#gi!!E048-I}a}HD6&k={v@nEQ`D_bAD(&B!>1G^3$1g)7+4| z$RH+T$BVcAD+5?$Qh&F@nQX(D3x63)c?OqBJD%FBLyaa`2Db*M8@WQaW31oJ?xvY* z!;o|qho&Jc48b#9lshWCW|S%;1V* z2N^Sr?r=IcF3{9qDCSBNXjrm)5fID=*e84j(8g@QiQ0OyhO2Y=xei1|C| z1}Bd^M&3@0>U+6??>_;&1ZEcN=4KD&8^{u7?3qb}2OmX@sR-|JFwC}o%=Iv2IB^7g z&R!ri7&IWqu(|-AewSyaF`nRg5c^b)(F0(^8yt>E8cq#X7V4`$1)hmhYoNR(Ncj$(w$Js-+c0dX95RIPW4%o|=~1{~2Ax8J5W6|eBnPLRdkS3b&~EgLl*ADB0Nxx#QIOp-f`51%O{(gmgaZL~ zZM9?S30IwYs9h$)-CM>26Y{v&Cq@YPXC9-wxOQ{%?|5sHcawm*Zlm3A%!|c6U9JNB zSF63)MTRRH_$r=JeWm1ev8wuReS_$P^6~6mS-IX}lS>xgL^U9p2g)w8d>ZNk;>cu^1z$TI|Iq<<-7-)26E+xwxWqVdPS;G_z#E?WC> zXlE~$ElRxi+_o98XM3DRs(D*w0q$wR^QQIOW&X-jzUz5fuH@luQ*BeMJn4YcPx~0g z@TUR0XHwn%wto+zZ#&Stc5x|iS}I9SRjzGIJ$hxS^@BC=5=Wo?DoLyCyB90}>gBY2 z`3ExAO>i71mGs zLR&cZlvo>WuJVLbB>iwEv%8RbCR=(6y`NhoG01+#^j?}LjZv&A4u{tN0SawtLfURd AwEzGB delta 2071 zcmV+y2TPLmPkcK^S26tCVX%>f`q<<{5I%QgKiF~C2T+)#6 z)fJKCk_b5OIgH1NvN({uExMQm@(Uf^1FZjydx{h=Uhnrb2`Nrv?|+JI0}AGqd)T**>08WW95u)S#$@m;~K^ESXO2b+nO=WRZ$(1_2PQ;nl5# zo^m?JM}ELBM-r|#w(2lIx7X^hV+jw0YVpoAcoTdCtM)77o|dyX*JnCpriHxFKsxx4 z8sylIWPdCM3-F$z9Wy@Zn4#N^KkLGhqG~yfwjMI6UJ}mXmd&-eoeEst zvUaO6wVSnbS{2M9?V;Q0Rfp@2;m(;ByGm)#s}S|O)`cckyJkOC0O}W0!$IwDm>e5a zXt<`G5K05u(S_~>Q5x`W75H`(qe0j3Qfrr}fq(01;ffQ02J}o9nyj{Ti+Zj@EqFl< zj;K1l8JapmDIyIGt$on-3zkVV!O>B(LNm!E8!B(EfhW{T@H(3WqVJ(~nJ&~~?M&So=ngm;{X*F*h=D;Y_{xlj|G zhpVa(xLJ_#lKcrt;P{gSF&K1UrMyN9hksd<^l=c2=BSRLBAT1yP?>{|ql=P2`~*&C zD?=QE?qTFiV96wm2icat8A{>OIAAFH{*s2qIoK+=hBh@ci! zZ>SEN>U8uJQSX}^5zfv*N7!UB+k9{id&;ww@8yt>E8jKBA7V4`$1%IL0GECs_ z81IY;7jVh^RdO)V6imJH-xMqRy1_I`1wKu1)TaQ3Iob4S?f?l<_mkT!vPTK?5RIPW4%qe&|x@Z23=@^5StmtBnKyj8wyvuhWvDiy!d=r!WPe}R8$a}&2v=V-weUj?R{`*5KZ=6vmJ!7La8y+nB^(H_ zYpeZCPq^y)3bhABxO>T1U_u_3;=~96f94Uiiz_QP|K_YFc{d4X>o(f`%Dh_u1pV*xoYVJ3pWOmR& zOwl>-d?S(v3rW%u!NRtLU2^nWqV&4Np_jxD2(>%z4f{1^!%#?d3MZuh#b=&%(y?}u zk~~(&w(zDUnMKpvEq|1pFyh~lXe;yjZkn%aTk3jAqE`FV9k6J{)yNgD`evDUQfy@| zeIgY)wXIE>^})(hTc`Gkw@x_ufENYffILF*hV*ZR?CZ=2aa(^>q-gy9UmH<{R~N1A zIJC1B%N8YGdv4tf*t0!OBh|dEvHpZRGwLmNLEgd)cQPkT`DQ)a6$5fKHta-Df)6&}BOI$Z!p_;RYdMK_=Z6Wk1 zl+m{zST|VlW`E0(u}_&QW%{cqvzu001^GkW((>%5t}CNZpe=`3%)aj_^J=tm-`c`4 zPO&&I+s-Awn`RoSpRKvbMu031q;;0#hEn3HH^9u$0_y<+k1fGUzN*WnhX4NgS6IL3 z6K&z#Q(|qjI>{4Kk@TC4% # USAGE VAR 1 (without the def auto call) +> # Autoinit control panels starts with + +> ``` +> CP_ +> ``` -``` -# USAGE VAR 1 (without the def auto call) -# Autoinit control panels starts with CP_ -Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\CP_*.py") - -# USAGE VAR 2 (with the def auto call) - for the backward compatibility CP for the Orchestrator ver. < 1.2.7 -# Autoinit control panels starts with CP_ -Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings") - -# INFO: The code above will replace the code below -## !!! For Relative import !!! CP Version Check -try: - sys.path.insert(0,os.path.abspath(os.path.join(r""))) - from ControlPanel import CP_VersionCheck - CP_VersionCheck.SettingsUpdate(inGSettings=gSettings) -except Exception as e: - gSettings["Logger"].exception(f"Exception when init CP. See below.") -``` + +> Orchestrator.OrchestratorPySearchInit(inGlobPatternStr=”ControlPanelCP_\*.py”) + +> # USAGE VAR 2 (with the def auto call) - for the backward compatibility CP for the Orchestrator ver. < 1.2.7 +> # Autoinit control panels starts with + +> ``` +> CP_ +> ``` + + +> Orchestrator.OrchestratorPySearchInit(inGlobPatternStr=”ControlPanelCP_\*.py”, inDefStr=”SettingsUpdate”, inDefArgNameGSettingsStr=”inGSettings”) + +> # INFO: The code above will replace the code below +> ## !!! For Relative import !!! CP Version Check +> try: + +> > sys.path.insert(0,os.path.abspath(os.path.join(r””))) +> > from ControlPanel import CP_VersionCheck +> > CP_VersionCheck.SettingsUpdate(inGSettings=gSettings) + +> except Exception as e: + +> gSettings[“Logger”].exception(f”Exception when init CP. See below.”) * **Parameters** @@ -1361,6 +1446,9 @@ except Exception as e: * **inDefArgNameGSettingsStr** – OPTIONAL The name of the GSettings argument in def (if exists) + * **inAsyncInitBool** – OPTIONAL True - init py modules in many threads - parallel execution. False (default) - sequence execution + + * **Returns** @@ -1401,11 +1489,15 @@ Fro example you can use: ### pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorSessionRestore(inGSettings=None) -Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working directory. if exist - load into gsettings -# _SessionLast_StorageDict.pickle (binary) +Check _SessioLast… files in working directory. if exist - load into gsettings +(from version 1.2.7) -> _SessionLast_RDPList.json (encoding = “utf-8”) -> _SessionLast_StorageDict.pickle (binary) +> _SessionLast_GSettings.pickle (binary) + +(above the version 1.2.7) + + _SessionLast_RDPList.json (encoding = “utf-8”) + _SessionLast_StorageDict.pickle (binary) * **Parameters** @@ -1421,6 +1513,11 @@ Check _SessionLast_RDPList.json and _SessionLast_StorageDict.pickle in working d ### pyOpenRPA.Orchestrator.__Orchestrator__.OrchestratorSessionSave(inGSettings=None) Orchestrator session save in file +(from version 1.2.7) + +> _SessionLast_GSettings.pickle (binary) + +(above the version 1.2.7) _SessionLast_RDPList.json (encoding = “utf-8”) _SessionLast_StorageDict.pickle (binary) @@ -1958,7 +2055,7 @@ lResultDict = Orchestrator.RDPSessionCMDRun( -### pyOpenRPA.Orchestrator.__Orchestrator__.RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings=None) +### pyOpenRPA.Orchestrator.__Orchestrator__.RDPSessionConnect(inRDPSessionKeyStr, inRDPTemplateDict=None, inHostStr=None, inPortStr=None, inLoginStr=None, inPasswordStr=None, inGSettings=None, inRedirectClipboardBool=True) Create new RDPSession in RobotRDPActive. Attention - activity will be ignored if RDP key is already exists 2 way of the use @@ -2342,7 +2439,7 @@ DEVELOPING, MAYBE NOT USEFUL Check RDP Session responsibility TODO NEED DEV + TE -### pyOpenRPA.Orchestrator.__Orchestrator__.RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr='127.0.0.1', inPortInt=3389, inWidthPXInt=1680, inHeightPXInt=1050, inUseBothMonitorBool=False, inDepthBitInt=32, inSharedDriveList=None) +### pyOpenRPA.Orchestrator.__Orchestrator__.RDPTemplateCreate(inLoginStr, inPasswordStr, inHostStr='127.0.0.1', inPortInt=3389, inWidthPXInt=1680, inHeightPXInt=1050, inUseBothMonitorBool=False, inDepthBitInt=32, inSharedDriveList=None, inRedirectClipboardBool=True) Create RDP connect dict item/ Use it connect/reconnect (Orchestrator.RDPSessionConnect) ``` @@ -2363,6 +2460,7 @@ lRDPItemDict = Orchestrator.RDPTemplateCreate( # "Host": "127.0.0.1", "Port": "3389", "Login": "USER_99", "Password": "USER_PASS_HERE", # "Screen": { "Width": 1680, "Height": 1050, "FlagUseAllMonitors": False, "DepthBit": "32" }, # "SharedDriveList": ["c"], +# "RedirectClipboardBool": True, # True - share clipboard to RDP; False - else # ###### Will updated in program ############ # "SessionHex": "77777sdfsdf77777dsfdfsf77777777", # Hex is created when robot runs, example "" # "SessionIsWindowExistBool": False, "SessionIsWindowResponsibleBool": False, "SessionIsIgnoredBool": False @@ -2400,6 +2498,9 @@ lRDPItemDict = Orchestrator.RDPTemplateCreate( * **inSharedDriveList** – Host local disc to connect to the RDP session. Example: [“c”, “d”] + * **inRedirectClipboardBool** – # True - share clipboard to RDP; False - else + + * **Returns** @@ -2419,6 +2520,7 @@ lRDPItemDict = Orchestrator.RDPTemplateCreate( }, “SharedDriveList”: inSharedDriveList, # List of the Root sesion hard drives, example [“c”] + “RedirectClipboardBool”: True, # True - share clipboard to RDP; False - else ###### Will updated in program ############ “SessionHex”: “77777sdfsdf77777dsfdfsf77777777”, # Hex is created when robot runs, example “” “SessionIsWindowExistBool”: False, @@ -2606,7 +2708,7 @@ Return user UAC hierarchy dict of the inRequest object. Empty dict - superuser a -### pyOpenRPA.Orchestrator.__Orchestrator__.WebAuditMessageCreate(inRequest, inOperationCodeStr='-', inMessageStr='-') +### pyOpenRPA.Orchestrator.__Orchestrator__.WebAuditMessageCreate(inRequest=None, inOperationCodeStr='-', inMessageStr='-') Create message string with request user details (IP, Login etc…). Very actual for IT security in big company. ``` @@ -2626,7 +2728,7 @@ lLogger.info(lWebAuditMessageStr) * **Parameters** - * **inRequest** – HTTP request handler + * **inRequest** – HTTP request handler. Optional if call def from request thread * **inOperationCodeStr** – operation code in string format (actual for IT audit in control panels) @@ -2693,13 +2795,17 @@ Create listen interface for the web server -### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyBytes(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestGet() +Return the web request instance if current thread was created by web request from client. else return None + + +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyBytes(inRequest=None) Extract the body in bytes from the request * **Parameters** - **inRequest** – inRequest from the server + **inRequest** – inRequest from the server. Optional if call def from request thread @@ -2709,13 +2815,13 @@ Extract the body in bytes from the request -### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyJSON(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyJSON(inRequest=None) Extract the body in dict/list from the request * **Parameters** - **inRequest** – + **inRequest** – inRequest from the server. Optional if call def from request thread @@ -2725,13 +2831,13 @@ Extract the body in dict/list from the request -### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyStr(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseBodyStr(inRequest=None) Extract the body in str from the request * **Parameters** - **inRequest** – inRequest from the server + **inRequest** – inRequest from the server. Optional if call def from request thread @@ -2741,13 +2847,13 @@ Extract the body in str from the request -### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseFile(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParseFile(inRequest=None) Parse the request - extract the file (name, body in bytes) * **Parameters** - **inRequest** – + **inRequest** – inRequest from the server. Optional if call def from request thread @@ -2757,13 +2863,13 @@ Parse the request - extract the file (name, body in bytes) -### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParsePath(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestParsePath(inRequest=None) Parse the request - extract the url. Example: /pyOpenRPA/Debugging/DefHelper/… * **Parameters** - **inRequest** – + **inRequest** – inRequest from the server. Optional if call def from request thread @@ -2773,12 +2879,22 @@ Parse the request - extract the url. Example: /pyOpenRPA/Debugging/DefHelper/… -### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestResponseSend(inRequest, inResponeStr) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebRequestResponseSend(inResponeStr, inRequest=None) Send response for the request -:return: -### pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr='application/octet-stream', inGSettings=None) +* **Parameters** + + **inRequest** – inRequest from the server. Optional if call def from request thread + + + +* **Returns** + + + + +### pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectDef(inMethodStr, inURLStr, inMatchTypeStr, inDef, inContentTypeStr='application/octet-stream', inGSettings=None, inUACBool=None) > Connect URL to DEF > “inMethodStr”:”GET|POST”, @@ -2794,23 +2910,26 @@ Send response for the request * **inGSettings** – Global settings dict (singleton) - * **inMethodStr** – + * **inMethodStr** – “GET|POST”, - * **inURLStr** – + * **inURLStr** – example “/index”, #URL of the request - * **inMatchTypeStr** – + * **inMatchTypeStr** – #”BeginWith|Contains|Equal|EqualCase”, - * **inDef** – + * **inDef** – def arg allowed list: 2:[inRequest, inGSettings], 1: [inRequest], 0: [] - * **inContentTypeStr** – + * **inContentTypeStr** – default: “application/octet-stream” + + * **inUACBool** – default: None; True - check user access before do this URL item. None - get Server flag if ask user -### pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr='application/octet-stream', inGSettings=None) + +### pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFile(inMethodStr, inURLStr, inMatchTypeStr, inFilePathStr, inContentTypeStr='application/octet-stream', inGSettings=None, inUACBool=None) Connect URL to File “inMethodStr”:”GET|POST”, @@ -2840,14 +2959,18 @@ Connect URL to File * **inContentTypeStr** – + * **inUACBool** – default: None; True - check user access before do this URL item. None - get Server flag if ask user + -### pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings=None) + +### pyOpenRPA.Orchestrator.__Orchestrator__.WebURLConnectFolder(inMethodStr, inURLStr, inMatchTypeStr, inFolderPathStr, inGSettings=None, inUACBool=None) Connect URL to Folder “inMethodStr”:”GET|POST”, “inURLStr”: “/Folder/”, #URL of the request “inMatchTypeStr”: “”, #”BeginWith|Contains|Equal|EqualCase”, “inFolderPathStr”: “”, #Absolute or relative path + “inUACBool” * **Parameters** @@ -2868,14 +2991,17 @@ Connect URL to Folder * **inFolderPathStr** – + * **inUACBool** – default: None; True - check user access before do this URL item. None - get Server flag if ask user + + -### pyOpenRPA.Orchestrator.__Orchestrator__.WebUserInfoGet(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebUserInfoGet(inRequest=None) Return User info about request * **Parameters** - **inRequest** – + **inRequest** – inRequest from the server. Optional if call def from request thread @@ -2885,14 +3011,14 @@ Return User info about request -### pyOpenRPA.Orchestrator.__Orchestrator__.WebUserIsSuperToken(inRequest, inGSettings=None) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebUserIsSuperToken(inRequest=None, inGSettings=None) Return bool if request is authentificated with supetoken (token which is never expires) * **Parameters** - * **inRequest** – + * **inRequest** – inRequest from the server. Optional if call def from request thread * **inGSettings** – Global settings dict (singleton) @@ -2905,13 +3031,13 @@ Return bool if request is authentificated with supetoken (token which is never e -### pyOpenRPA.Orchestrator.__Orchestrator__.WebUserUACHierarchyGet(inRequest) +### pyOpenRPA.Orchestrator.__Orchestrator__.WebUserUACHierarchyGet(inRequest=None) Return User UAC Hierarchy DICT Return {…} * **Parameters** - **inRequest** – + **inRequest** – inRequest from the server. Optional if call def from request thread diff --git a/Wiki/ENG_Guide/markdown/Orchestrator/03_gSettingsTemplate.md b/Wiki/ENG_Guide/markdown/Orchestrator/03_gSettingsTemplate.md index 34332273..23b2fb59 100644 --- a/Wiki/ENG_Guide/markdown/Orchestrator/03_gSettingsTemplate.md +++ b/Wiki/ENG_Guide/markdown/Orchestrator/03_gSettingsTemplate.md @@ -114,18 +114,20 @@ def __Create__(): # "ResponseFilePath": "", #Absolute or relative path # "ResponseFolderPath": "", #Absolute or relative path # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result + # "ResponseDefRequestGlobal": None ,#Function with str result + # "UACBool": True # True - check user access before do this URL item. None - get Server flag if ask user # } - { - "Method": "GET", - "URL": "/test/", # URL of the request - "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", - # "ResponseFilePath": "", #Absolute or relative path - "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", - # Absolute or relative path - # "ResponseContentType": "", #HTTP Content-type - # "ResponseDefRequestGlobal": None #Function with str result - } + #{ + # "Method": "GET", + # "URL": "/test/", # URL of the request + # "MatchType": "BeginWith", # "BeginWith|Contains|Equal|EqualCase", + # # "ResponseFilePath": "", #Absolute or relative path + # "ResponseFolderPath": "C:\Abs\Archive\scopeSrcUL\OpenRPA\Orchestrator\Settings", + # # Absolute or relative path + # # "ResponseContentType": "", #HTTP Content-type + # # "ResponseDefRequestGlobal": None #Function with str result + # # "UACBool": True # True - check user access before do this URL item + #} ], }, @@ -156,6 +158,7 @@ def __Create__(): ], }, "ManagersProcessDict":{}, # The key of the Process is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mProcessNameWOExeStr.upper()) + "ManagersGitDict":{}, # The key of the Git instance is (mAgentHostNameStr.upper(), mAgentUserNameStr.upper(), mAbsPathUpperStr.upper()) "ProcessorDict": { # Has been changed. New general processor (one threaded) v.1.2.0 "ActivityList": [ # List of the activities # { @@ -167,26 +170,13 @@ def __Create__(): # "GUIDStr": ..., # GUID of the activity # }, ], + "ActivityItemNowDict": None, # Activity Item which is executing now "AliasDefDict": {}, # Storage for def with Str alias. To use it see pyOpenRPA.Orchestrator.ControlPanel "CheckIntervalSecFloat": 1.0, # Interval for check gSettings in ProcessorDict > ActivityList "ExecuteBool": True, # Flag to execute thread processor "ThreadIdInt": None, # Technical field - will be setup when processor init "WarningExecutionMoreThanSecFloat": 60.0 # Push warning if execution more than n seconds }, - # TODO REMOVE DEPRECATED KEYS IN v.2.0.0 - "ControlPanelDict": { # Old structure > CPDict - "RefreshSeconds": 5, # deprecated parameter - "RobotList": [ - #{ - # "RenderFunction": RenderRobotR01, - # "KeyStr": "TestControlPanelKey" - #} - ] - }, - # TODO REMOVE DEPRECATED KEYS IN v.2.0.0 - "CPDict": { - # "CPKey": {"HTMLRenderDef":None, "JSONGeneratorDef":None, "JSInitGeneratorDef":None} - }, # # # # # # # # # # # # # # "RobotRDPActive": { "RecoveryDict": { diff --git a/Wiki/ENG_Guide/markdown/Orchestrator/04_HowToUse.md b/Wiki/ENG_Guide/markdown/Orchestrator/04_HowToUse.md index 4a8a5156..88d35dc4 100644 --- a/Wiki/ENG_Guide/markdown/Orchestrator/04_HowToUse.md +++ b/Wiki/ENG_Guide/markdown/Orchestrator/04_HowToUse.md @@ -13,7 +13,8 @@ if __name__ == "__main__": # New init way - allow run as module -m PyOpenRPA.Orc If you need more configurations - so you can see here: ``` -import psutil, datetime, logging, os, sys # stdout from logging +import psutil, datetime, logging, os, sys + # Config settings lPyOpenRPASourceFolderPathStr = r"..\Sources" # Path for test pyOpenRPA package @@ -28,10 +29,10 @@ from pyOpenRPA import Orchestrator # Import orchestrator main if not Orchestrator.OrchestratorIsAdmin(): Orchestrator.OrchestratorRerunAsAdmin() print(f"Orchestrator will be run as administrator!") -elif __name__ == "__main__": # New init way - allow run as module -m PyOpenRPA.Orchestrator +else: gSettings = Orchestrator.GSettingsGet() #gSettings = SettingsTemplate.Create(inModeStr="BASIC") # Create GSettings with basic configuration - no more config is available from the box - you can create own - + Orchestrator.OrchestratorLoggerGet().setLevel(logging.INFO) # TEST Add User ND - Add Login ND to superuser of the Orchestrator lUACClientDict = SettingsTemplate.__UACClientAdminCreate__() Orchestrator.UACUpdate(inGSettings=gSettings, inADLoginStr="ND", inADStr="", inADIsDefaultBool=True, inURLList=[], inRoleHierarchyAllowedDict=lUACClientDict) @@ -44,34 +45,10 @@ elif __name__ == "__main__": # New init way - allow run as module -m PyOpenRPA.O # Restore DUMP Orchestrator.OrchestratorSessionRestore(inGSettings=gSettings) # Autoinit control panels starts with CP_ - lPyModules = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings") + lPyModules = Orchestrator.OrchestratorPySearchInit(inGlobPatternStr="ControlPanel\\CP_*.py", inDefStr="SettingsUpdate", inDefArgNameGSettingsStr="inGSettings", inAsyncInitBool=True) # Call the orchestrator def Orchestrator.Orchestrator(inGSettings=gSettings, inDumpRestoreBool=False) - #import atexit - #def test(): - # Orchestrator.OrchestratorLoggerGet().info(123) - #atexit.register(test) - import signal - import sys - - - from pyOpenRPA.Tools import Terminator - Terminator.Init(inLogger=Orchestrator.OrchestratorLoggerGet()) - - #def signal_term_handler(signal, frame): - # Orchestrator.OrchestratorLoggerGet().info('got SIGTERM') - # sys.exit(0) - - #signal.signal(signal.SIGTERM, signal_term_handler) - import time - while not Terminator.IsSignalClose(): - time.sleep(2) - print(999) - time.sleep(2) - -else: - print("!WARNING! Current orchestrator settings do not support old type of the Orchestrator start. Use new Orchestrator type start (see v1.2.0)") ``` diff --git a/Wiki/ENG_Guide/markdown/Orchestrator/06_Defs Managers.md b/Wiki/ENG_Guide/markdown/Orchestrator/06_Defs Managers.md index 23bfc9f1..382a0d8f 100644 --- a/Wiki/ENG_Guide/markdown/Orchestrator/06_Defs Managers.md +++ b/Wiki/ENG_Guide/markdown/Orchestrator/06_Defs Managers.md @@ -17,11 +17,21 @@ lProcess = Orchestrator.Managers.Process(inAgentHostNameStr="PC-DESKTOP",inAgent | **Functions:** +| `ProcessExists`(inAgentHostNameStr, …) + + | Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + | | `ProcessGet`(inAgentHostNameStr, …) | Return the process instance by the inProcessNameWOExeStr | +| `ProcessInitSafe`(inAgentHostNameStr, …[, …]) + + | Exception safe function. + + | | `ProcessManual2Auto`(inAgentHostNameStr, …) | Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -37,6 +47,11 @@ lProcess = Orchestrator.Managers.Process(inAgentHostNameStr="PC-DESKTOP",inAgent | Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): | +| `ProcessScheduleStatusCheckEverySeconds`(…) + + | Run status check every interval in second you specify. + + | | `ProcessStart`(inAgentHostNameStr, …[, …]) | Manual/Auto start. @@ -47,6 +62,16 @@ lProcess = Orchestrator.Managers.Process(inAgentHostNameStr="PC-DESKTOP",inAgent | Check if process is alive. | +| `ProcessStatusRestore`(inAgentHostNameStr, …) + + | Execute the StatusCheck, after that restore status to the saved state (see StatusSave). + + | +| `ProcessStatusSave`(inAgentHostNameStr, …) + + | Save current status of the process. + + | | `ProcessStatusStrGet`(inAgentHostNameStr, …) | Get the status of the Process instance. @@ -63,11 +88,14 @@ lProcess = Orchestrator.Managers.Process(inAgentHostNameStr="PC-DESKTOP",inAgent | -### class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=120) +### class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30) Manager process, which is need to be started / stopped / restarted With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not. +All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . +After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet + Process instance has the following statuses: @@ -89,14 +117,14 @@ Process instance has the following statuses: * 5_STARTED_MANUAL How to use StopSafe on the robot side -.. code-block:: python - -> from pyOpenRPA.Tools import StopSafe -> StopSafe.Init(inLogger=None) -> StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe **Methods:** +| `KeyTurpleGet`() + + | Get the key turple of the current process + + | | `Manual2Auto`() | Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -132,14 +160,9 @@ How to use StopSafe on the robot side | Manual/Auto restart safe. | -| `ScheduleStatusCheckEverySeconds`([…]) - - | Run status check every interval in second you specify. +| `Start`([inIsManualBool, inStartArgDict]) - | -| `Start`([inIsManualBool]) - - | Manual/Auto start. + | Manual/Auto start. | | `StartCheck`() @@ -157,6 +180,11 @@ How to use StopSafe on the robot side | Check if process is alive. | +| `StatusCheckIntervalRestore`() + + | Call from orchestrator when init + + | | `StatusCheckStart`() | Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED” @@ -172,9 +200,19 @@ How to use StopSafe on the robot side | Check process status and auto stop safe it if self.mStatusStr is 4_STARTED | -| `StopForce`([inIsManualBool]) +| `StatusRestore`() + + | Execute the StatusCheck, after that restore status to the saved state (see StatusSave). + + | +| `StatusSave`() + + | Save current status of the process. - | Manual/Auto stop force. + | +| `StopForce`([inIsManualBool, inMuteIgnoreBool]) + + | Manual/Auto stop force. | | `StopForceCheck`() @@ -182,17 +220,21 @@ How to use StopSafe on the robot side | Stop force program if auto started (4_STARTED). | -| `StopSafe`([inIsManualBool]) +| `StopSafe`([inIsManualBool, …]) - | Manual/Auto stop safe. + | Manual/Auto stop safe. | -| `StopSafeCheck`() +| `StopSafeCheck`([inStopSafeTimeoutSecFloat]) - | Stop safe program if auto started (4_STARTED). + | Stop safe program if auto started (4_STARTED). | +#### KeyTurpleGet() +Get the key turple of the current process + + #### Manual2Auto() Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -286,23 +328,7 @@ Manual stop safe will block scheduling execution. To return schedule execution u -#### ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=120) -Run status check every interval in second you specify. - - -* **Parameters** - - **inIntervalSecondsInt** – Interval in seconds. Default is 120 - - - -* **Returns** - - None - - - -#### Start(inIsManualBool=True) +#### Start(inIsManualBool=True, inStartArgDict=None) Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. Will not start if STOP SAFE is now and don’t start auto is stopped manual now @@ -339,7 +365,7 @@ Lof information about status change #### StatusCheck() -Check if process is alive. The def will save the manual flag is exists. +Check if process is alive. The def will save the manual flag is exists. Don’t wait mute but set mute if it is not set. * **Returns** @@ -348,6 +374,10 @@ Check if process is alive. The def will save the manual flag is exists. +#### StatusCheckIntervalRestore() +Call from orchestrator when init + + #### StatusCheckStart() Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED” @@ -377,7 +407,27 @@ Check process status and auto stop safe it if self.mStatusStr is 4_STARTED -#### StopForce(inIsManualBool=True) +#### StatusRestore() +Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + +#### StatusSave() +Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED” + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + +#### StopForce(inIsManualBool=True, inMuteIgnoreBool=False) Manual/Auto stop force. Force stop don’t wait process termination - it just terminate process now. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto @@ -404,14 +454,18 @@ Stop force program if auto started (4_STARTED). -#### StopSafe(inIsManualBool=True) +#### StopSafe(inIsManualBool=True, inStopSafeTimeoutSecFloat=None) Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto * **Parameters** - **inIsManualBool** – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + + * **inIsManualBool** – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + + + * **inStopSafeTimeoutSecFloat** – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force @@ -421,16 +475,45 @@ Manual stop safe will block scheduling execution. To return schedule execution u -#### StopSafeCheck() +#### StopSafeCheck(inStopSafeTimeoutSecFloat=None) Stop safe program if auto started (4_STARTED). +* **Parameters** + + **inStopSafeTimeoutSecFloat** – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + + + * **Returns** Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL +### pyOpenRPA.Orchestrator.Managers.Process.ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) +Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + +* **Parameters** + + + * **inAgentHostNameStr** – Agent hostname in any case. Required to identify Process + + + * **inAgentUserNameStr** – Agent user name in any case. Required to identify Process + + + * **inProcessNameWOExeStr** – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + + + +* **Returns** + + True - process exists in gsettings; False - else + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) Return the process instance by the inProcessNameWOExeStr @@ -454,6 +537,39 @@ Return the process instance by the inProcessNameWOExeStr +### pyOpenRPA.Orchestrator.Managers.Process.ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300) +Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) +Return existing instance (if exists) or create new instance and return it. + + +* **Parameters** + + + * **inAgentHostNameStr** – Agent hostname in any case. Required to identify Process + + + * **inAgentUserNameStr** – Agent user name in any case. Required to identify Process + + + * **inProcessNameWOExeStr** – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + + + * **inStartPathStr** – Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + + + * **inStartCMDStr** – CMD script to start program (if no start file is exists) + + + * **inStopSafeTimeoutSecFloat** – Time to wait for stop safe. After that do the stop force (if process is not stopped) + + + +* **Returns** + + Process instance + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -537,6 +653,32 @@ Remove Manual flag from process (if exists) - it will allow the schedule operati +### pyOpenRPA.Orchestrator.Managers.Process.ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIntervalSecondsInt: int = 120) +Run status check every interval in second you specify. + + +* **Parameters** + + + * **inAgentHostNameStr** – Agent hostname in any case. Required to identify Process + + + * **inAgentUserNameStr** – Agent user name in any case. Required to identify Process + + + * **inProcessNameWOExeStr** – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + + + * **inIntervalSecondsInt** – Interval in seconds. Default is 120 + + + +* **Returns** + + None + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto @@ -602,6 +744,26 @@ Check if process is alive. The def will save the manual flag is exists. +### pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) +Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + +### pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) +Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED” + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) Get the status of the Process instance. @@ -668,7 +830,7 @@ Manual stop safe will block scheduling execution. To return schedule execution u -### pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) +### pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat=None) Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto @@ -688,6 +850,9 @@ Manual stop safe will block scheduling execution. To return schedule execution u * **inIsManualBool** – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + * **inStopSafeTimeoutSecFloat** – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + + * **Returns** @@ -713,11 +878,21 @@ Manual stop safe will block scheduling execution. To return schedule execution u | **Functions:** +| `ProcessExists`(inAgentHostNameStr, …) + + | Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + | | `ProcessGet`(inAgentHostNameStr, …) | Return the process instance by the inProcessNameWOExeStr | +| `ProcessInitSafe`(inAgentHostNameStr, …[, …]) + + | Exception safe function. + + | | `ProcessManual2Auto`(inAgentHostNameStr, …) | Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -733,6 +908,11 @@ Manual stop safe will block scheduling execution. To return schedule execution u | Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): | +| `ProcessScheduleStatusCheckEverySeconds`(…) + + | Run status check every interval in second you specify. + + | | `ProcessStart`(inAgentHostNameStr, …[, …]) | Manual/Auto start. @@ -743,6 +923,16 @@ Manual stop safe will block scheduling execution. To return schedule execution u | Check if process is alive. | +| `ProcessStatusRestore`(inAgentHostNameStr, …) + + | Execute the StatusCheck, after that restore status to the saved state (see StatusSave). + + | +| `ProcessStatusSave`(inAgentHostNameStr, …) + + | Save current status of the process. + + | | `ProcessStatusStrGet`(inAgentHostNameStr, …) | Get the status of the Process instance. @@ -759,11 +949,14 @@ Manual stop safe will block scheduling execution. To return schedule execution u | -### class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=120) +### class pyOpenRPA.Orchestrator.Managers.Process.Process(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300, inStartArgDict=None, inStatusCheckIntervalSecFloat=30) Manager process, which is need to be started / stopped / restarted With Process instance you can automate your process activity. Use schedule package to set interval when process should be active and when not. +All defs in class are pickle safe! After orchestrator restart (if not the force stop of the orchestrator process) your instance with properties will be restored. But it not coverage the scheduler which is in __Orchestrator__ . +After orc restart you need to reinit all schedule rules: Orchestrator.OrchestratorScheduleGet + Process instance has the following statuses: @@ -785,14 +978,14 @@ Process instance has the following statuses: * 5_STARTED_MANUAL How to use StopSafe on the robot side -.. code-block:: python - -> from pyOpenRPA.Tools import StopSafe -> StopSafe.Init(inLogger=None) -> StopSafe.IsSafeStop() # True - WM_CLOSE SIGNAL has come. taskkill /im someprocess.exe **Methods:** +| `KeyTurpleGet`() + + | Get the key turple of the current process + + | | `Manual2Auto`() | Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -828,14 +1021,9 @@ How to use StopSafe on the robot side | Manual/Auto restart safe. | -| `ScheduleStatusCheckEverySeconds`([…]) +| `Start`([inIsManualBool, inStartArgDict]) - | Run status check every interval in second you specify. - - | -| `Start`([inIsManualBool]) - - | Manual/Auto start. + | Manual/Auto start. | | `StartCheck`() @@ -853,6 +1041,11 @@ How to use StopSafe on the robot side | Check if process is alive. | +| `StatusCheckIntervalRestore`() + + | Call from orchestrator when init + + | | `StatusCheckStart`() | Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED” @@ -868,9 +1061,19 @@ How to use StopSafe on the robot side | Check process status and auto stop safe it if self.mStatusStr is 4_STARTED | -| `StopForce`([inIsManualBool]) +| `StatusRestore`() + + | Execute the StatusCheck, after that restore status to the saved state (see StatusSave). + + | +| `StatusSave`() + + | Save current status of the process. + + | +| `StopForce`([inIsManualBool, inMuteIgnoreBool]) - | Manual/Auto stop force. + | Manual/Auto stop force. | | `StopForceCheck`() @@ -878,17 +1081,21 @@ How to use StopSafe on the robot side | Stop force program if auto started (4_STARTED). | -| `StopSafe`([inIsManualBool]) +| `StopSafe`([inIsManualBool, …]) - | Manual/Auto stop safe. + | Manual/Auto stop safe. | -| `StopSafeCheck`() +| `StopSafeCheck`([inStopSafeTimeoutSecFloat]) - | Stop safe program if auto started (4_STARTED). + | Stop safe program if auto started (4_STARTED). | +#### KeyTurpleGet() +Get the key turple of the current process + + #### Manual2Auto() Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -982,23 +1189,7 @@ Manual stop safe will block scheduling execution. To return schedule execution u -#### ScheduleStatusCheckEverySeconds(inIntervalSecondsInt=120) -Run status check every interval in second you specify. - - -* **Parameters** - - **inIntervalSecondsInt** – Interval in seconds. Default is 120 - - - -* **Returns** - - None - - - -#### Start(inIsManualBool=True) +#### Start(inIsManualBool=True, inStartArgDict=None) Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto. Will not start if STOP SAFE is now and don’t start auto is stopped manual now @@ -1035,7 +1226,7 @@ Lof information about status change #### StatusCheck() -Check if process is alive. The def will save the manual flag is exists. +Check if process is alive. The def will save the manual flag is exists. Don’t wait mute but set mute if it is not set. * **Returns** @@ -1044,6 +1235,10 @@ Check if process is alive. The def will save the manual flag is exists. +#### StatusCheckIntervalRestore() +Call from orchestrator when init + + #### StatusCheckStart() Check process status and run it if auto stopped self.mStatusStr is “0_STOPPED” @@ -1073,7 +1268,27 @@ Check process status and auto stop safe it if self.mStatusStr is 4_STARTED -#### StopForce(inIsManualBool=True) +#### StatusRestore() +Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + +#### StatusSave() +Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED” + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + +#### StopForce(inIsManualBool=True, inMuteIgnoreBool=False) Manual/Auto stop force. Force stop don’t wait process termination - it just terminate process now. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto @@ -1100,14 +1315,18 @@ Stop force program if auto started (4_STARTED). -#### StopSafe(inIsManualBool=True) +#### StopSafe(inIsManualBool=True, inStopSafeTimeoutSecFloat=None) Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto * **Parameters** - **inIsManualBool** – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + + * **inIsManualBool** – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + + + * **inStopSafeTimeoutSecFloat** – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force @@ -1117,16 +1336,45 @@ Manual stop safe will block scheduling execution. To return schedule execution u -#### StopSafeCheck() +#### StopSafeCheck(inStopSafeTimeoutSecFloat=None) Stop safe program if auto started (4_STARTED). +* **Parameters** + + **inStopSafeTimeoutSecFloat** – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + + + * **Returns** Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL +### pyOpenRPA.Orchestrator.Managers.Process.ProcessExists(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) +Check if the Process instance is exists in GSettings by the (inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) + + +* **Parameters** + + + * **inAgentHostNameStr** – Agent hostname in any case. Required to identify Process + + + * **inAgentUserNameStr** – Agent user name in any case. Required to identify Process + + + * **inProcessNameWOExeStr** – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + + + +* **Returns** + + True - process exists in gsettings; False - else + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) Return the process instance by the inProcessNameWOExeStr @@ -1150,6 +1398,39 @@ Return the process instance by the inProcessNameWOExeStr +### pyOpenRPA.Orchestrator.Managers.Process.ProcessInitSafe(inAgentHostNameStr, inAgentUserNameStr, inProcessNameWOExeStr, inStartPathStr=None, inStartCMDStr=None, inStopSafeTimeoutSecFloat=300) +Exception safe function. Check if process instance is not exists in GSettings (it can be after restart because Orchestrator restore objects from dump of the previous Orchestrator session) +Return existing instance (if exists) or create new instance and return it. + + +* **Parameters** + + + * **inAgentHostNameStr** – Agent hostname in any case. Required to identify Process + + + * **inAgentUserNameStr** – Agent user name in any case. Required to identify Process + + + * **inProcessNameWOExeStr** – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + + + * **inStartPathStr** – Path to start process (.cmd/ .exe or something else). Path can be relative (from orc working directory) or absolute + + + * **inStartCMDStr** – CMD script to start program (if no start file is exists) + + + * **inStopSafeTimeoutSecFloat** – Time to wait for stop safe. After that do the stop force (if process is not stopped) + + + +* **Returns** + + Process instance + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessManual2Auto(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) Remove Manual flag from process (if exists) - it will allow the schedule operations via def StatusCheckStart(self): def StatusCheckStorForce(self): def StatusCheckStopSafe(self): @@ -1233,6 +1514,32 @@ Remove Manual flag from process (if exists) - it will allow the schedule operati +### pyOpenRPA.Orchestrator.Managers.Process.ProcessScheduleStatusCheckEverySeconds(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIntervalSecondsInt: int = 120) +Run status check every interval in second you specify. + + +* **Parameters** + + + * **inAgentHostNameStr** – Agent hostname in any case. Required to identify Process + + + * **inAgentUserNameStr** – Agent user name in any case. Required to identify Process + + + * **inProcessNameWOExeStr** – The process name without extension .exe (the key of the Process instance). Any case - will be processed to the upper case + + + * **inIntervalSecondsInt** – Interval in seconds. Default is 120 + + + +* **Returns** + + None + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessStart(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) Manual/Auto start. Manual start will block scheduling execution. To return schedule execution use def Manual2Auto @@ -1298,6 +1605,26 @@ Check if process is alive. The def will save the manual flag is exists. +### pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusRestore(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) +Execute the StatusCheck, after that restore status to the saved state (see StatusSave). Work when orchestrator is restarted. + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + +### pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusSave(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) +Save current status of the process. After that you can restore process activity. Work when orchestrator is restarted. Don’t save “STOP_SAFE” status > “STOPPED” + + +* **Returns** + + Process status. See self.mStatusStr. 0_STOPPED 1_STOPPED_MANUAL 2_STOP_SAFE 3_STOP_SAFE_MANUAL 4_STARTED 5_STARTED_MANUAL + + + ### pyOpenRPA.Orchestrator.Managers.Process.ProcessStatusStrGet(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str) Get the status of the Process instance. @@ -1364,7 +1691,7 @@ Manual stop safe will block scheduling execution. To return schedule execution u -### pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True) +### pyOpenRPA.Orchestrator.Managers.Process.ProcessStopSafe(inAgentHostNameStr: str, inAgentUserNameStr: str, inProcessNameWOExeStr: str, inIsManualBool: bool = True, inStopSafeTimeoutSecFloat=None) Manual/Auto stop safe. Stop safe is the operation which send signal to process to terminate own work (send term signal to process). Managers.Process wait for the mStopSafeTimeoutSecFloat seconds. After that, if process is not terminated - self will StopForce it. Manual stop safe will block scheduling execution. To return schedule execution use def Manual2Auto @@ -1384,6 +1711,9 @@ Manual stop safe will block scheduling execution. To return schedule execution u * **inIsManualBool** – Default is True - Mark this operation as manual - StatusCheckStart/Stop will be blocked - only StatusCheck will be working. False - Auto operation + * **inStopSafeTimeoutSecFloat** – Default value goes from the instance. You can specify time is second to wait while safe stop. After that program will stop force + + * **Returns** @@ -1440,6 +1770,9 @@ EnumerateDef: enumerate, OperatorModule: operator, MathModule: math +You can modify jinja context by use the function: +Jinja2DataUpdateDictSet + **Methods:** | `DataDictGenerate`(inRequest) @@ -1712,6 +2045,9 @@ EnumerateDef: enumerate, OperatorModule: operator, MathModule: math +You can modify jinja context by use the function: +Jinja2DataUpdateDictSet + **Methods:** | `DataDictGenerate`(inRequest) diff --git a/Wiki/ENG_Guide/markdown/index.md b/Wiki/ENG_Guide/markdown/index.md index cfd8b077..09779cd9 100644 --- a/Wiki/ENG_Guide/markdown/index.md +++ b/Wiki/ENG_Guide/markdown/index.md @@ -12,22 +12,17 @@ contain the root `toctree` directive. --> ! ATTENTION ! pyOpenRPA works only on MS Windows 7+/Server 2008+. Guys from Unix/Mac - sorry. We will come to you soon :) -## Donate - -pyOpenRPA is absolutely non-commercial project. - -Please donate some $ if pyOpenRPA project is actual for you. Link to online donations. -[https://yoomoney.ru/to/4100115560661986](https://yoomoney.ru/to/4100115560661986) - ## About Dear RPA-tors. Let me congratulate you with great change in the RPA world. Since 2019 the first enterprise level open source RPA platform is here! +pyOpenRPA is absolutely open source commercial project. Hosted by LLC PYOPENRPA (RUSSIA) + The pyOpenRPA - free, fast and reliable Powerful OpenSource RPA tool for business (based on python 3). Best performance and absolutely free! The pyOpenRPA is based on Python and using well known OpenSource solutions such as Selenium, OpenCV, Win32, UI automation and others. Thanks to it we were able to create consolidated platform with all possible features. -The pyOpenRPA is distributed under the MIT license which allows you to use it in any way you want and any time you need without any restrictions. +The pyOpenRPA is distributed under the PYOPENRPA license. At the time of this writing the pyOpenRPA is successfully using in several big Russian companies. Companies in which it was decided to develop own RPA division with no dependencies on expensive licenses. ## Repo structure diff --git a/v1.2.6 b/v1.2.7 similarity index 100% rename from v1.2.6 rename to v1.2.7