Пример #1
0
        private async Task RunScriptAsync(TheScript script, TheThing variables, int stepNumber = 1, bool replay = false)
        {
            TheThing variablesSnapshot;

            try
            {
                for (; stepNumber <= script.Steps.Length; stepNumber++)
                {
                    //Clone thing before step occurs
                    variablesSnapshot = new TheThing();
                    variables.CloneThingAndPropertyMetaData(variablesSnapshot, true);

                    var step = script.Steps[stepNumber - 1];

                    var existingSnapshot = MyScriptTableStorage.MyMirrorCache.GetEntryByFunc(snapshot => snapshot.ScriptName == script.Name && snapshot.ScriptStep == stepNumber);
                    if (existingSnapshot?.Disabled == true)
                    {
                        TheBaseAssets.MySYSLOG.WriteToLog(175002, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step: skipped step because it was disabled", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                            { "Script", script.Name },
                            { "Step", stepNumber },
                            { "Message", step.Message.MessageName },
                            { "Target", step.Message.Target },
                        })));

                        UpdateStorageList(script.Name, "Disabled", stepNumber, script, variablesSnapshot, replay);
                        continue;
                    }

                    if (step.Condition != null)
                    {
                        var condition = TheCommonUtils.GenerateFinalStr(step.Condition, variables);
                        if (
                            (condition == "" || condition.ToLowerInvariant() == "false" || condition.Trim() == "0") ||
                            (condition.StartsWith("!") && condition.Length >= 1 && (condition.Substring(1).ToLowerInvariant() == "true") || condition.Substring(1).Trim() == "1"))
                        {
                            TheBaseAssets.MySYSLOG.WriteToLog(175002, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step: skipped step due to condition not met", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                                { "Script", script.Name },
                                { "Step", stepNumber },
                                { "Message", step.Message.MessageName },
                                { "Target", step.Message.Target },
                                { "Condition", step.Condition },
                                { "ConditionEvaluated", condition },
                            })));

                            UpdateStorageList(script.Name, "Condition Not Met", stepNumber, script, variablesSnapshot, replay);

                            continue;
                        }
                    }
                    var messageType = TheCommonUtils.GenerateFinalStr(step.Message.MessageName, variables);
                    var txtPayload  = TheCommonUtils.GenerateFinalStr(step.Message.Parameters?.ToString(), variables);
                    {
                        var txtPayload2 = txtPayload?.Replace("\"\\\"", "");
                        var txtPayload3 = txtPayload2?.Replace("\\\"\"", "");
                        txtPayload = txtPayload3;
                    }

                    // TODO Need a simpler and more flexible way to specify thing address in the script JSON
                    var target = step.Message.Target;
                    if (target == null)
                    {
                        if (txtPayload.Contains("EngineName"))
                        {
                            var    payloadDict        = TheCommonUtils.DeserializeJSONStringToObject <Dictionary <string, object> >(txtPayload);
                            object engineNameInferred = null;
                            if (payloadDict?.TryGetValue("EngineName", out engineNameInferred) == true && !string.IsNullOrEmpty(engineNameInferred?.ToString()))
                            {
                                target = new TheMessageAddress {
                                    EngineName = engineNameInferred.ToString()
                                };
                            }
                        }
                    }
                    if (target.EngineName.StartsWith("%") || target.EngineName.StartsWith("{"))
                    {
                        target.EngineName = TheCommonUtils.GenerateFinalStr(target.EngineName, variables);
                        // TODO Clean this up: support a serialized TheMessageAddress in the engine name, so that an output variable can be fed into a method invocation
                        try
                        {
                            var newTarget = TheCommonUtils.DeserializeJSONStringToObject <TheMessageAddress>(target.EngineName);
                            if (newTarget != null)
                            {
                                target = newTarget;
                            }
                        }
                        catch
                        {
                            // parsing error: ignore, will result in other errors downstream
                        }
                    }

                    await TheThingRegistry.WaitForInitializeAsync(target);

                    bool bDoRetry;
                    int  remainingRetryCount = step.RetryCount ?? 0;
                    do
                    {
                        existingSnapshot = MyScriptTableStorage.MyMirrorCache.GetEntryByFunc(snapshot => snapshot.ScriptName == script.Name && snapshot.ScriptStep == stepNumber);
                        if (existingSnapshot?.Disabled == true)
                        {
                            TheBaseAssets.MySYSLOG.WriteToLog(175002, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step: skipped step because it was disabled", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                                { "Script", script.Name },
                                { "Step", stepNumber },
                                { "Message", step.Message.MessageName },
                                { "Target", step.Message.Target },
                            })));

                            UpdateStorageList(script.Name, "Disabled", stepNumber, script, variablesSnapshot, replay);
                            break;
                        }

                        bDoRetry = false;
                        var response = await TheCommRequestResponse.PublishRequestAsync(MyBaseThing, target, messageType, new TimeSpan(0, 0, 0, 0, step.Message.timeout), null, txtPayload, null);

                        if (!string.IsNullOrEmpty(response?.PLS))
                        {
                            var outputs = TheCommonUtils.DeserializeJSONStringToObject <Dictionary <string, object> >(response.PLS);
                            if (outputs != null)
                            {
                                if (step.Message.outputs != null)
                                {
                                    foreach (var output in step.Message.outputs)
                                    {
                                        if (output.Key == "*")
                                        {
                                            variables.SetProperty(output.Value, response.PLS);
                                        }
                                        else if (outputs.TryGetValue(output.Key, out var outputValue))
                                        {
                                            variables.SetProperty(output.Value, outputValue);
                                            if (output.Value.Contains("Error") && !string.IsNullOrEmpty(TheCommonUtils.CStr(outputValue)))
                                            {
                                                TheBaseAssets.MySYSLOG.WriteToLog(175004, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error in script step: output reported error", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                                                    { "Script", script.Name },
                                                    { "Step", stepNumber },
                                                    { "Message", messageType },
                                                    { "Target", target },
                                                    { "PLS", txtPayload },
                                                    { "Response", response },
                                                    { "ResponsePLS", response?.PLS },
                                                })));

                                                UpdateStorageList(script.Name, $"Error {outputValue} in output", stepNumber, script, variablesSnapshot, replay);

                                                if (remainingRetryCount < 0 || remainingRetryCount > 0)
                                                {
                                                    remainingRetryCount--;
                                                    bDoRetry = true;
                                                }
                                                string retriesRemaining = bDoRetry ? (remainingRetryCount >= 0 ? $"{remainingRetryCount + 1}" : "infinite") : "none";
                                                MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}', step {stepNumber}: output '{output.Value}' reported error {outputValue}. Retries remaining: {retriesRemaining}");
                                            }
                                        }
                                        else
                                        {
                                            // TODO provide access to sub-elements in the JSON
                                            //var outputParts = output.Key.Split('/');
                                            //dynamic currentNode = outputs;
                                            //foreach (var outputPart in outputParts)
                                            //{
                                            //    if (currentNode.TryGetValue(outputPart, out var nextNode))
                                            //    {
                                            //        currentNode = nextNode;
                                            //    }
                                            //}
                                        }
                                    }
                                }
                                TheBaseAssets.MySYSLOG.WriteToLog(175003, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Finished script step", eMsgLevel.l3_ImportantMessage, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                                    { "Script", script.Name },
                                    { "Step", stepNumber },
                                    { "Message", messageType },
                                    { "Target", target },
                                    { "PLS", txtPayload },
                                    { "ResponsePLS", response.PLS },
                                })));

                                UpdateStorageList(script.Name, "Finished", stepNumber, script, variablesSnapshot, replay);
                            }
                            else
                            {
                                TheBaseAssets.MySYSLOG.WriteToLog(175004, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error in script step: no outputs found in response", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                                    { "Script", script.Name },
                                    { "Step", stepNumber },
                                    { "Message", messageType },
                                    { "Target", target },
                                    { "PLS", txtPayload },
                                    { "Response", response },
                                    { "ResponsePLS", response?.PLS },
                                })));

                                UpdateStorageList(script.Name, "Error: No Output", stepNumber, script, variablesSnapshot, replay);

                                if (step.DontRetryOnEmptyResponse != true && (remainingRetryCount < 0 || remainingRetryCount > 0))
                                {
                                    remainingRetryCount--;
                                    bDoRetry = true;
                                }
                                string retriesRemaining = bDoRetry ? (remainingRetryCount >= 0 ? $"{remainingRetryCount + 1}" : "infinite") : "none";
                                MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}', step {stepNumber}: no outputs found in response. Retries remaining: {retriesRemaining}");
                            }
                        }
                        else
                        {
                            TheBaseAssets.MySYSLOG.WriteToLog(175005, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error Script step: timeout", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                                { "Script", script.Name },
                                { "Step", stepNumber },
                                { "Message", messageType },
                                { "Target", target },
                                { "PLS", txtPayload },
                                { "Response", response },
                            })));

                            UpdateStorageList(script.Name, "Error: Timeout", stepNumber, script, variablesSnapshot, replay);

                            //Retries infinitely unless count is specified
                            if (remainingRetryCount < 0 || remainingRetryCount > 0)
                            {
                                remainingRetryCount--;
                                bDoRetry = true;
                            }
                            string retriesRemaining = bDoRetry ? (remainingRetryCount >= 0 ? $"{remainingRetryCount + 1}" : "infinite") : "none";
                            MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}', step {stepNumber}: timeout. Retries remaining: {retriesRemaining}");
                        }
                        if (bDoRetry)
                        {
                            await TheCommonUtils.TaskDelayOneEye(30000, 100).ConfigureAwait(false);
                        }
                    } while (bDoRetry && TheBaseAssets.MasterSwitch);
                }
            }
            catch (Exception e)
            {
                TheBaseAssets.MySYSLOG.WriteToLog(175006, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyBaseThing.EngineName, "Error in script step", eMsgLevel.l1_Error, TheCommonUtils.SerializeObjectToJSONString(new Dictionary <String, object> {
                    { "Script", script.Name },
                    { "Exception", e.Message },
                })));
                MyBaseThing.SetStatus(3, $"Error in script '{script?.Name}': {e.Message}");
                //Save variables instead of snapshot in case of error
                UpdateStorageList(script.Name, $"Error: {e.Message}", stepNumber, script, variables, replay);
            }
        }