diff --git a/README.md b/README.md index 9a5b39c9..d43341e3 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,9 @@ Now you can use the following docs: - ENG Guide PDF [|OPEN GITLAB|](Wiki/ENG_Guide/pdf/pyOpenRPA_Guide_ENG.pdf) - RUS Article: Less cost - no paid RPA [|OPEN HABR|](https://habr.com/ru/post/506766/) -- RUS Tutorial Desktop UI [|OPEN HABR|](https://habr.com/ru/post/509644/); [|OPEN GITLAB|](Wiki/RUS_Tutorial/DesktopGUI_Habr/index.md) +- RUS Tutorial Desktop UI [|OPEN HABR|](https://habr.com/ru/post/509644/); [|OPEN GITLAB|](Wiki/RUS_Tutorial/DesktopGUI_Habr/README.md) - RUS Tutorial Web UI [|OPEN HABR|](https://habr.com/ru/post/515310/); [|OPEN GITLAB|](Wiki/RUS_Tutorial/WebGUI_Habr/3.%20WebGUI_Habr.md) -- RUS Leaflet pyOpenRPA v4.pdf [|OPEN GITLAB|](Wiki/RUS_Leaflet/RUS%20Leaflet%20pyOpenRPA%20v4.pdf) +- RUS Leaflet pyOpenRPA v5.pdf [|OPEN GITLAB|](Wiki/RUS_Leaflet/RUS%20Leaflet%20pyOpenRPA%20v5.pdf) ## Copyrights & Contacts pyOpenRPA is created by Ivan Maslov (Russia). Use it for free (MIT)! @@ -47,7 +47,7 @@ If you need IT help feel free to contact me. My contacts: -- E-mail: Ivan.Maslov@UnicodeLabs.ru +- E-mail: I.Maslov@mail.ru - Skype: MegaFinder - Facebook: https://www.facebook.com/RU.IT4Business - LinkedIn: https://www.linkedin.com/in/RU-IvanMaslov/ diff --git a/Sources/pyOpenRPA/Agent/O2A.py b/Sources/pyOpenRPA/Agent/O2A.py index 0f040635..0994a0c0 100644 --- a/Sources/pyOpenRPA/Agent/O2A.py +++ b/Sources/pyOpenRPA/Agent/O2A.py @@ -32,12 +32,18 @@ def O2A_Loop(inGSettings): time.sleep(inGSettings["O2ADict"]["RetryTimeoutSecFloat"]) else: lRequestBody = lResponse.text - if len(lRequestBody) != 0: # CHeck if not empty result when close the connection from orch + 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) - if lL: lL.debug(f"ActivityItem was received from orchestrator: {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"); 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: diff --git a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py index e6ac51d4..727376ee 100644 --- a/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py +++ b/Sources/pyOpenRPA/Orchestrator/BackwardCompatibility.py @@ -480,4 +480,12 @@ def Update(inGSettings): if "AgentFileChunkCheckIntervalSecFloat" not in inGSettings["ServerDict"]: inGSettings["ServerDict"]["AgentFileChunkCheckIntervalSecFloat"]= 0.2 if lL: lL.warning( - f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > AgentFileChunkCheckIntervalSecFloat") # Log about compatibility \ No newline at end of file + f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > AgentFileChunkCheckIntervalSecFloat") # Log about compatibility + if "ServerThread" not in inGSettings["ServerDict"]: + inGSettings["ServerDict"]["ServerThread"]= None + if lL: lL.warning( + f"Backward compatibility (v1.2.3 to v1.2.4): Add new key ServerDict > ServerThread") # Log about compatibility + 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 diff --git a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py index aba20211..ed09b85f 100644 --- a/Sources/pyOpenRPA/Orchestrator/ServerSettings.py +++ b/Sources/pyOpenRPA/Orchestrator/ServerSettings.py @@ -397,8 +397,16 @@ def pyOpenRPA_Agent_O2A(inRequest, inGSettings): lReturnActivityItemDict = copy.deepcopy(lReturnActivityItemDict) if "CreatedByDatetime" in lReturnActivityItemDict: del lReturnActivityItemDict["CreatedByDatetime"] - if lL: lL.info(f"Activity item to agent Hostname {lInput['HostNameUpperStr']}, User {lInput['UserUpperStr']}. Activity item: {lReturnActivityItemDict}") inRequest.OpenRPAResponseDict["Body"] = bytes(json.dumps(lReturnActivityItemDict), "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}") lDoLoopBool = False # CLose the connection else: # Nothing to send - sleep for the next iteration time.sleep(lAgentLoopSleepSecFloat) diff --git a/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py b/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py index b1656b60..242ddf55 100644 --- a/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py +++ b/Sources/pyOpenRPA/Orchestrator/SettingsTemplate.py @@ -39,6 +39,8 @@ def __Create__(): # # # # # # # # # # # # # # # # # # }, "ServerDict": { + "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 "AgentConnectionLifetimeSecFloat": 300.0, # Time in seconds to handle the open connection to the Agent "AgentLoopSleepSecFloat": 2.0, # Time in seconds to sleep between loops when check to send some activity to the agent diff --git a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py index c9ee1d80..805472d0 100644 --- a/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py +++ b/Sources/pyOpenRPA/Orchestrator/__Orchestrator__.py @@ -75,7 +75,6 @@ def AgentActivityItemExists(inGSettings, inHostNameStr, inUserStr, inGUIDStr): lAgentDictItemKeyTurple = (inHostNameStr.upper(),inUserStr.upper()) lResultBool = False if lAgentDictItemKeyTurple in inGSettings["AgentDict"]: - inGSettings["AgentDict"][lAgentDictItemKeyTurple] = SettingsTemplate.__AgentDictItemCreate__() for lActivityItem in inGSettings["AgentDict"][lAgentDictItemKeyTurple]["ActivityList"]: if inGUIDStr == lActivityItem.get("GUIDStr",None): lResultBool = True @@ -144,6 +143,7 @@ def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePat """ Send the file from the Orchestrator to Agent (synchroniously) pyOpenRPA.Agent daemon process (safe for JSON transmition). Work safety with big files + Thread safe - you can call def even if you dont init the orchestrator - def will be executed later :param inGSettings: Global settings dict (singleton) :param inHostNameStr: @@ -152,41 +152,58 @@ def AgentOSFileSend(inGSettings, inHostNameStr, inUserStr, inOrchestratorFilePat :param inFileDataBytes: :return: GUID String of the ActivityItem - you can wait (sync or async) result by this guid! """ - lActivityItemCheckIntervalSecFloat = inGSettings["ServerDict"]["AgentFileChunkCheckIntervalSecFloat"] - - # Get the chunk limit - lChunkByteSizeInt = inGSettings["ServerDict"]["AgentFileChunkBytesSizeInt"] - lL = inGSettings.get("Logger",None) - # Open the file and get the size (in bytes) - lFile = open(inOrchestratorFilePathStr,"rb") - lFileSizeBytesInt = lFile.seek(0,2) - lFile.seek(0) - - lChunkCountInt = math.ceil(lFileSizeBytesInt/lChunkByteSizeInt) - if lL: lL.info(f"O2A: Start to send binary file via chunks. Chunk count: {lChunkCountInt}, From (Orch side): {inOrchestratorFilePathStr}, To (Agent side): {inAgentFilePathStr}") - for lChunkNumberInt in range(lChunkCountInt): - # Read chunk - lFileChunkBytes = lFile.read(lChunkByteSizeInt) - # Convert to base64 - lFileChunkBase64Str = base64.b64encode(lFileChunkBytes).decode("utf-8") - # Send chunk - if lChunkNumberInt == 0: - lActivityItemGUIDStr = AgentOSFileBinaryDataBase64StrCreate(inGSettings=inGSettings,inHostNameStr=inHostNameStr, - inUserStr=inUserStr,inFilePathStr=inAgentFilePathStr, - inFileDataBase64Str=lFileChunkBase64Str) - else: - lActivityItemGUIDStr = AgentOSFileBinaryDataBase64StrAppend(inGSettings=inGSettings, inHostNameStr=inHostNameStr, - inUserStr=inUserStr, inFilePathStr=inAgentFilePathStr, - inFileDataBase64Str=lFileChunkBase64Str) - # Wait for the activity will be deleted - while AgentActivityItemExists(inGSettings=inGSettings,inHostNameStr=inHostNameStr,inUserStr=inUserStr,inGUIDStr=lActivityItemGUIDStr): - time.sleep(lActivityItemCheckIntervalSecFloat) - if lL: lL.debug( - f"O2A: BINARY SEND: Current chunk index: {lChunkNumberInt}") - # Close the file - lFile.close() + # 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.") + lResult = { + "Def": AgentOSFileSend, # def link or def alias (look gSettings["Processor"]["AliasDefDict"]) + "ArgList": [], # Args list + "ArgDict": {"inHostNameStr":inHostNameStr, "inUserStr":inUserStr, "inOrchestratorFilePathStr":inOrchestratorFilePathStr, "inAgentFilePathStr": inAgentFilePathStr}, # 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) + } + inGSettings["ProcessorDict"]["ActivityList"].append(lResult) + else: # In processor - do execution + lActivityItemCheckIntervalSecFloat = inGSettings["ServerDict"]["AgentFileChunkCheckIntervalSecFloat"] + + # Get the chunk limit + lChunkByteSizeInt = inGSettings["ServerDict"]["AgentFileChunkBytesSizeInt"] + + lL = inGSettings.get("Logger",None) + + # Open the file and get the size (in bytes) + lFile = open(inOrchestratorFilePathStr,"rb") + lFileSizeBytesInt = lFile.seek(0,2) + lFile.seek(0) + #import pdb + #pdb.set_trace() + lChunkCountInt = math.ceil(lFileSizeBytesInt/lChunkByteSizeInt) + if lL: lL.info(f"O2A: Start to send binary file via chunks. Chunk count: {lChunkCountInt}, From (Orch side): {inOrchestratorFilePathStr}, To (Agent side): {inAgentFilePathStr}") + for lChunkNumberInt in range(lChunkCountInt): + # Read chunk + lFileChunkBytes = lFile.read(lChunkByteSizeInt) + # Convert to base64 + lFileChunkBase64Str = base64.b64encode(lFileChunkBytes).decode("utf-8") + # Send chunk + if lChunkNumberInt == 0: + lActivityItemGUIDStr = AgentOSFileBinaryDataBase64StrCreate(inGSettings=inGSettings,inHostNameStr=inHostNameStr, + inUserStr=inUserStr,inFilePathStr=inAgentFilePathStr, + inFileDataBase64Str=lFileChunkBase64Str) + else: + lActivityItemGUIDStr = AgentOSFileBinaryDataBase64StrAppend(inGSettings=inGSettings, inHostNameStr=inHostNameStr, + inUserStr=inUserStr, inFilePathStr=inAgentFilePathStr, + inFileDataBase64Str=lFileChunkBase64Str) + # Wait for the activity will be deleted + while AgentActivityItemExists(inGSettings=inGSettings,inHostNameStr=inHostNameStr,inUserStr=inUserStr,inGUIDStr=lActivityItemGUIDStr): + time.sleep(lActivityItemCheckIntervalSecFloat) + if lL: lL.debug( + f"O2A: BINARY SEND: Current chunk index: {lChunkNumberInt}") + if lL: lL.info( + f"O2A: BINARY SEND: Transmition has been complete") + # Close the file + lFile.close() def AgentOSFileBinaryDataBytesCreate(inGSettings, inHostNameStr, inUserStr, inFilePathStr, inFileDataBytes): """ @@ -2131,6 +2148,7 @@ def Orchestrator(inGSettings, inDumpRestoreBool = True, inRunAsAdministratorBool lItemDict = lListenDict[lItemKeyStr] lThreadServer = Server.RobotDaemonServer(lItemKeyStr, gSettingsDict) lThreadServer.start() + gSettingsDict["ServerDict"]["ServerThread"] = lThreadServer lItemDict["ServerInstance"] = lThreadServer # Init the RobotScreenActive in another thread diff --git a/Sources/setup.py b/Sources/setup.py index ca68354c..acc919a3 100644 --- a/Sources/setup.py +++ b/Sources/setup.py @@ -53,7 +53,7 @@ setup(name='pyOpenRPA', install_requires=[ 'pywinauto>=0.6.8;platform_system=="win32" and python_version>="3.0"', 'WMI>=1.4.9;platform_system=="win32" and python_version>="3.0"', - 'pillow>=6.0.0','keyboard>=0.13.3','pyautogui>=0.9.44', + 'pillow>=6.0.0','keyboard>=0.13.3','pyautogui>=0.9.44 and pyautogui<=0.9.52', 'pywin32>=224;platform_system=="win32" and python_version>="3.0"', 'crypto>=1.4.1' #'pywin32>=224;platform_system=="Linux" and python_version>="3.0"', 'crypto>=1.4.1' ], diff --git a/Wiki/RUS_Leaflet/RUS Leaflet pyOpenRPA v5.pdf b/Wiki/RUS_Leaflet/RUS Leaflet pyOpenRPA v5.pdf new file mode 100644 index 00000000..832cb465 Binary files /dev/null and b/Wiki/RUS_Leaflet/RUS Leaflet pyOpenRPA v5.pdf differ diff --git a/v1.2.3 b/v1.2.4 similarity index 100% rename from v1.2.3 rename to v1.2.4